Webatrice: Registration toasts (#4566)

* wip

* Registration Success Toast

* remove debugging code

* remove unused field

* Show toast on successful password reset

* Toast on account activation success

* lint and PR feedback

* Rework interface names to avoid collision

* Move CssBaseline to sibling of ToastProvider

Co-authored-by: Brent Clark <brent@backboneiq.com>
This commit is contained in:
Brent Clark 2022-02-15 19:40:30 -06:00 committed by GitHub
parent 88b861d632
commit 4c04b4ef5a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 164 additions and 13 deletions

View file

@ -0,0 +1,71 @@
import { createContext, FC, ReactChild, ReactNode, useContext, useEffect, useReducer, ContextType, Context } from 'react'
import { ACTIONS, initialState, reducer } from './reducer';
import Toast from './Toast'
interface ToastEntry {
isOpen: boolean,
children: ReactChild,
}
interface ToastState {
toasts: Map<string, ToastEntry>,
addToast: (key, children) => void,
openToast: (key) => void,
closeToast: (key) => void,
removeToast: (key) => void,
}
const ToastContext: Context<any> = createContext<ToastState>({
toasts: new Map<string, ToastEntry>(),
addToast: (key, children) => {},
openToast: (key) => {},
closeToast: (key) => {},
removeToast: (key) => {},
});
export const ToastProvider: FC<ReactNode> = (props) => {
const { children } = props
const [state, dispatch] = useReducer(reducer, initialState)
const providerState = {
toasts: state.toasts,
addToast: (key, children) => dispatch({ type: ACTIONS.ADD_TOAST, payload: { key, children } }),
openToast: key => dispatch({ type: ACTIONS.OPEN_TOAST, payload: key }),
closeToast: key => dispatch({ type: ACTIONS.CLOSE_TOAST, payload: key }),
removeToast: key => dispatch({ type: ACTIONS.REMOVE_TOAST, payload: key }),
}
return (
<ToastContext.Provider value={providerState}>
{children}
<div>
{Array.from(state.toasts).map(([key, value]) => {
const { isOpen, children } = value;
return (
<Toast key={key} open={isOpen} onClose={() => dispatch({ type: ACTIONS.CLOSE_TOAST, payload: key })}>
{children}
</Toast>
)
})}
</div>
</ToastContext.Provider>
)
}
export interface ToastHookOptions {
key: string,
children: ReactNode
}
export function useToast<ToastHookOptions>({ key, children }) {
const { addToast, openToast, closeToast, removeToast } = useContext(ToastContext)
useEffect(() => {
addToast(key, children)
}, [])
return {
openToast: () => openToast(key),
closeToast: () => closeToast(key),
removeToast: () => removeToast(key),
}
}

View file

@ -0,0 +1,8 @@
import { useToast, ToastProvider } from './ToastContext';
import Toast from './Toast';
export {
Toast as default,
useToast,
ToastProvider,
}

View file

@ -0,0 +1,48 @@
export const ACTIONS = {
ADD_TOAST: 'ADD_TOAST',
OPEN_TOAST: 'OPEN_TOAST',
CLOSE_TOAST: 'CLOSE_TOAST',
REMOVE_TOAST: 'REMOVE_TOAST',
}
export const initialState = {
toasts: new Map()
}
export function reducer(state, action) {
const { type, payload } = action
switch (type) {
case ACTIONS.ADD_TOAST: {
const newState = { ...state }
newState.toasts = new Map(Array.from(state.toasts))
const { toasts } = newState;
const { key, children } = payload
toasts.set(key, { isOpen: false, children })
return newState
}
case ACTIONS.OPEN_TOAST: {
const newState = { ...state }
newState.toasts = new Map(Array.from(state.toasts))
const { toasts } = newState;
const toast = toasts.get(payload)
toasts.set(payload, { isOpen: true, children: toast.children })
return newState
}
case ACTIONS.CLOSE_TOAST: {
const newState = { ...state }
newState.toasts = new Map(Array.from(state.toasts))
const { toasts } = newState;
const toast = toasts.get(payload)
toasts.set(payload, { isOpen: false, children: toast.children })
return newState
}
case ACTIONS.REMOVE_TOAST: {
const newState = { ...state }
newState.toasts = new Map(Array.from(state.toasts))
newState.toasts.delete(payload)
return newState
}
default:
throw Error('Please pick an available action')
}
}