Update toast replacement animation styles in Calling UI
This commit is contained in:
parent
cfe15b929b
commit
be7b178817
1 changed files with 60 additions and 31 deletions
|
@ -16,6 +16,7 @@ import classNames from 'classnames';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import { useIsMounted } from '../hooks/useIsMounted';
|
import { useIsMounted } from '../hooks/useIsMounted';
|
||||||
import type { LocalizerType } from '../types/I18N';
|
import type { LocalizerType } from '../types/I18N';
|
||||||
|
import { usePrevious } from '../hooks/usePrevious';
|
||||||
|
|
||||||
const DEFAULT_LIFETIME = 5000;
|
const DEFAULT_LIFETIME = 5000;
|
||||||
|
|
||||||
|
@ -55,11 +56,14 @@ const CallingToastContext = createContext<CallingToastContextType | null>(null);
|
||||||
export function CallingToastProvider({
|
export function CallingToastProvider({
|
||||||
i18n,
|
i18n,
|
||||||
children,
|
children,
|
||||||
|
maxToasts = 5,
|
||||||
}: {
|
}: {
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
|
maxToasts?: number;
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
const [toasts, setToasts] = React.useState<Array<CallingToastStateType>>([]);
|
const [toasts, setToasts] = React.useState<Array<CallingToastStateType>>([]);
|
||||||
|
const previousToasts = usePrevious([], toasts);
|
||||||
const timeouts = React.useRef<Map<string, TimeoutType>>(new Map());
|
const timeouts = React.useRef<Map<string, TimeoutType>>(new Map());
|
||||||
// All toasts are paused on hover or focus so that toasts don't disappear while a user
|
// All toasts are paused on hover or focus so that toasts don't disappear while a user
|
||||||
// is attempting to interact with them
|
// is attempting to interact with them
|
||||||
|
@ -67,17 +71,21 @@ export function CallingToastProvider({
|
||||||
const shownToasts = React.useRef<Set<string>>(new Set());
|
const shownToasts = React.useRef<Set<string>>(new Set());
|
||||||
const isMounted = useIsMounted();
|
const isMounted = useIsMounted();
|
||||||
|
|
||||||
|
const clearToastTimeout = useCallback((key: string) => {
|
||||||
|
const timeout = timeouts.current.get(key);
|
||||||
|
if (timeout?.status === 'active') {
|
||||||
|
clearTimeout(timeout.timeout);
|
||||||
|
}
|
||||||
|
timeouts.current.delete(key);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const hideToast = useCallback(
|
const hideToast = useCallback(
|
||||||
(key: string) => {
|
(key: string) => {
|
||||||
if (!isMounted()) {
|
if (!isMounted()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const timeout = timeouts.current.get(key);
|
clearToastTimeout(key);
|
||||||
if (timeout?.status === 'active') {
|
|
||||||
clearTimeout(timeout.timeout);
|
|
||||||
}
|
|
||||||
timeouts.current.delete(key);
|
|
||||||
|
|
||||||
setToasts(state => {
|
setToasts(state => {
|
||||||
const existingIndex = state.findIndex(toast => toast.key === key);
|
const existingIndex = state.findIndex(toast => toast.key === key);
|
||||||
|
@ -92,7 +100,7 @@ export function CallingToastProvider({
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[isMounted]
|
[isMounted, clearToastTimeout]
|
||||||
);
|
);
|
||||||
|
|
||||||
const startTimer = useCallback(
|
const startTimer = useCallback(
|
||||||
|
@ -127,17 +135,24 @@ export function CallingToastProvider({
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (state.length === maxToasts) {
|
||||||
|
const toastToBePushedOut = state.at(-1);
|
||||||
|
if (toastToBePushedOut) {
|
||||||
|
clearToastTimeout(toastToBePushedOut.key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (toast.autoClose) {
|
if (toast.autoClose) {
|
||||||
startTimer(key, DEFAULT_LIFETIME);
|
startTimer(key, DEFAULT_LIFETIME);
|
||||||
}
|
}
|
||||||
shownToasts.current.add(key);
|
shownToasts.current.add(key);
|
||||||
|
|
||||||
return [{ ...toast, key }, ...state];
|
return [{ ...toast, key }, ...state.slice(0, maxToasts - 1)];
|
||||||
});
|
});
|
||||||
|
|
||||||
return key;
|
return key;
|
||||||
},
|
},
|
||||||
[startTimer]
|
[startTimer, clearToastTimeout, maxToasts]
|
||||||
);
|
);
|
||||||
|
|
||||||
const pauseAll = useCallback(() => {
|
const pauseAll = useCallback(() => {
|
||||||
|
@ -181,7 +196,15 @@ export function CallingToastProvider({
|
||||||
const TOAST_HEIGHT_PX = 42;
|
const TOAST_HEIGHT_PX = 42;
|
||||||
const TOAST_GAP_PX = 8;
|
const TOAST_GAP_PX = 8;
|
||||||
const transitions = useTransition(toasts, {
|
const transitions = useTransition(toasts, {
|
||||||
from: { opacity: 0, marginTop: `${-1 * TOAST_HEIGHT_PX}px` },
|
from: item => ({
|
||||||
|
opacity: 0,
|
||||||
|
marginTop:
|
||||||
|
// If this is the first toast shown, or if this is replacing the
|
||||||
|
// first toast, we just fade-in (and don't slide down)
|
||||||
|
previousToasts.length === 0 || item.key === previousToasts[0].key
|
||||||
|
? '0px'
|
||||||
|
: `${-1 * TOAST_HEIGHT_PX}px`,
|
||||||
|
}),
|
||||||
enter: {
|
enter: {
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
zIndex: 1,
|
zIndex: 1,
|
||||||
|
@ -196,28 +219,34 @@ export function CallingToastProvider({
|
||||||
return {};
|
return {};
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
leave: (_item, index) => ({
|
leave: item => {
|
||||||
zIndex: 0,
|
return {
|
||||||
opacity: 0,
|
zIndex: 0,
|
||||||
marginTop:
|
opacity: 0,
|
||||||
// If the last toast in the list is leaving, we don't need to move it up. Its
|
// If the last toast in the list is leaving, we don't need to move it up.
|
||||||
// index is toasts.length instead of toasts.length - 1 since it has already been
|
marginTop:
|
||||||
// removed from state
|
previousToasts.findIndex(toast => toast.key === item.key) ===
|
||||||
index === toasts.length
|
previousToasts.length - 1
|
||||||
? '0px'
|
? '0px'
|
||||||
: `${-1 * (TOAST_HEIGHT_PX + TOAST_GAP_PX)}px`,
|
: `${-1 * (TOAST_HEIGHT_PX + TOAST_GAP_PX)}px`,
|
||||||
config: (key: string) => {
|
// If this toast is being replaced by another one with the same key, immediately
|
||||||
if (key === 'zIndex') {
|
// hide it
|
||||||
return { duration: 0 };
|
display: toasts.some(toast => toast.key === item.key)
|
||||||
}
|
? 'none'
|
||||||
if (key === 'opacity') {
|
: 'block',
|
||||||
return { duration: 100 };
|
config: (key: string) => {
|
||||||
}
|
if (key === 'zIndex') {
|
||||||
return {
|
return { duration: 0 };
|
||||||
duration: 300,
|
}
|
||||||
};
|
if (key === 'opacity') {
|
||||||
},
|
return { duration: 100 };
|
||||||
}),
|
}
|
||||||
|
return {
|
||||||
|
duration: 300,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const contextValue = useMemo(() => {
|
const contextValue = useMemo(() => {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue