Refactor portals/outside clicks in sticker creator
This commit is contained in:
parent
8172840535
commit
4973dac57a
11 changed files with 96 additions and 198 deletions
|
@ -25,6 +25,7 @@
|
||||||
"@formatjs/fast-memoize": "1.2.8",
|
"@formatjs/fast-memoize": "1.2.8",
|
||||||
"@indutny/emoji-picker-react": "4.4.9",
|
"@indutny/emoji-picker-react": "4.4.9",
|
||||||
"@popperjs/core": "2.11.7",
|
"@popperjs/core": "2.11.7",
|
||||||
|
"@react-aria/interactions": "3.19.0",
|
||||||
"@reduxjs/toolkit": "1.9.5",
|
"@reduxjs/toolkit": "1.9.5",
|
||||||
"@stablelib/x25519": "1.0.3",
|
"@stablelib/x25519": "1.0.3",
|
||||||
"base64-js": "1.5.1",
|
"base64-js": "1.5.1",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright 2023 Signal Messenger, LLC
|
// Copyright 2023 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import React from 'react';
|
import React, { useRef } from 'react';
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import {
|
import {
|
||||||
|
@ -11,6 +11,7 @@ import {
|
||||||
} from 'react-popper';
|
} from 'react-popper';
|
||||||
import type { EmojiClickData } from '@indutny/emoji-picker-react';
|
import type { EmojiClickData } from '@indutny/emoji-picker-react';
|
||||||
|
|
||||||
|
import { useInteractOutside } from '@react-aria/interactions';
|
||||||
import { AddEmoji } from '../elements/icons';
|
import { AddEmoji } from '../elements/icons';
|
||||||
import type { Props as DropZoneProps } from '../elements/DropZone';
|
import type { Props as DropZoneProps } from '../elements/DropZone';
|
||||||
import { DropZone } from '../elements/DropZone';
|
import { DropZone } from '../elements/DropZone';
|
||||||
|
@ -19,11 +20,9 @@ import { Spinner } from '../elements/Spinner';
|
||||||
import styles from './ArtFrame.module.scss';
|
import styles from './ArtFrame.module.scss';
|
||||||
import { useI18n } from '../contexts/I18n';
|
import { useI18n } from '../contexts/I18n';
|
||||||
import { assert } from '../util/assert';
|
import { assert } from '../util/assert';
|
||||||
import { noop } from '../util/noop';
|
|
||||||
import { ArtType } from '../constants';
|
import { ArtType } from '../constants';
|
||||||
import type { EmojiData } from '../types.d';
|
import type { EmojiData } from '../types.d';
|
||||||
import EMOJI_SHEET from '../assets/emoji.webp';
|
import EMOJI_SHEET from '../assets/emoji.webp';
|
||||||
import { PopperRootContext } from './PopperRootContext';
|
|
||||||
import EmojiPicker from './EmojiPicker';
|
import EmojiPicker from './EmojiPicker';
|
||||||
|
|
||||||
export type Mode = 'removable' | 'pick-emoji' | 'add';
|
export type Mode = 'removable' | 'pick-emoji' | 'add';
|
||||||
|
@ -71,11 +70,9 @@ export const ArtFrame = React.memo(function ArtFrame({
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const [emojiPickerOpen, setEmojiPickerOpen] = React.useState(false);
|
const [emojiPickerOpen, setEmojiPickerOpen] = React.useState(false);
|
||||||
const [emojiPopperRoot, setEmojiPopperRoot] =
|
const emojiPickerPopperRef = useRef<HTMLElement>(null);
|
||||||
React.useState<HTMLElement | null>(null);
|
|
||||||
const [previewActive, setPreviewActive] = React.useState(false);
|
const [previewActive, setPreviewActive] = React.useState(false);
|
||||||
const [previewPopperRoot, setPreviewPopperRoot] =
|
const previewPopperRef = useRef<HTMLElement>(null);
|
||||||
React.useState<HTMLElement | null>(null);
|
|
||||||
const timerRef = React.useRef<number>();
|
const timerRef = React.useRef<number>();
|
||||||
|
|
||||||
const handleToggleEmojiPicker = React.useCallback(() => {
|
const handleToggleEmojiPicker = React.useCallback(() => {
|
||||||
|
@ -138,51 +135,19 @@ export const ArtFrame = React.memo(function ArtFrame({
|
||||||
[timerRef]
|
[timerRef]
|
||||||
);
|
);
|
||||||
|
|
||||||
const { createRoot, removeRoot } = React.useContext(PopperRootContext);
|
useInteractOutside({
|
||||||
|
ref: emojiPickerPopperRef,
|
||||||
|
onInteractOutside() {
|
||||||
|
setEmojiPickerOpen(false);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// Create popper root and handle outside clicks
|
useInteractOutside({
|
||||||
React.useEffect(() => {
|
ref: previewPopperRef,
|
||||||
if (emojiPickerOpen) {
|
onInteractOutside() {
|
||||||
const root = createRoot();
|
setPreviewActive(false);
|
||||||
setEmojiPopperRoot(root);
|
},
|
||||||
const handleOutsideClick = ({ target }: MouseEvent) => {
|
});
|
||||||
const targetNode = target as HTMLElement;
|
|
||||||
const button = targetNode.closest(`button.${styles.emojiButton}`);
|
|
||||||
if (!root.contains(targetNode) && !button) {
|
|
||||||
setEmojiPickerOpen(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
document.addEventListener('click', handleOutsideClick);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
removeRoot(root);
|
|
||||||
setEmojiPopperRoot(null);
|
|
||||||
document.removeEventListener('click', handleOutsideClick);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return noop;
|
|
||||||
}, [createRoot, emojiPickerOpen, removeRoot]);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (mode !== 'pick-emoji' && image && previewActive) {
|
|
||||||
const root = createRoot();
|
|
||||||
setPreviewPopperRoot(root);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
removeRoot(root);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return noop;
|
|
||||||
}, [
|
|
||||||
createRoot,
|
|
||||||
image,
|
|
||||||
mode,
|
|
||||||
previewActive,
|
|
||||||
removeRoot,
|
|
||||||
setPreviewPopperRoot,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const [dragActive, setDragActive] = React.useState<boolean>(false);
|
const [dragActive, setDragActive] = React.useState<boolean>(false);
|
||||||
|
|
||||||
|
@ -246,23 +211,27 @@ export const ArtFrame = React.memo(function ArtFrame({
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</PopperReference>
|
</PopperReference>
|
||||||
{emojiPickerOpen && emojiPopperRoot
|
{emojiPickerOpen
|
||||||
? createPortal(
|
? createPortal(
|
||||||
<Popper placement="bottom-start">
|
<Popper
|
||||||
|
innerRef={emojiPickerPopperRef}
|
||||||
|
placement="bottom-start"
|
||||||
|
>
|
||||||
{({ ref, style }) => (
|
{({ ref, style }) => (
|
||||||
<div ref={ref} style={{ ...style, marginTop: '8px' }}>
|
<div ref={ref} style={{ ...style, marginTop: '8px' }}>
|
||||||
<EmojiPicker onEmojiClick={handlePickEmoji} />
|
<EmojiPicker onEmojiClick={handlePickEmoji} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Popper>,
|
</Popper>,
|
||||||
emojiPopperRoot
|
document.body
|
||||||
)
|
)
|
||||||
: null}
|
: null}
|
||||||
</PopperManager>
|
</PopperManager>
|
||||||
) : null}
|
) : null}
|
||||||
{mode !== 'pick-emoji' && image && previewActive && previewPopperRoot
|
{mode !== 'pick-emoji' && image && previewActive
|
||||||
? createPortal(
|
? createPortal(
|
||||||
<Popper
|
<Popper
|
||||||
|
innerRef={previewPopperRef}
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
modifiers={[
|
modifiers={[
|
||||||
{ name: 'offset', options: { offset: [undefined, 8] } },
|
{ name: 'offset', options: { offset: [undefined, 8] } },
|
||||||
|
@ -281,7 +250,7 @@ export const ArtFrame = React.memo(function ArtFrame({
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
</Popper>,
|
</Popper>,
|
||||||
previewPopperRoot
|
document.body
|
||||||
)
|
)
|
||||||
: null}
|
: null}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,45 +1,23 @@
|
||||||
// Copyright 2023 Signal Messenger, LLC
|
// Copyright 2023 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import React from 'react';
|
import React, { useRef } from 'react';
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
|
import { useInteractOutside } from '@react-aria/interactions';
|
||||||
import styles from './ConfirmModal.module.scss';
|
import styles from './ConfirmModal.module.scss';
|
||||||
import type { Props } from '../elements/ConfirmDialog';
|
import type { Props } from '../elements/ConfirmDialog';
|
||||||
import { ConfirmDialog } from '../elements/ConfirmDialog';
|
import { ConfirmDialog } from '../elements/ConfirmDialog';
|
||||||
|
|
||||||
export type Mode = 'removable' | 'pick-emoji' | 'add';
|
export type Mode = 'removable' | 'pick-emoji' | 'add';
|
||||||
|
|
||||||
export const ConfirmModal = React.memo(function ConfirmModalInner(
|
export function ConfirmModal(props: Props): JSX.Element {
|
||||||
props: Props & { buttonRef: React.RefObject<HTMLElement> }
|
const { onCancel } = props;
|
||||||
) {
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
const { buttonRef, onCancel } = props;
|
useInteractOutside({ ref, onInteractOutside: onCancel });
|
||||||
const [popperRoot, setPopperRoot] = React.useState<HTMLDivElement>();
|
return createPortal(
|
||||||
|
<div className={styles.facade}>
|
||||||
// Create popper root and handle outside clicks
|
<ConfirmDialog ref={ref} {...props} />
|
||||||
React.useEffect(() => {
|
</div>,
|
||||||
const root = document.createElement('div');
|
document.body
|
||||||
setPopperRoot(root);
|
);
|
||||||
document.body.appendChild(root);
|
}
|
||||||
const handleOutsideClick = ({ target }: MouseEvent) => {
|
|
||||||
const node = target as Node;
|
|
||||||
if (!root.contains(node) && !buttonRef.current?.contains(node)) {
|
|
||||||
onCancel();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
document.addEventListener('click', handleOutsideClick);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
document.body.removeChild(root);
|
|
||||||
document.removeEventListener('click', handleOutsideClick);
|
|
||||||
};
|
|
||||||
}, [onCancel, buttonRef]);
|
|
||||||
|
|
||||||
return popperRoot
|
|
||||||
? createPortal(
|
|
||||||
<div className={styles.facade}>
|
|
||||||
<ConfirmDialog {...props} />
|
|
||||||
</div>,
|
|
||||||
popperRoot
|
|
||||||
)
|
|
||||||
: null;
|
|
||||||
});
|
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
// Copyright 2020 Signal Messenger, LLC
|
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
const makeApi = (classes?: Array<string>) => ({
|
|
||||||
createRoot: () => {
|
|
||||||
const div = document.createElement('div');
|
|
||||||
|
|
||||||
if (classes) {
|
|
||||||
classes.forEach(theme => {
|
|
||||||
div.classList.add(theme);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
document.body.appendChild(div);
|
|
||||||
|
|
||||||
return div;
|
|
||||||
},
|
|
||||||
removeRoot: (root: HTMLElement) => {
|
|
||||||
document.body.removeChild(root);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const PopperRootContext = React.createContext(makeApi());
|
|
||||||
|
|
||||||
export type ClassyProviderProps = {
|
|
||||||
classes?: Array<string>;
|
|
||||||
children?: React.ReactNode;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function ClassyProvider({
|
|
||||||
classes,
|
|
||||||
children,
|
|
||||||
}: ClassyProviderProps): JSX.Element {
|
|
||||||
const api = React.useMemo(() => makeApi(classes), [classes]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<PopperRootContext.Provider value={api}>
|
|
||||||
{children}
|
|
||||||
</PopperRootContext.Provider>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -31,7 +31,6 @@ const getClassName = ({ primary, pill }: Props) => {
|
||||||
export function Button({
|
export function Button({
|
||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
buttonRef,
|
|
||||||
primary,
|
primary,
|
||||||
...otherProps
|
...otherProps
|
||||||
}: React.PropsWithChildren<Props>): JSX.Element {
|
}: React.PropsWithChildren<Props>): JSX.Element {
|
||||||
|
@ -42,7 +41,6 @@ export function Button({
|
||||||
getClassName({ primary, ...otherProps }),
|
getClassName({ primary, ...otherProps }),
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
ref={buttonRef}
|
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
// Copyright 2023 Signal Messenger, LLC
|
// Copyright 2023 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import React from 'react';
|
import type { Ref } from 'react';
|
||||||
|
import React, { forwardRef } from 'react';
|
||||||
|
|
||||||
import { useI18n } from '../contexts/I18n';
|
import { useI18n } from '../contexts/I18n';
|
||||||
import styles from './ConfirmDialog.module.scss';
|
import styles from './ConfirmDialog.module.scss';
|
||||||
|
@ -16,19 +17,15 @@ export type Props = Readonly<{
|
||||||
onCancel: () => unknown;
|
onCancel: () => unknown;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export function ConfirmDialog({
|
export const ConfirmDialog = forwardRef(function ConfirmDialog(
|
||||||
title,
|
{ title, children, confirm, cancel, onConfirm, onCancel }: Props,
|
||||||
children,
|
ref: Ref<HTMLDivElement>
|
||||||
confirm,
|
): JSX.Element {
|
||||||
cancel,
|
|
||||||
onConfirm,
|
|
||||||
onCancel,
|
|
||||||
}: Props): JSX.Element {
|
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const cancelText = cancel || i18n('StickerCreator--ConfirmDialog--cancel');
|
const cancelText = cancel || i18n('StickerCreator--ConfirmDialog--cancel');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.base}>
|
<div ref={ref} className={styles.base}>
|
||||||
<h1 className={styles.title}>{title}</h1>
|
<h1 className={styles.title}>{title}</h1>
|
||||||
<p className={styles.text}>{children}</p>
|
<p className={styles.text}>{children}</p>
|
||||||
<div className={styles.grow} />
|
<div className={styles.grow} />
|
||||||
|
@ -40,4 +37,4 @@ export function ConfirmDialog({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|
|
@ -81,6 +81,6 @@
|
||||||
}
|
}
|
||||||
&:dir(rtl) {
|
&:dir(rtl) {
|
||||||
// stylelint-disable-next-line declaration-property-value-disallowed-list
|
// stylelint-disable-next-line declaration-property-value-disallowed-list
|
||||||
transform: translate(50%, 0px)
|
transform: translate(50%, 0px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,6 @@ export type Props = Readonly<{
|
||||||
noScroll?: boolean;
|
noScroll?: boolean;
|
||||||
onNext?: () => unknown;
|
onNext?: () => unknown;
|
||||||
onPrev?: () => unknown;
|
onPrev?: () => unknown;
|
||||||
nextButtonRef?: React.RefObject<HTMLButtonElement>;
|
|
||||||
nextText?: string;
|
nextText?: string;
|
||||||
showGuide?: boolean;
|
showGuide?: boolean;
|
||||||
setShowGuide?: (value: boolean) => unknown;
|
setShowGuide?: (value: boolean) => unknown;
|
||||||
|
@ -48,7 +47,6 @@ export function AppStage(props: Props): JSX.Element {
|
||||||
next,
|
next,
|
||||||
nextActive,
|
nextActive,
|
||||||
nextText,
|
nextText,
|
||||||
nextButtonRef,
|
|
||||||
noScroll,
|
noScroll,
|
||||||
onNext,
|
onNext,
|
||||||
onPrev,
|
onPrev,
|
||||||
|
@ -99,7 +97,6 @@ export function AppStage(props: Props): JSX.Element {
|
||||||
) : null}
|
) : null}
|
||||||
{next || onNext ? (
|
{next || onNext ? (
|
||||||
<Button
|
<Button
|
||||||
buttonRef={nextButtonRef}
|
|
||||||
className={styles.button}
|
className={styles.button}
|
||||||
onClick={onNext || handleNext}
|
onClick={onNext || handleNext}
|
||||||
primary
|
primary
|
||||||
|
|
|
@ -35,7 +35,6 @@ export function MetaStage(): JSX.Element {
|
||||||
const title = useTitle();
|
const title = useTitle();
|
||||||
const author = useAuthor();
|
const author = useAuthor();
|
||||||
const [confirming, setConfirming] = React.useState(false);
|
const [confirming, setConfirming] = React.useState(false);
|
||||||
const buttonRef = React.useRef<HTMLButtonElement | null>(null);
|
|
||||||
|
|
||||||
const onDrop = React.useCallback(
|
const onDrop = React.useCallback(
|
||||||
async ([file]: Array<FileWithPath>) => {
|
async ([file]: Array<FileWithPath>) => {
|
||||||
|
@ -69,7 +68,6 @@ export function MetaStage(): JSX.Element {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppStage
|
<AppStage
|
||||||
nextButtonRef={buttonRef}
|
|
||||||
onNext={onNext}
|
onNext={onNext}
|
||||||
nextActive={valid}
|
nextActive={valid}
|
||||||
noMessage
|
noMessage
|
||||||
|
@ -83,7 +81,6 @@ export function MetaStage(): JSX.Element {
|
||||||
confirm={i18n('StickerCreator--MetaStage--ConfirmDialog--confirm')}
|
confirm={i18n('StickerCreator--MetaStage--ConfirmDialog--confirm')}
|
||||||
onCancel={onCancel}
|
onCancel={onCancel}
|
||||||
onConfirm={onConfirm}
|
onConfirm={onConfirm}
|
||||||
buttonRef={buttonRef}
|
|
||||||
>
|
>
|
||||||
{i18n(`StickerCreator--MetaStage--ConfirmDialog--text--${artType}`)}
|
{i18n(`StickerCreator--MetaStage--ConfirmDialog--text--${artType}`)}
|
||||||
</ConfirmModal>
|
</ConfirmModal>
|
||||||
|
|
|
@ -585,6 +585,46 @@
|
||||||
resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570"
|
resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570"
|
||||||
integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==
|
integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==
|
||||||
|
|
||||||
|
"@react-aria/interactions@3.19.0":
|
||||||
|
version "3.19.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@react-aria/interactions/-/interactions-3.19.0.tgz#37c14668e43f5c1fc737ddfa3cdf77311ce9c858"
|
||||||
|
integrity sha512-nJ8VTmEOYJAAvV7wzeQVnamxWd3j16hGAzG++onjhluSWWKO1jMRN6WG9LDwvT5mBI0VYwf7JdVB3QBaCa9fsQ==
|
||||||
|
dependencies:
|
||||||
|
"@react-aria/ssr" "^3.8.0"
|
||||||
|
"@react-aria/utils" "^3.21.0"
|
||||||
|
"@react-types/shared" "^3.21.0"
|
||||||
|
"@swc/helpers" "^0.5.0"
|
||||||
|
|
||||||
|
"@react-aria/ssr@^3.8.0":
|
||||||
|
version "3.8.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@react-aria/ssr/-/ssr-3.8.0.tgz#e7f467ac42f72504682724304ce221f785d70d49"
|
||||||
|
integrity sha512-Y54xs483rglN5DxbwfCPHxnkvZ+gZ0LbSYmR72LyWPGft8hN/lrl1VRS1EW2SMjnkEWlj+Km2mwvA3kEHDUA0A==
|
||||||
|
dependencies:
|
||||||
|
"@swc/helpers" "^0.5.0"
|
||||||
|
|
||||||
|
"@react-aria/utils@^3.21.0":
|
||||||
|
version "3.21.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@react-aria/utils/-/utils-3.21.0.tgz#147a91188d74f1aa284ad6d3d7db24b0de069fca"
|
||||||
|
integrity sha512-0ZNaXgvbWnqqiG7FB0qhAIENN7CmBU30AnyTzz5ZZgvJexUJkhd2GMjvTqrBZ6zSjeMpUEIKg5PUA1eptGRPww==
|
||||||
|
dependencies:
|
||||||
|
"@react-aria/ssr" "^3.8.0"
|
||||||
|
"@react-stately/utils" "^3.8.0"
|
||||||
|
"@react-types/shared" "^3.21.0"
|
||||||
|
"@swc/helpers" "^0.5.0"
|
||||||
|
clsx "^1.1.1"
|
||||||
|
|
||||||
|
"@react-stately/utils@^3.8.0":
|
||||||
|
version "3.8.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@react-stately/utils/-/utils-3.8.0.tgz#88a45742c58bde804f6cbecb20ea3833915cfdf0"
|
||||||
|
integrity sha512-wCIoFDbt/uwNkWIBF+xV+21k8Z8Sj5qGO3uptTcVmjYcZngOaGGyB4NkiuZhmhG70Pkv+yVrRwoC1+4oav9cCg==
|
||||||
|
dependencies:
|
||||||
|
"@swc/helpers" "^0.5.0"
|
||||||
|
|
||||||
|
"@react-types/shared@^3.21.0":
|
||||||
|
version "3.21.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@react-types/shared/-/shared-3.21.0.tgz#1af41fdf7dfbdbd33bbc1210617c43ed0d4ef20c"
|
||||||
|
integrity sha512-wJA2cUF8dP4LkuNUt9Vh2kkfiQb2NLnV2pPXxVnKJZ7d4x2/7VPccN+LYPnH8m0X3+rt50cxWuPKQmjxSsCFOg==
|
||||||
|
|
||||||
"@reduxjs/toolkit@1.9.5":
|
"@reduxjs/toolkit@1.9.5":
|
||||||
version "1.9.5"
|
version "1.9.5"
|
||||||
resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-1.9.5.tgz#d3987849c24189ca483baa7aa59386c8e52077c4"
|
resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-1.9.5.tgz#d3987849c24189ca483baa7aa59386c8e52077c4"
|
||||||
|
@ -646,6 +686,13 @@
|
||||||
"@stablelib/random" "^1.0.2"
|
"@stablelib/random" "^1.0.2"
|
||||||
"@stablelib/wipe" "^1.0.1"
|
"@stablelib/wipe" "^1.0.1"
|
||||||
|
|
||||||
|
"@swc/helpers@^0.5.0":
|
||||||
|
version "0.5.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.2.tgz#85ea0c76450b61ad7d10a37050289eded783c27d"
|
||||||
|
integrity sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==
|
||||||
|
dependencies:
|
||||||
|
tslib "^2.4.0"
|
||||||
|
|
||||||
"@types/chai-subset@^1.3.3":
|
"@types/chai-subset@^1.3.3":
|
||||||
version "1.3.3"
|
version "1.3.3"
|
||||||
resolved "https://registry.yarnpkg.com/@types/chai-subset/-/chai-subset-1.3.3.tgz#97893814e92abd2c534de422cb377e0e0bdaac94"
|
resolved "https://registry.yarnpkg.com/@types/chai-subset/-/chai-subset-1.3.3.tgz#97893814e92abd2c534de422cb377e0e0bdaac94"
|
||||||
|
@ -1213,7 +1260,7 @@ cliui@^8.0.1:
|
||||||
strip-ansi "^6.0.1"
|
strip-ansi "^6.0.1"
|
||||||
wrap-ansi "^7.0.0"
|
wrap-ansi "^7.0.0"
|
||||||
|
|
||||||
clsx@^1.2.1:
|
clsx@^1.1.1, clsx@^1.2.1:
|
||||||
version "1.2.1"
|
version "1.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12"
|
resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12"
|
||||||
integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==
|
integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
// Copyright 2020 Signal Messenger, LLC
|
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
import * as React from 'react';
|
|
||||||
|
|
||||||
const makeApi = (classes?: Array<string>) => ({
|
|
||||||
createRoot: () => {
|
|
||||||
const div = document.createElement('div');
|
|
||||||
|
|
||||||
if (classes) {
|
|
||||||
classes.forEach(theme => {
|
|
||||||
div.classList.add(theme);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
document.body.appendChild(div);
|
|
||||||
|
|
||||||
return div;
|
|
||||||
},
|
|
||||||
removeRoot: (root: HTMLElement) => {
|
|
||||||
document.body.removeChild(root);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const PopperRootContext = React.createContext(makeApi());
|
|
||||||
|
|
||||||
export type ClassyProviderProps = {
|
|
||||||
classes?: Array<string>;
|
|
||||||
children?: React.ReactChildren;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function ClassyProvider({
|
|
||||||
classes,
|
|
||||||
children,
|
|
||||||
}: ClassyProviderProps): JSX.Element {
|
|
||||||
const api = React.useMemo(() => makeApi(classes), [classes]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<PopperRootContext.Provider value={api}>
|
|
||||||
{children}
|
|
||||||
</PopperRootContext.Provider>
|
|
||||||
);
|
|
||||||
}
|
|
Loading…
Add table
Add a link
Reference in a new issue