Skip to content

Commit

Permalink
Feat(web-react): Add autoclose feature to Toast component
Browse files Browse the repository at this point in the history
  • Loading branch information
pavelklibani committed May 21, 2024
1 parent c9797b5 commit a87c94f
Show file tree
Hide file tree
Showing 13 changed files with 381 additions and 162 deletions.
330 changes: 204 additions & 126 deletions apps/web-twig-demo/yarn.lock

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion packages/web-react/src/components/Toast/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -353,9 +353,11 @@ const { show } = useToast();
│ ┌─⫸ ToastBar ID (required)
│ │
show('Toast message', 'toast-id', {
autoCloseInterval: 3000 // Set interval in ms after ToastBar will be closed, default: 3000
color: 'danger', // Color variant, default: 'inverted'
iconName: 'download', // Name of a custom icon to be shown along the message, default: undefined
enableAutoClose: true // If true, ToastBar will close after `autoCloseInterval`, default: true
hasIcon: true // If true, an icon is shown along the message, default: false \*
iconName: 'download', // Name of a custom icon to be shown along the message, default: undefined
isDismissible: true // If true, ToastBar can be dismissed by user, default: false
});
```
Expand Down
70 changes: 53 additions & 17 deletions packages/web-react/src/components/Toast/ToastContext.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import React, { FC, ReactNode, createContext, useCallback, useMemo, useReducer } from 'react';
import { ToastColorType } from '../../types';
import { delayedCallback } from '../../utils';
import { TOAST_AUTO_CLOSE_INTERVAL } from './constants';

type ToastState = {
queue: ToastItem[];
};

export interface ToastItem {
color: ToastColorType | undefined;
iconName: string | undefined;
autoCloseInterval?: number;
color?: ToastColorType;
enableAutoClose?: boolean;
hasIcon: boolean;
isDismissible: boolean;
iconName?: string;
id: string;
isDismissible: boolean;
isOpen: boolean;
message: string | JSX.Element;
}
Expand All @@ -22,7 +26,14 @@ export interface ToastContextType extends ToastState {
show: (
text: string | JSX.Element,
id: string,
options?: { color?: ToastColorType; iconName?: string; hasIcon?: boolean; isDismissible?: boolean },
options?: {
autoCloseInterval?: number;
color?: ToastColorType;
enableAutoClose?: boolean;
hasIcon?: boolean;
iconName?: string;
isDismissible?: boolean;
},
) => void;
}

Expand All @@ -42,7 +53,14 @@ type ActionType =
payload: {
text: string | JSX.Element;
toastId: string;
options?: { color?: ToastColorType; iconName?: string; hasIcon?: boolean; isDismissible?: boolean };
options?: {
autoCloseInterval?: number;
color?: ToastColorType;
enableAutoClose?: boolean;
hasIcon?: boolean;
iconName?: string;
isDismissible?: boolean;
};
};
}
| { type: 'hide'; payload: { id: string } }
Expand All @@ -58,7 +76,9 @@ const reducer = (state: ToastState, action: ActionType): ToastState => {
const newQueue = [
...currentQueue,
{
autoCloseInterval: payload.options?.autoCloseInterval || TOAST_AUTO_CLOSE_INTERVAL,
color: payload.options?.color || undefined,
enableAutoClose: payload.options?.enableAutoClose || true,
hasIcon: payload.options?.hasIcon || false,
iconName: payload.options?.iconName,
id: payload.toastId,
Expand Down Expand Up @@ -95,42 +115,58 @@ export const ToastProvider: FC<ToastProviderProps> = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialToastState);
const { queue } = state;

const hide = useCallback((id: string) => {
dispatch({ type: 'hide', payload: { id } });
}, []);

const clear = useCallback(() => {
dispatch({ type: 'clear', payload: null });
}, []);

const show = useCallback(
(
text: string | JSX.Element,
toastId: string,
options?: { color?: ToastColorType; iconName?: string; hasIcon?: boolean; isDismissible?: boolean },
options?: {
autoCloseInterval?: number;
color?: ToastColorType;
enableAutoClose?: boolean;
hasIcon?: boolean;
iconName?: string;
isDismissible?: boolean;
},
) => {
dispatch({ type: 'show', payload: { text, toastId, options } });

options?.enableAutoClose &&
delayedCallback(() => hide(toastId), options?.autoCloseInterval || TOAST_AUTO_CLOSE_INTERVAL);
},
[],
);

const hide = useCallback((id: string) => {
dispatch({ type: 'hide', payload: { id } });
}, []);

const clear = useCallback(() => {
dispatch({ type: 'clear', payload: null });
}, []);

const setQueue = useCallback((newQueue: ToastItem[]) => {
dispatch({ type: 'clear', payload: null });

newQueue.forEach((item) => {
const enableAutoClose = item.enableAutoClose ?? true;
const autoCloseInterval = item.autoCloseInterval || TOAST_AUTO_CLOSE_INTERVAL;

dispatch({
type: 'show',
payload: {
text: item.message,
toastId: item.id,
options: {
autoCloseInterval,
enableAutoClose,
color: item.color,
hasIcon: item.hasIcon || false,
iconName: item.iconName,
hasIcon: item.hasIcon,
isDismissible: item.isDismissible,
isDismissible: item.isDismissible || false,
},
},
});

enableAutoClose && delayedCallback(() => hide(item.id), autoCloseInterval || TOAST_AUTO_CLOSE_INTERVAL);
});
}, []);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React from 'react';
import { UncontrolledToastProps } from '../../types';
import Toast from './Toast';
import ToastBar from './ToastBar';
import { useToast } from './useToast';
import { UncontrolledToastProps } from '../../types';

const UncontrolledToast = (props: UncontrolledToastProps) => {
const { alignmentX, alignmentY, isCollapsible, closeLabel, ...restProps } = props;
Expand Down
2 changes: 2 additions & 0 deletions packages/web-react/src/components/Toast/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ export const ICON_BOX_SIZE = 20;
export const TOAST_BAR_CLOSE_BUTTON_LABEL_DEFAULT = 'Close';

export const DEFAULT_TOAST_COLOR = 'inverted';

export const TOAST_AUTO_CLOSE_INTERVAL = 3000; // milliseconds
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import Toast from '../Toast';
import ToastBar from '../ToastBar';
import { ToastItem, ToastProvider } from '../ToastContext';
import { useToast } from '../useToast';
import { TOAST_AUTO_CLOSE_INTERVAL } from '../constants';

const ToastDynamicToastQueue = () => {
const [isCollapsible, setIsCollapsible] = useState(true);
Expand All @@ -20,6 +21,8 @@ const ToastDynamicToastQueue = () => {
const [colorValue, setColorValue] = useState<ToastColorType>('inverted');
const [hasIconValue, setHasIconValue] = useState(true);
const [isDismissibleValue, setIsDismissibleValue] = useState(true);
const [enableAutoCloseValue, setEnableAutoCloseValue] = useState(true);
const [autoCloseInterval, setAutoCloseInterval] = useState(TOAST_AUTO_CLOSE_INTERVAL);
const [messageValue, setMessageValue] = useState('This is a new toast message.');

const { queue, show, hide, clear, setQueue } = useToast();
Expand All @@ -40,6 +43,7 @@ const ToastDynamicToastQueue = () => {
hasIcon: true,
isDismissible: true,
iconName: undefined,
enableAutoClose: false,
},
{
id: '2',
Expand All @@ -56,6 +60,7 @@ const ToastDynamicToastQueue = () => {
hasIcon: true,
isDismissible: true,
iconName: undefined,
enableAutoClose: false,
},
];

Expand Down Expand Up @@ -174,6 +179,25 @@ const ToastDynamicToastQueue = () => {
isChecked={isDismissibleValue}
onChange={() => setIsDismissibleValue(!isDismissibleValue)}
/>
<Checkbox
name="enable-autoClose"
id="toast-enable-autoClose"
label="Enable AutoClose"
isChecked={enableAutoCloseValue}
onChange={() => setEnableAutoCloseValue(!enableAutoCloseValue)}
/>
<TextField
type="number"
min="0"
max="60000"
step="500"
value={autoCloseInterval}
onChange={(e) => setAutoCloseInterval(Number(e.currentTarget.value))}
isDisabled={!enableAutoCloseValue}
label="AutoClose interval (ms)"
name="autoCloseInterval"
id="toast-auto-close-interval"
/>
<TextArea
label="Message"
name="content"
Expand All @@ -186,9 +210,11 @@ const ToastDynamicToastQueue = () => {
<Button
onClick={() => {
show(messageValue, `my-dynamic-toast-${Date.now().toString()}`, {
autoCloseInterval,
color: colorValue,
hasIcon: hasIconValue,
isDismissible: isDismissibleValue,
enableAutoClose: enableAutoCloseValue,
});
}}
>
Expand Down Expand Up @@ -227,7 +253,7 @@ const ToastDynamicToastQueue = () => {
iconName={iconName}
isDismissible={isDismissible}
onClose={() => hide(id)}
isOpen={!!isOpen && !!message}
isOpen={isOpen && !!message}
>
{message}
</ToastBar>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ const ShowToastButton = () => {
iconName: 'download',
isDismissible: true,
hasIcon: true,
enableAutoClose: true,
autoCloseInterval: 1500,
});
}}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ const meta: Meta<typeof ToastBar> = {
},
closeLabel: {
control: 'text',
table: {
defaultValue: { summary: 'Close' },
},
},
color: {
control: 'select',
Expand Down
Loading

0 comments on commit a87c94f

Please sign in to comment.