// TODO: /* eslint-disable @typescript-eslint/prefer-optional-chain */ /* eslint-disable @typescript-eslint/no-unnecessary-condition */ /* eslint-disable @typescript-eslint/prefer-nullish-coalescing */ import { FocusVisibleButton } from "@/base/components/mui/FocusVisibleButton"; import { LoadingButton } from "@/base/components/mui/LoadingButton"; import type { ButtonProps } from "@mui/material"; import { Box, Dialog, DialogContent, DialogTitle, Stack, Typography, type DialogProps, } from "@mui/material"; import { t } from "i18next"; import React, { useState } from "react"; /** * Customize the contents of an {@link AttributedMiniDialog}. */ export interface MiniDialogAttributes { /** * The dialog's title. * * This will be usually be a string, but the prop accepts any React node to * allow passing a i18next component. */ title?: React.ReactNode; /** * An optional component shown next to the title. */ icon?: React.ReactNode; /** * The dialog's message. * * This will be usually be a string, but the prop accepts any React node to * allow passing a i18next component. */ message?: React.ReactNode; /** * If `true`, then clicks in the backdrop are ignored. The default behaviour * is to close the dialog when the background is clicked. */ staticBackdrop?: boolean; /** * If `true`, then the dialog cannot be closed (e.g. with the ESC key, or * clicking on the backdrop) except through one of the explicitly provided * actions. */ nonClosable?: boolean; /** * Customize the primary action button shown in the dialog. * * This is provided by boxes which serve as some sort of confirmation. For * dialogs which are informational notifications, this is usually skipped, * only the {@link close} action button is configured. */ continue?: { /** * The string to use as the label for the primary action button. * * Default is `t("ok")`. */ text?: string; /** * The color of the button. * * Default is "accent". */ color?: ButtonProps["color"]; /** * If `true`, the primary action button is auto focused when the dialog * is opened, allowing the user to confirm just by pressing ENTER. */ autoFocus?: boolean; /** * The function to call when the user activates the button. * * Default is to close the dialog. * * It is passed a {@link setLoading} function that can be used to show * or hide loading indicator or the primary action button. */ action?: | (() => void | Promise) | ((setLoading: (value: boolean) => void) => void | Promise); }; /** * The string to use as the label for the cancel button. * * Default is `t("cancel")`. * * Set this to `false` to omit the cancel button altogether. */ cancel?: string | false; /** The direction in which the buttons are stacked. Default is "column". */ buttonDirection?: "row" | "column"; } type MiniDialogProps = Omit & { onClose: () => void; attributes?: MiniDialogAttributes; }; /** * A small, mostly predefined, MUI {@link Dialog} that can be used to notify the * user, or ask for confirmation before actions. * * The rendered dialog can be customized by modifying the {@link attributes} * prop. If you find yourself wanting to customize it further, consider either * using a {@link TitledMiniDialog} or {@link Dialog}. */ export const AttributedMiniDialog: React.FC< React.PropsWithChildren > = ({ open, onClose, attributes, children, ...props }) => { const [loading, setLoading] = useState(false); if (!attributes) { return <>; } const handleClose = () => { if (attributes.nonClosable) return; onClose(); }; const { PaperProps, ...rest } = props; return ( {(attributes.icon || attributes.title) && ( svg": { fontSize: "32px", color: "text.faint", }, padding: "24px 16px 16px 16px", }} > {attributes.title && ( {attributes.title} )} {attributes.icon} )} {attributes.message && ( {attributes.message} )} {children} {attributes.continue && ( { await attributes.continue?.action?.(setLoading); onClose(); }} > {attributes.continue?.text ?? t("ok")} )} {attributes.cancel && ( {attributes.cancel ?? t("cancel")} )} ); }; // TODO: Sketch of a possible approach to using this. Haven't throught this // through, just noting down the outline inspired by an API I saw. // /** // * A React hook for simplifying use of MiniDialog within the photos app context. // * // * It relies on the presence of the {@link setDialogBoxAttributesV2} function // * provided by the Photos app's {@link AppContext}. // */ // export const useConfirm = (attr) => { // const {setDialogBoxAttributesV2} = useAppContext(); // return () => { // new Promise((resolve) => { // setDialogBoxAttributesV2( // proceed: { // action: attr.action.then(resolve) // } // ) // } // } type TitledMiniDialogProps = Omit & { onClose: () => void; /** * The dialog's title. */ title?: React.ReactNode; }; /** * MiniDialog in a "shell" form. * * This is a {@link Dialog} for use at places which need more customization than * what {@link AttributedMiniDialog} provides, but wish to retain a similar look * and feel without duplicating code. * * It does three things: * * - Sets a fixed size and padding similar to {@link AttributedMiniDialog}. * - Takes the title as a prop, and wraps it in a {@link DialogTitle}. * - Wraps children in a scrollable {@link DialogContent}. */ export const TitledMiniDialog: React.FC< React.PropsWithChildren > = ({ open, onClose, title, children, ...props }) => { const { PaperProps, ...rest } = props; return ( {title} {children} ); };