Skip to content

Commit

Permalink
Merge branch 'develop' into fix/disabled-retention-warning
Browse files Browse the repository at this point in the history
  • Loading branch information
kodiakhq[bot] committed Sep 6, 2024
2 parents 49baaaa + b8effb5 commit 68814eb
Show file tree
Hide file tree
Showing 10 changed files with 123 additions and 55 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ Free for 30 days. Afterward, choose between continuing to host on our secure clo
You can follow these instructions to setup a dev environment:

- Install **Node 14.x (LTS)** either [manually](https://nodejs.org/dist/latest-v14.x/) or using a tool like [nvm](https://github.com/creationix/nvm) or [volta](https://volta.sh/) (recommended)
- Install **Meteor** ([version here](apps/meteor/.meteor/release)): https://www.meteor.com/developers/install
- Install **Meteor** ([version here](apps/meteor/.meteor/release)): https://docs.meteor.com/about/install.html
- Install **yarn**: https://yarnpkg.com/getting-started/install
- Clone this repo: `git clone https://github.com/RocketChat/Rocket.Chat.git`
- Run `yarn` to install dependencies
Expand Down
18 changes: 0 additions & 18 deletions apps/meteor/client/lib/utils/createAnchor.ts

This file was deleted.

11 changes: 0 additions & 11 deletions apps/meteor/client/lib/utils/deleteAnchor.ts

This file was deleted.

11 changes: 4 additions & 7 deletions apps/meteor/client/portals/TooltipPortal.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
import { AnchorPortal } from '@rocket.chat/ui-client';
import type { ReactNode } from 'react';
import React, { memo, useEffect, useState } from 'react';
import { createPortal } from 'react-dom';
import React, { memo } from 'react';

import { createAnchor } from '../lib/utils/createAnchor';
import { deleteAnchor } from '../lib/utils/deleteAnchor';
const tooltipAnchorId = 'tooltip-root';

type TooltipPortalProps = {
children?: ReactNode;
};

const TooltipPortal = ({ children }: TooltipPortalProps) => {
const [tooltipRoot] = useState(() => createAnchor('tooltip-root'));
useEffect(() => (): void => deleteAnchor(tooltipRoot), [tooltipRoot]);
return <>{createPortal(children, tooltipRoot)}</>;
return <AnchorPortal id={tooltipAnchorId}>{children}</AnchorPortal>;
};

export default memo(TooltipPortal);
11 changes: 4 additions & 7 deletions apps/meteor/client/portals/VideoConfPopupPortal.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
import { AnchorPortal } from '@rocket.chat/ui-client';
import type { ReactElement, ReactNode } from 'react';
import React, { memo, useEffect, useState } from 'react';
import { createPortal } from 'react-dom';
import React, { memo } from 'react';

import { createAnchor } from '../lib/utils/createAnchor';
import { deleteAnchor } from '../lib/utils/deleteAnchor';
const videoConfAnchorId = 'video-conf-root';

type VideoConfPortalProps = {
children?: ReactNode;
};

const VideoConfPortal = ({ children }: VideoConfPortalProps): ReactElement => {
const [videoConfRoot] = useState(() => createAnchor('video-conf-root'));
useEffect(() => (): void => deleteAnchor(videoConfRoot), [videoConfRoot]);
return <>{createPortal(children, videoConfRoot)}</>;
return <AnchorPortal id={videoConfAnchorId}>{children}</AnchorPortal>;
};

export default memo(VideoConfPortal);
45 changes: 45 additions & 0 deletions packages/ui-client/src/components/AnchorPortal.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { render, screen } from '@testing-library/react';

import AnchorPortal from './AnchorPortal';

it('should render children', () => {
render(<AnchorPortal id='test-anchor' children={<div role='presentation' aria-label='example' />} />, { legacyRoot: true });

expect(screen.getByRole('presentation', { name: 'example' })).toBeInTheDocument();
});

it('should not recreate the anchor element', () => {
render(<AnchorPortal id='test-anchor' children={<div role='presentation' aria-label='example A' />} />, { legacyRoot: true });
const anchorA = document.getElementById('test-anchor');

render(<AnchorPortal id='test-anchor' children={<div role='presentation' aria-label='example B' />} />, { legacyRoot: true });
const anchorB = document.getElementById('test-anchor');

expect(anchorA).toBe(anchorB);
expect(screen.getByRole('presentation', { name: 'example A' })).toBeInTheDocument();
expect(screen.getByRole('presentation', { name: 'example B' })).toBeInTheDocument();
});

it('should remove the anchor element when unmounted', () => {
const { unmount } = render(<AnchorPortal id='test-anchor' children={<div role='presentation' aria-label='example' />} />, {
legacyRoot: true,
});
expect(document.getElementById('test-anchor')).toBeInTheDocument();

unmount();
expect(document.getElementById('test-anchor')).not.toBeInTheDocument();
});

it('should not remove the anchor element after unmounting if there are other portals with the same id', () => {
const { unmount } = render(<AnchorPortal id='test-anchor' children={<div role='presentation' aria-label='example' />} />, {
legacyRoot: true,
});
expect(document.getElementById('test-anchor')).toBeInTheDocument();

render(<AnchorPortal id='test-anchor' children={<div role='presentation' aria-label='example' />} />, {
legacyRoot: true,
});
unmount();

expect(document.getElementById('test-anchor')).toBeInTheDocument();
});
25 changes: 25 additions & 0 deletions packages/ui-client/src/components/AnchorPortal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { ReactNode, useLayoutEffect } from 'react';
import { createPortal } from 'react-dom';

import { ensureAnchorElement, refAnchorElement, unrefAnchorElement } from '../helpers/anchors';

export type AnchorPortalProps = {
id: string;
children: ReactNode;
};

const AnchorPortal = ({ id, children }: AnchorPortalProps) => {
const anchorElement = ensureAnchorElement(id);

useLayoutEffect(() => {
refAnchorElement(anchorElement);

return () => {
unrefAnchorElement(anchorElement);
};
}, [anchorElement]);

return <>{createPortal(children, anchorElement)}</>;
};

export default AnchorPortal;
1 change: 1 addition & 0 deletions packages/ui-client/src/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { default as AnchorPortal, AnchorPortalProps } from './AnchorPortal';
export * from './EmojiPicker';
export * from './ExternalLink';
export * from './DotLeader';
Expand Down
32 changes: 32 additions & 0 deletions packages/ui-client/src/helpers/anchors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
export const ensureAnchorElement = (id: string): HTMLElement => {
const existingAnchor = document.getElementById(id);
if (existingAnchor) return existingAnchor;

const newAnchor = document.createElement('div');
newAnchor.id = id;
document.body.appendChild(newAnchor);
return newAnchor;
};

const getAnchorRefCount = (anchorElement: HTMLElement): number => {
const { refCount } = anchorElement.dataset;
if (refCount) return parseInt(refCount, 10);
return 0;
};

const setAnchorRefCount = (anchorElement: HTMLElement, refCount: number): void => {
anchorElement.dataset.refCount = String(refCount);
};

export const refAnchorElement = (anchorElement: HTMLElement): void => {
setAnchorRefCount(anchorElement, getAnchorRefCount(anchorElement) + 1);
};

export const unrefAnchorElement = (anchorElement: HTMLElement): void => {
const refCount = getAnchorRefCount(anchorElement) - 1;
setAnchorRefCount(anchorElement, refCount);

if (refCount <= 0) {
document.body.removeChild(anchorElement);
}
};
22 changes: 11 additions & 11 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -8934,10 +8934,10 @@ __metadata:
"@rocket.chat/icons": "*"
"@rocket.chat/prettier-config": "*"
"@rocket.chat/styled": "*"
"@rocket.chat/ui-avatar": 6.0.0-rc.6
"@rocket.chat/ui-contexts": 10.0.0-rc.6
"@rocket.chat/ui-kit": 0.36.1-rc.0
"@rocket.chat/ui-video-conf": 10.0.0-rc.6
"@rocket.chat/ui-avatar": 6.0.0
"@rocket.chat/ui-contexts": 10.0.0
"@rocket.chat/ui-kit": 0.36.1
"@rocket.chat/ui-video-conf": 10.0.0
"@tanstack/react-query": "*"
react: "*"
react-dom: "*"
Expand Down Expand Up @@ -9021,8 +9021,8 @@ __metadata:
"@rocket.chat/fuselage-tokens": "*"
"@rocket.chat/message-parser": 0.31.29
"@rocket.chat/styled": "*"
"@rocket.chat/ui-client": 10.0.0-rc.6
"@rocket.chat/ui-contexts": 10.0.0-rc.6
"@rocket.chat/ui-client": 10.0.0
"@rocket.chat/ui-contexts": 10.0.0
katex: "*"
react: "*"
languageName: unknown
Expand Down Expand Up @@ -10228,7 +10228,7 @@ __metadata:
typescript: ~5.5.4
peerDependencies:
"@rocket.chat/fuselage": "*"
"@rocket.chat/ui-contexts": 10.0.0-rc.6
"@rocket.chat/ui-contexts": 10.0.0
react: ~17.0.2
languageName: unknown
linkType: soft
Expand Down Expand Up @@ -10278,7 +10278,7 @@ __metadata:
"@rocket.chat/fuselage": "*"
"@rocket.chat/fuselage-hooks": "*"
"@rocket.chat/icons": "*"
"@rocket.chat/ui-contexts": 10.0.0-rc.6
"@rocket.chat/ui-contexts": 10.0.0
react: ~17.0.2
languageName: unknown
linkType: soft
Expand Down Expand Up @@ -10448,8 +10448,8 @@ __metadata:
"@rocket.chat/fuselage-hooks": "*"
"@rocket.chat/icons": "*"
"@rocket.chat/styled": "*"
"@rocket.chat/ui-avatar": 6.0.0-rc.6
"@rocket.chat/ui-contexts": 10.0.0-rc.6
"@rocket.chat/ui-avatar": 6.0.0
"@rocket.chat/ui-contexts": 10.0.0
react: ^17.0.2
react-dom: ^17.0.2
languageName: unknown
Expand Down Expand Up @@ -10536,7 +10536,7 @@ __metadata:
peerDependencies:
"@rocket.chat/layout": "*"
"@rocket.chat/tools": 0.2.2
"@rocket.chat/ui-contexts": 10.0.0-rc.6
"@rocket.chat/ui-contexts": 10.0.0
"@tanstack/react-query": "*"
react: "*"
react-hook-form: "*"
Expand Down

0 comments on commit 68814eb

Please sign in to comment.