Skip to content

Commit f04731f

Browse files
committed
feat(ToastProvider): added modify toast and exists methods
1 parent 95364f5 commit f04731f

File tree

4 files changed

+76
-27
lines changed

4 files changed

+76
-27
lines changed

.storybook/style.scss

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,19 @@ body {
2424
user-select: none;
2525
}
2626

27+
.toastprovider__buttons {
28+
display: flex;
29+
30+
> div {
31+
display: flex;
32+
flex-direction: column;
33+
margin: 0 16px;
34+
}
35+
button {
36+
margin-bottom: 16px;
37+
}
38+
}
39+
2740
.colors__theme {
2841
z-index: $z-index-dragging;
2942
position: sticky;

src/components/ToastProvider/ToastProvider.stories.tsx

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,23 +17,39 @@ const DemoButtons = () => {
1717
const hasTimeout = boolean('has timeout', false, 'ToastContext.add');
1818
const timeout = hasTimeout ? integer('timeout', 0, 'ToastContext.add') : undefined;
1919

20-
const pushToast = (message: ToastProps['message']) => {
21-
toast.add({
20+
const pushToast = (message: ToastProps['message'], loading?: boolean) => {
21+
const id = toast.add({
2222
heading: `A new ${message} toast`,
2323
children,
24-
message,
24+
message: loading ? 'loading' : message,
2525
action: hasAction ? { text: 'Fire Alert', onClick: () => alert('Fired action.') } : undefined,
2626
closable,
2727
timeout,
2828
});
29+
30+
setTimeout(() => {
31+
toast.modify(id, { message });
32+
}, 2000);
2933
};
3034

3135
return (
3236
<div style={{ display: 'grid', justifyContent: 'center', gap: 8 }}>
33-
<Button onClick={() => pushToast('info')}>Add info Toast</Button>
34-
<Button onClick={() => pushToast('success')}>Add success Toast</Button>
35-
<Button onClick={() => pushToast('warning')}>Add warning Toast</Button>
36-
<Button onClick={() => pushToast('error')}>Add error Toast</Button>
37+
<div className="toastprovider__buttons">
38+
<div>
39+
<Button onClick={() => pushToast('info')}>Add info Toast</Button>
40+
<Button onClick={() => pushToast('success')}>Add success Toast</Button>
41+
<Button onClick={() => pushToast('warning')}>Add warning Toast</Button>
42+
<Button onClick={() => pushToast('error')}>Add error Toast</Button>
43+
<Button onClick={() => pushToast('loading')}>Add loading Toast</Button>
44+
</div>
45+
<div>
46+
<Button onClick={() => pushToast('info', true)}>Add delayed info Toast</Button>
47+
<Button onClick={() => pushToast('success', true)}>Add delayed success Toast</Button>
48+
<Button onClick={() => pushToast('warning', true)}>Add delayed warning Toast</Button>
49+
<Button onClick={() => pushToast('error', true)}>Add delayed error Toast</Button>
50+
</div>
51+
</div>
52+
3753
<Button buttonStyle="outline" onClick={() => toast.remove()}>
3854
Pop current Toast
3955
</Button>
@@ -47,7 +63,7 @@ export const Basic = () => (
4763
noTimeout={
4864
(select(
4965
'noTimeout',
50-
['', 'info', 'success', 'warning', 'error'],
66+
['', 'info', 'success', 'warning', 'error', 'loading'],
5167
'',
5268
'ToastProvider',
5369
) as CommonToastProps['message']) || undefined

src/components/ToastProvider/ToastProvider.tsx

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,8 @@ export const ToastProvider: FC<ToastProviderProps> = ({
5555
const onHover = useCallback(() => setHovered(true), []);
5656
const onBlur = useCallback(() => setHovered(false), []);
5757

58-
const _pop = useCallback(() => {
59-
setClosing(true);
60-
}, []);
58+
const _pop = useCallback(() => setClosing(true), []);
59+
6160
useEffect(() => {
6261
if (closing) {
6362
const timeoutId = window.setTimeout(() => {
@@ -89,16 +88,14 @@ export const ToastProvider: FC<ToastProviderProps> = ({
8988
}, [defaultTimeout, noTimeoutTypes, timeout, toastProps.message]);
9089

9190
useEffect(() => {
92-
if (!hovered && toastId && timeoutValue) {
93-
const timeoutId = window.setTimeout(() => {
94-
_pop();
95-
}, timeoutValue);
96-
return () => {
97-
window.clearTimeout(timeoutId);
98-
};
91+
// toastId and toastProps.message are included to reset timer when message
92+
// changes.
93+
if (!hovered && toastId && timeoutValue && toastProps.message) {
94+
const timeoutId = window.setTimeout(_pop, timeoutValue);
95+
return () => window.clearTimeout(timeoutId);
9996
}
10097
return;
101-
}, [_pop, hovered, timeoutValue, toastId]);
98+
}, [_pop, hovered, timeoutValue, toastId, toastProps.message]);
10299

103100
const add = useCallback<ToastContextValue['add']>(toast => {
104101
const toastId = toastIdSequence++;
@@ -117,15 +114,26 @@ export const ToastProvider: FC<ToastProviderProps> = ({
117114
[_pop, toasts],
118115
);
119116

120-
const value = useMemo<ToastContextValue>(
121-
() => ({
122-
add,
123-
remove,
124-
toasts,
125-
}),
126-
[add, remove, toasts],
117+
const modify = useCallback<ToastContextValue['modify']>((id, update) => {
118+
setToasts(prev => {
119+
const index = prev.findIndex(t => t.toastId === id);
120+
if (index === -1) return prev;
121+
return [...prev.slice(0, index), { ...prev[index], ...update }, ...prev.slice(index + 1)];
122+
});
123+
}, []);
124+
125+
const exists = useCallback<ToastContextValue['exists']>(
126+
id => {
127+
const index = toasts.findIndex(t => t.toastId === id);
128+
if (index === -1) return false;
129+
if (closing && index === 0) return false;
130+
return true;
131+
},
132+
[closing, toasts],
127133
);
128134

135+
const value = useMemo<ToastContextValue>(() => ({ add, remove, modify, exists }), [add, remove, modify, exists]);
136+
129137
const padding = useMemo(() => {
130138
if (customPadding === undefined) return undefined;
131139
const isTop = ['top-left', 'top', 'top-right'].includes(position);

src/hooks/useToast.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export interface CommonToastProps {
1616
* icon of the toast.
1717
* @default "info"
1818
*/
19-
message?: 'info' | 'success' | 'warning' | 'error';
19+
message?: 'info' | 'success' | 'warning' | 'error' | 'loading';
2020
/**
2121
* Adds an action button to the toast. Will position to the left of the close
2222
* button if `onClose` was provided.
@@ -54,6 +54,18 @@ export interface ToastContextValue {
5454
* @param toastId The ID of the toast to remove.
5555
*/
5656
remove(toastId?: number): void;
57+
/**
58+
* Modify a toast by providing the ID, then a partial toast object with fields
59+
* you wish to update.
60+
* @param toastId The ID of the toast to update.
61+
* @param toUpdate The toast fields to update.
62+
*/
63+
modify(toastId: number, toUpdate: Partial<AddToast>): void;
64+
/**
65+
* Returns true if toast exists in the queue.
66+
* @param toastId The ID of the toast to check.
67+
*/
68+
exists(toastId: number): boolean;
5769
}
5870

5971
export const ToastContext = createContext<ToastContextValue>({} as ToastContextValue);

0 commit comments

Comments
 (0)