import * as React from 'react'; import Button from '@mui/material/Button'; import Dialog from '@mui/material/Dialog'; import DialogActions from '@mui/material/DialogActions'; import DialogContent from '@mui/material/DialogContent'; import DialogContentText from '@mui/material/DialogContentText'; import DialogTitle from '@mui/material/DialogTitle'; import TextField from '@mui/material/TextField'; import useEventCallback from '@mui/utils/useEventCallback'; import DialogsContext from './DialogsContext'; export interface OpenDialogOptions { /** * A function that is called before closing the dialog closes. The dialog * stays open as long as the returned promise is not resolved. Use this if * you want to perform an async action on close and show a loading state. * * @param result The result that the dialog will return after closing. * @returns A promise that resolves when the dialog can be closed. */ onClose?: (result: R) => Promise; } export interface AlertOptions extends OpenDialogOptions { /** * A title for the dialog. Defaults to `'Alert'`. */ title?: React.ReactNode; /** * The text to show in the "Ok" button. Defaults to `'Ok'`. */ okText?: React.ReactNode; } export interface ConfirmOptions extends OpenDialogOptions { /** * A title for the dialog. Defaults to `'Confirm'`. */ title?: React.ReactNode; /** * The text to show in the "Ok" button. Defaults to `'Ok'`. */ okText?: React.ReactNode; /** * Denotes the purpose of the dialog. This will affect the color of the * "Ok" button. Defaults to `undefined`. */ severity?: 'error' | 'info' | 'success' | 'warning'; /** * The text to show in the "Cancel" button. Defaults to `'Cancel'`. */ cancelText?: React.ReactNode; } export interface PromptOptions extends OpenDialogOptions { /** * A title for the dialog. Defaults to `'Prompt'`. */ title?: React.ReactNode; /** * The text to show in the "Ok" button. Defaults to `'Ok'`. */ okText?: React.ReactNode; /** * The text to show in the "Cancel" button. Defaults to `'Cancel'`. */ cancelText?: React.ReactNode; } /** * The props that are passed to a dialog component. */ export interface DialogProps

{ /** * The payload that was passed when the dialog was opened. */ payload: P; /** * Whether the dialog is open. */ open: boolean; /** * A function to call when the dialog should be closed. If the dialog has a return * value, it should be passed as an argument to this function. You should use the promise * that is returned to show a loading state while the dialog is performing async actions * on close. * @param result The result to return from the dialog. * @returns A promise that resolves when the dialog can be fully closed. */ onClose: (result: R) => Promise; } export interface OpenAlertDialog { /** * Open an alert dialog. Returns a promise that resolves when the user * closes the dialog. * * @param msg The message to show in the dialog. * @param options Additional options for the dialog. * @returns A promise that resolves when the dialog is closed. */ (msg: React.ReactNode, options?: AlertOptions): Promise; } export interface OpenConfirmDialog { /** * Open a confirmation dialog. Returns a promise that resolves to true if * the user confirms, false if the user cancels. * * @param msg The message to show in the dialog. * @param options Additional options for the dialog. * @returns A promise that resolves to true if the user confirms, false if the user cancels. */ (msg: React.ReactNode, options?: ConfirmOptions): Promise; } export interface OpenPromptDialog { /** * Open a prompt dialog to request user input. Returns a promise that resolves to the input * if the user confirms, null if the user cancels. * * @param msg The message to show in the dialog. * @param options Additional options for the dialog. * @returns A promise that resolves to the user input if the user confirms, null if the user cancels. */ (msg: React.ReactNode, options?: PromptOptions): Promise; } export type DialogComponent = React.ComponentType>; export interface OpenDialog { /** * Open a dialog without payload. * @param Component The dialog component to open. * @param options Additional options for the dialog. */

( Component: DialogComponent, payload?: P, options?: OpenDialogOptions, ): Promise; /** * Open a dialog and pass a payload. * @param Component The dialog component to open. * @param payload The payload to pass to the dialog. * @param options Additional options for the dialog. */ ( Component: DialogComponent, payload: P, options?: OpenDialogOptions, ): Promise; } export interface CloseDialog { /** * Close a dialog and return a result. * @param dialog The dialog to close. The promise returned by `open`. * @param result The result to return from the dialog. * @returns A promise that resolves when the dialog is fully closed. */ (dialog: Promise, result: R): Promise; } export interface DialogHook { alert: OpenAlertDialog; confirm: OpenConfirmDialog; prompt: OpenPromptDialog; open: OpenDialog; close: CloseDialog; } function useDialogLoadingButton(onClose: () => Promise) { const [loading, setLoading] = React.useState(false); const handleClick = async () => { try { setLoading(true); await onClose(); } finally { setLoading(false); } }; return { onClick: handleClick, loading, }; } export interface AlertDialogPayload extends AlertOptions { msg: React.ReactNode; } export interface AlertDialogProps extends DialogProps {} export function AlertDialog({ open, payload, onClose }: AlertDialogProps) { const okButtonProps = useDialogLoadingButton(() => onClose()); return (

onClose()}> {payload.title ?? 'Alert'} {payload.msg} ); } export interface ConfirmDialogPayload extends ConfirmOptions { msg: React.ReactNode; } export interface ConfirmDialogProps extends DialogProps {} export function ConfirmDialog({ open, payload, onClose }: ConfirmDialogProps) { const cancelButtonProps = useDialogLoadingButton(() => onClose(false)); const okButtonProps = useDialogLoadingButton(() => onClose(true)); return ( onClose(false)}> {payload.title ?? 'Confirm'} {payload.msg} ); } export interface PromptDialogPayload extends PromptOptions { msg: React.ReactNode; } export interface PromptDialogProps extends DialogProps {} export function PromptDialog({ open, payload, onClose }: PromptDialogProps) { const [input, setInput] = React.useState(''); const cancelButtonProps = useDialogLoadingButton(() => onClose(null)); const [loading, setLoading] = React.useState(false); const name = 'input'; return ( onClose(null)} slotProps={{ paper: { component: 'form', onSubmit: async (event: React.FormEvent) => { event.preventDefault(); try { setLoading(true); const formData = new FormData(event.currentTarget); const value = formData.get(name) ?? ''; if (typeof value !== 'string') { throw new Error('Value must come from a text input.'); } await onClose(value); } finally { setLoading(false); } }, }, }} > {payload.title ?? 'Confirm'} {payload.msg} setInput(event.target.value)} /> ); } export function useDialogs(): DialogHook { const dialogsContext = React.useContext(DialogsContext); if (!dialogsContext) { throw new Error('Dialogs context was used without a provider.'); } const { open, close } = dialogsContext; const alert = useEventCallback( (msg, { onClose, ...options } = {}) => open(AlertDialog, { ...options, msg }, { onClose }), ); const confirm = useEventCallback( (msg, { onClose, ...options } = {}) => open(ConfirmDialog, { ...options, msg }, { onClose }), ); const prompt = useEventCallback( (msg, { onClose, ...options } = {}) => open(PromptDialog, { ...options, msg }, { onClose }), ); return React.useMemo( () => ({ alert, confirm, prompt, open, close, }), [alert, close, confirm, open, prompt], ); }