Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat(web, web-twig, web-react) Toast AutoClose #DS-1251 #1415

Merged
merged 3 commits into from
May 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion apps/web-twig-demo/assets/scripts/toast-dynamic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ import { Toast } from '@lmc-eu/spirit-web/src/js/index.esm';
const addDynamicToast = (event, containerId) => {
const formElement = event.target.closest('form');
const config = {
autoCloseInterval: formElement.querySelector('#toast-auto-close-interval').value,
color: formElement.querySelector('#toast-color').value,
containerId,
content: formElement.querySelector('#toast-content').value,
enableAutoClose: formElement.querySelector('#toast-enable-auto-close').checked,
hasIcon: formElement.querySelector('#toast-has-icon').checked,
id: `my-dynamic-toast-${Date.now()}`,
isDismissible: formElement.querySelector('#toast-is-dismissible').checked,
Expand All @@ -27,7 +29,7 @@ export const clearQueue = (event, containerId) => {

if (instance instanceof Toast && instance?.isShown) {
instance?.hide();
cleared++;
cleared += 1;
}
});

Expand All @@ -38,6 +40,13 @@ export const clearQueue = (event, containerId) => {
}
};

declare global {
interface Window {
addDynamicToast: (event: Event, containerId: string) => void;
clearQueue: (event: Event, containerId: string) => void;
}
}

// Make functions available in the global scope
window.addDynamicToast = addDynamicToast;
window.clearQueue = clearQueue;
Expand Down
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 { DEFAULT_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 || DEFAULT_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 || DEFAULT_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 || DEFAULT_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 || DEFAULT_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 DEFAULT_TOAST_AUTO_CLOSE_INTERVAL = 3000; // milliseconds
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { TextField } from '../../TextField';
import Toast from '../Toast';
import ToastBar from '../ToastBar';
import { ToastItem, ToastProvider } from '../ToastContext';
import { DEFAULT_TOAST_AUTO_CLOSE_INTERVAL } from '../constants';
import { useToast } from '../useToast';

const ToastDynamicToastQueue = () => {
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(DEFAULT_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
Loading