UNPKG

@patreon/studio

Version:

Patreon Studio Design System

107 lines 4.06 kB
'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