import React, { createContext, useCallback, useContext, useEffect, useId, useMemo, useRef, useState } from "react"; import { Button, Dialog, DialogActions, DialogContent, DialogTitle, TextField, Typography } from "@mui/material"; import { useOptionalI18n } from "./i18n/I18nProvider"; type PromptOptions = { title?: string; message: string; defaultValue?: string; confirmLabel?: string; cancelLabel?: string; }; type PromptContextValue = { prompt: (options: PromptOptions) => Promise; }; type PromptState = PromptOptions & { open: boolean; resolver?: (value: string | null) => void; }; const PromptContext = createContext(null); export function PromptProvider({ children }: { children: React.ReactNode }) { const i18n = useOptionalI18n(); const messageId = useId(); const inputRef = useRef(null); const [state, setState] = useState({ open: false, title: "Enter value", message: "", defaultValue: "", confirmLabel: "Save", cancelLabel: "Cancel", }); const [value, setValue] = useState(""); useEffect(() => { if (!state.open) return; const frame = requestAnimationFrame(() => inputRef.current?.focus()); return () => cancelAnimationFrame(frame); }, [state.open]); const closeWith = useCallback((next: string | null) => { setState((prev) => { prev.resolver?.(next); return { ...prev, open: false, resolver: undefined }; }); }, []); const prompt = useCallback((options: PromptOptions) => { return new Promise((resolve) => { setValue(options.defaultValue ?? ""); setState({ open: true, title: options.title ?? i18n?.t("enterValue") ?? "Enter value", message: options.message, defaultValue: options.defaultValue ?? "", confirmLabel: options.confirmLabel ?? i18n?.t("save") ?? "Save", cancelLabel: options.cancelLabel ?? i18n?.t("cancel") ?? "Cancel", resolver: resolve, }); }); }, [i18n]); const contextValue = useMemo(() => ({ prompt }), [prompt]); return ( {children} closeWith(null)} aria-describedby={messageId} fullWidth maxWidth="xs" slotProps={{ paper: { sx: { borderRadius: 3, border: 1, borderColor: "divider", backgroundImage: "none" } } }}> {state.title} {state.message} setValue(e.target.value)} inputProps={{ maxLength: 180, "aria-labelledby": messageId }} helperText={`${value.length}/180`} /> ); } export function usePrompt() { const ctx = useContext(PromptContext); if (!ctx) throw new Error("usePrompt must be used within PromptProvider"); return ctx; }