Memoize toasts to unstick them in AudioCapture

This commit is contained in:
Josh Perez 2021-10-12 15:09:00 -04:00 committed by GitHub
parent f4b0bade80
commit 7488fa5abc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 85 additions and 77 deletions

View file

@ -1,7 +1,13 @@
// Copyright 2021 Signal Messenger, LLC // Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import React, { KeyboardEvent, MouseEvent, ReactNode, useEffect } from 'react'; import React, {
KeyboardEvent,
MouseEvent,
ReactNode,
memo,
useEffect,
} from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
import { useRestoreFocus } from '../hooks/useRestoreFocus'; import { useRestoreFocus } from '../hooks/useRestoreFocus';
@ -19,88 +25,90 @@ export type PropsType = {
}; };
}; };
export const Toast = ({ export const Toast = memo(
autoDismissDisabled = false, ({
children, autoDismissDisabled = false,
className, children,
disableCloseOnClick = false, className,
onClose, disableCloseOnClick = false,
timeout = 8000, onClose,
toastAction, timeout = 8000,
}: PropsType): JSX.Element | null => { toastAction,
const [root, setRoot] = React.useState<HTMLElement | null>(null); }: PropsType): JSX.Element | null => {
const [focusRef] = useRestoreFocus(); const [root, setRoot] = React.useState<HTMLElement | null>(null);
const [focusRef] = useRestoreFocus();
useEffect(() => { useEffect(() => {
const div = document.createElement('div'); const div = document.createElement('div');
document.body.appendChild(div); document.body.appendChild(div);
setRoot(div); setRoot(div);
return () => { return () => {
document.body.removeChild(div); document.body.removeChild(div);
setRoot(null); setRoot(null);
}; };
}, []); }, []);
useEffect(() => { useEffect(() => {
if (!root || autoDismissDisabled) { if (!root || autoDismissDisabled) {
return; return;
}
const timeoutId = setTimeout(onClose, timeout);
return () => {
if (timeoutId) {
clearTimeout(timeoutId);
} }
};
}, [autoDismissDisabled, onClose, root, timeout]);
return root const timeoutId = setTimeout(onClose, timeout);
? createPortal(
<div return () => {
aria-live="assertive" if (timeoutId) {
className={classNames('Toast', className)} clearTimeout(timeoutId);
onClick={() => { }
if (!disableCloseOnClick) { };
onClose(); }, [autoDismissDisabled, onClose, root, timeout]);
}
}} return root
onKeyDown={(ev: KeyboardEvent<HTMLDivElement>) => { ? createPortal(
if (ev.key === 'Enter' || ev.key === ' ') { <div
aria-live="assertive"
className={classNames('Toast', className)}
onClick={() => {
if (!disableCloseOnClick) { if (!disableCloseOnClick) {
onClose(); onClose();
} }
} }}
}} onKeyDown={(ev: KeyboardEvent<HTMLDivElement>) => {
role="button" if (ev.key === 'Enter' || ev.key === ' ') {
tabIndex={0} if (!disableCloseOnClick) {
> onClose();
<div className="Toast__content">{children}</div> }
{toastAction && ( }
<div }}
className="Toast__button" role="button"
onClick={(ev: MouseEvent<HTMLDivElement>) => { tabIndex={0}
ev.stopPropagation(); >
ev.preventDefault(); <div className="Toast__content">{children}</div>
toastAction.onClick(); {toastAction && (
}} <div
onKeyDown={(ev: KeyboardEvent<HTMLDivElement>) => { className="Toast__button"
if (ev.key === 'Enter' || ev.key === ' ') { onClick={(ev: MouseEvent<HTMLDivElement>) => {
ev.stopPropagation(); ev.stopPropagation();
ev.preventDefault(); ev.preventDefault();
toastAction.onClick(); toastAction.onClick();
} }}
}} onKeyDown={(ev: KeyboardEvent<HTMLDivElement>) => {
ref={focusRef} if (ev.key === 'Enter' || ev.key === ' ') {
role="button" ev.stopPropagation();
tabIndex={0} ev.preventDefault();
> toastAction.onClick();
{toastAction.label} }
</div> }}
)} ref={focusRef}
</div>, role="button"
root tabIndex={0}
) >
: null; {toastAction.label}
}; </div>
)}
</div>,
root
)
: null;
}
);

View file

@ -134,9 +134,9 @@ export const AudioCapture = ({
completeRecording(conversationId, onSendAudioRecording); completeRecording(conversationId, onSendAudioRecording);
}, [conversationId, completeRecording, onSendAudioRecording]); }, [conversationId, completeRecording, onSendAudioRecording]);
function closeToast() { const closeToast = useCallback(() => {
setToastType(undefined); setToastType(undefined);
} }, []);
let toastElement: JSX.Element | undefined; let toastElement: JSX.Element | undefined;
if (toastType === ToastType.VoiceNoteLimit) { if (toastType === ToastType.VoiceNoteLimit) {