Skip to content

Commit

Permalink
feat(ToastProvider): added modify toast and exists methods
Browse files Browse the repository at this point in the history
  • Loading branch information
liamross committed May 5, 2020
1 parent 95364f5 commit f04731f
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 27 deletions.
13 changes: 13 additions & 0 deletions .storybook/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,19 @@ body {
user-select: none;
}

.toastprovider__buttons {
display: flex;

> div {
display: flex;
flex-direction: column;
margin: 0 16px;
}
button {
margin-bottom: 16px;
}
}

.colors__theme {
z-index: $z-index-dragging;
position: sticky;
Expand Down
32 changes: 24 additions & 8 deletions src/components/ToastProvider/ToastProvider.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,39 @@ const DemoButtons = () => {
const hasTimeout = boolean('has timeout', false, 'ToastContext.add');
const timeout = hasTimeout ? integer('timeout', 0, 'ToastContext.add') : undefined;

const pushToast = (message: ToastProps['message']) => {
toast.add({
const pushToast = (message: ToastProps['message'], loading?: boolean) => {
const id = toast.add({
heading: `A new ${message} toast`,
children,
message,
message: loading ? 'loading' : message,
action: hasAction ? { text: 'Fire Alert', onClick: () => alert('Fired action.') } : undefined,
closable,
timeout,
});

setTimeout(() => {
toast.modify(id, { message });
}, 2000);
};

return (
<div style={{ display: 'grid', justifyContent: 'center', gap: 8 }}>
<Button onClick={() => pushToast('info')}>Add info Toast</Button>
<Button onClick={() => pushToast('success')}>Add success Toast</Button>
<Button onClick={() => pushToast('warning')}>Add warning Toast</Button>
<Button onClick={() => pushToast('error')}>Add error Toast</Button>
<div className="toastprovider__buttons">
<div>
<Button onClick={() => pushToast('info')}>Add info Toast</Button>
<Button onClick={() => pushToast('success')}>Add success Toast</Button>
<Button onClick={() => pushToast('warning')}>Add warning Toast</Button>
<Button onClick={() => pushToast('error')}>Add error Toast</Button>
<Button onClick={() => pushToast('loading')}>Add loading Toast</Button>
</div>
<div>
<Button onClick={() => pushToast('info', true)}>Add delayed info Toast</Button>
<Button onClick={() => pushToast('success', true)}>Add delayed success Toast</Button>
<Button onClick={() => pushToast('warning', true)}>Add delayed warning Toast</Button>
<Button onClick={() => pushToast('error', true)}>Add delayed error Toast</Button>
</div>
</div>

<Button buttonStyle="outline" onClick={() => toast.remove()}>
Pop current Toast
</Button>
Expand All @@ -47,7 +63,7 @@ export const Basic = () => (
noTimeout={
(select(
'noTimeout',
['', 'info', 'success', 'warning', 'error'],
['', 'info', 'success', 'warning', 'error', 'loading'],
'',
'ToastProvider',
) as CommonToastProps['message']) || undefined
Expand Down
44 changes: 26 additions & 18 deletions src/components/ToastProvider/ToastProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,8 @@ export const ToastProvider: FC<ToastProviderProps> = ({
const onHover = useCallback(() => setHovered(true), []);
const onBlur = useCallback(() => setHovered(false), []);

const _pop = useCallback(() => {
setClosing(true);
}, []);
const _pop = useCallback(() => setClosing(true), []);

useEffect(() => {
if (closing) {
const timeoutId = window.setTimeout(() => {
Expand Down Expand Up @@ -89,16 +88,14 @@ export const ToastProvider: FC<ToastProviderProps> = ({
}, [defaultTimeout, noTimeoutTypes, timeout, toastProps.message]);

useEffect(() => {
if (!hovered && toastId && timeoutValue) {
const timeoutId = window.setTimeout(() => {
_pop();
}, timeoutValue);
return () => {
window.clearTimeout(timeoutId);
};
// toastId and toastProps.message are included to reset timer when message
// changes.
if (!hovered && toastId && timeoutValue && toastProps.message) {
const timeoutId = window.setTimeout(_pop, timeoutValue);
return () => window.clearTimeout(timeoutId);
}
return;
}, [_pop, hovered, timeoutValue, toastId]);
}, [_pop, hovered, timeoutValue, toastId, toastProps.message]);

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

const value = useMemo<ToastContextValue>(
() => ({
add,
remove,
toasts,
}),
[add, remove, toasts],
const modify = useCallback<ToastContextValue['modify']>((id, update) => {
setToasts(prev => {
const index = prev.findIndex(t => t.toastId === id);
if (index === -1) return prev;
return [...prev.slice(0, index), { ...prev[index], ...update }, ...prev.slice(index + 1)];
});
}, []);

const exists = useCallback<ToastContextValue['exists']>(
id => {
const index = toasts.findIndex(t => t.toastId === id);
if (index === -1) return false;
if (closing && index === 0) return false;
return true;
},
[closing, toasts],
);

const value = useMemo<ToastContextValue>(() => ({ add, remove, modify, exists }), [add, remove, modify, exists]);

const padding = useMemo(() => {
if (customPadding === undefined) return undefined;
const isTop = ['top-left', 'top', 'top-right'].includes(position);
Expand Down
14 changes: 13 additions & 1 deletion src/hooks/useToast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export interface CommonToastProps {
* icon of the toast.
* @default "info"
*/
message?: 'info' | 'success' | 'warning' | 'error';
message?: 'info' | 'success' | 'warning' | 'error' | 'loading';
/**
* Adds an action button to the toast. Will position to the left of the close
* button if `onClose` was provided.
Expand Down Expand Up @@ -54,6 +54,18 @@ export interface ToastContextValue {
* @param toastId The ID of the toast to remove.
*/
remove(toastId?: number): void;
/**
* Modify a toast by providing the ID, then a partial toast object with fields
* you wish to update.
* @param toastId The ID of the toast to update.
* @param toUpdate The toast fields to update.
*/
modify(toastId: number, toUpdate: Partial<AddToast>): void;
/**
* Returns true if toast exists in the queue.
* @param toastId The ID of the toast to check.
*/
exists(toastId: number): boolean;
}

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

0 comments on commit f04731f

Please sign in to comment.