Files
jobtrackingapp/job-tracker-ui/src/prompt.tsx
T

81 lines
2.6 KiB
TypeScript

import React, { createContext, useCallback, useContext, useMemo, useState } from "react";
import { Button, Dialog, DialogActions, DialogContent, DialogTitle, TextField, Typography } from "@mui/material";
type PromptOptions = {
title?: string;
message: string;
defaultValue?: string;
confirmLabel?: string;
cancelLabel?: string;
};
type PromptContextValue = {
prompt: (options: PromptOptions) => Promise<string | null>;
};
type PromptState = PromptOptions & {
open: boolean;
resolver?: (value: string | null) => void;
};
const PromptContext = createContext<PromptContextValue | null>(null);
export function PromptProvider({ children }: { children: React.ReactNode }) {
const [state, setState] = useState<PromptState>({
open: false,
title: "Enter value",
message: "",
defaultValue: "",
confirmLabel: "Save",
cancelLabel: "Cancel",
});
const [value, setValue] = useState("");
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<string | null>((resolve) => {
setValue(options.defaultValue ?? "");
setState({
open: true,
title: options.title ?? "Enter value",
message: options.message,
defaultValue: options.defaultValue ?? "",
confirmLabel: options.confirmLabel ?? "Save",
cancelLabel: options.cancelLabel ?? "Cancel",
resolver: resolve,
});
});
}, []);
const contextValue = useMemo(() => ({ prompt }), [prompt]);
return (
<PromptContext.Provider value={contextValue}>
{children}
<Dialog open={state.open} onClose={() => closeWith(null)} fullWidth maxWidth="xs">
<DialogTitle>{state.title}</DialogTitle>
<DialogContent>
<Typography sx={{ color: "text.secondary", mb: 1.5 }}>{state.message}</Typography>
<TextField autoFocus fullWidth value={value} onChange={(e) => setValue(e.target.value)} inputProps={{ maxLength: 180 }} helperText={`${value.length}/180`} />
</DialogContent>
<DialogActions>
<Button onClick={() => closeWith(null)}>{state.cancelLabel}</Button>
<Button variant="contained" onClick={() => closeWith(value)}>{state.confirmLabel}</Button>
</DialogActions>
</Dialog>
</PromptContext.Provider>
);
}
export function usePrompt() {
const ctx = useContext(PromptContext);
if (!ctx) throw new Error("usePrompt must be used within PromptProvider");
return ctx;
}