@patreon/studio
Version:
Patreon Studio Design System
107 lines • 4.06 kB
JSX
'use client';
import React, { createContext, useCallback, useContext, useMemo, useReducer } from 'react';
import { Toast } from './Toast';
import { DEFAULT_DURATION, DEFAULT_DURATION_WITH_ACTION, DEFAULT_TOAST_KEY_PREFIX } from './constants';
import { toasterReducer } from './reducers';
import { ToasterContainer } from './styled-components';
import { CustomToast, ErrorToast, LoadingToast, SuccessToast } from './variants';
// This default value isn't used in practice, since we set it in the context
// provider. We still have to provide a default value that matches the correct
// signature though, so we provide a stub that does nothing
export const ToasterContext = createContext({
closeAll: () => {
// Do nothing
},
error: () => ({
close: () => {
// Do nothing
},
}),
loading: () => ({
close: () => {
// Do nothing
},
}),
success: () => ({
close: () => {
// Do nothing
},
}),
custom: () => ({
close: () => {
// Do nothing
},
}),
});
let currentId = 0;
export const Toaster = ({ children }) => {
const [toasts, dispatch] = useReducer(toasterReducer, []);
const createToast = useCallback((contentBuilder, options = {}) => {
if (!options.duration) {
options.duration = options.action ? DEFAULT_DURATION_WITH_ACTION : DEFAULT_DURATION;
}
const id = currentId++;
if (options.key) {
dispatch({
type: 'update',
payload: {
isClosing: true,
key: options.key,
},
});
}
const key = options.key ?? `${DEFAULT_TOAST_KEY_PREFIX}${id}`;
dispatch({
type: 'add',
payload: {
action: options.action,
contentBuilder,
'data-tag': options['data-tag'],
duration: options.duration,
id,
key,
},
});
return {
close: () => dispatch({
type: 'update',
payload: {
isClosing: true,
id,
},
}),
};
}, []);
const toaster = useMemo(() => ({
closeAll: () => dispatch({ type: 'closeAll' }),
error: (content, options = {}) => createToast((setState) => (<ErrorToast action={options.action} setState={setState}>
{content}
</ErrorToast>), options),
loading: (content, options = {}) => createToast((setState) => (<LoadingToast action={options.action} setState={setState}>
{content}
</LoadingToast>), { duration: 'infinite', ...options }),
success: (content, options = {}) => createToast((setState) => (<SuccessToast action={options.action} setState={setState}>
{content}
</SuccessToast>), options),
custom: (content, options = {}) => createToast((setState) => (<CustomToast showCloseButton={options.showCloseButton ?? true} setState={setState}>
{content}
</CustomToast>), options),
}), [createToast]);
return (<ToasterContext.Provider value={toaster}>
{children}
<ToasterContainer aria-live="polite" data-tag="toaster" data-overlay-dismiss="false">
{toasts.map((toast) => (<Toast action={toast.action} data-tag={toast['data-tag']} duration={toast.duration} isClosing={toast.isClosing} key={`${toast.key}-${toast.id}`} onClose={() => dispatch({
type: 'remove',
payload: {
id: toast.id,
},
})} contentBuilder={toast.contentBuilder}/>))}
</ToasterContainer>
</ToasterContext.Provider>);
};
export const useToaster = () => useContext(ToasterContext);
/**
* @deprecated Use `useToaster` in a function component instead
*/
export const ToasterConsumer = ToasterContext.Consumer;
//# sourceMappingURL=Toaster.jsx.map