190 lines
6.6 KiB
TypeScript
190 lines
6.6 KiB
TypeScript
import { forwardRef, Ref, useEffect, useImperativeHandle, useRef, useState } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
import {
|
|
AlertDialog,
|
|
AlertDialogAction,
|
|
AlertDialogCancel,
|
|
AlertDialogContent,
|
|
AlertDialogDescription,
|
|
AlertDialogFooter,
|
|
AlertDialogHeader,
|
|
AlertDialogTitle,
|
|
} from "~/components/ui/alert-dialog";
|
|
import { cn } from "~/lib/utils";
|
|
import { MailIcon } from "~/components/ui/icons/vendor";
|
|
|
|
export interface RefConfirmModal {
|
|
setDestructive: (destructive: boolean) => void;
|
|
setValue: (value: any) => void;
|
|
show: (_question: string, _yesTitle?: string, _noTitle?: string, _description?: string, _inputString?: string) => void;
|
|
}
|
|
|
|
interface Props {
|
|
destructive?: boolean;
|
|
inputType?: string;
|
|
onYes?: (value: any) => void;
|
|
onNo?: () => void;
|
|
placeholder?: string;
|
|
contentUiId?: string;
|
|
}
|
|
|
|
const ConfirmModal = (props: Props, ref: Ref<RefConfirmModal>) => {
|
|
const { t } = useTranslation();
|
|
|
|
const [open, setOpen] = useState(false);
|
|
const [title, setTitle] = useState<string>();
|
|
const [value, setValue] = useState<string>();
|
|
const [description, setDescription] = useState<string>();
|
|
const [inputString, setInputString] = useState<string | undefined>("");
|
|
const [yesTitle, setYesTitle] = useState<string>("");
|
|
const [noTitle, setNoTitle] = useState<string>("");
|
|
const [isDestructive, setDestructive] = useState<boolean>(props.destructive || false);
|
|
|
|
const inputValue = useRef<HTMLInputElement>(null);
|
|
|
|
useEffect(() => {
|
|
setTitle(t("shared.confirm").toString());
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, []);
|
|
|
|
function no() {
|
|
setOpen(false);
|
|
if (props.onNo) {
|
|
props.onNo();
|
|
}
|
|
}
|
|
|
|
function yes() {
|
|
setOpen(false);
|
|
if (props.onYes) {
|
|
props.onYes(value ?? inputString);
|
|
}
|
|
}
|
|
|
|
useImperativeHandle(ref, () => ({ show, setValue, setDestructive }));
|
|
|
|
function show(
|
|
_question: string,
|
|
_yesTitle: string = t("shared.confirm").toString(),
|
|
_noTitle: string = t("shared.back").toString(),
|
|
_description?: string,
|
|
_inputString?: string
|
|
) {
|
|
setTitle(_question.toString());
|
|
if (_yesTitle) {
|
|
setYesTitle(_yesTitle);
|
|
}
|
|
if (_noTitle) {
|
|
setNoTitle(_noTitle);
|
|
}
|
|
if (_description) {
|
|
setDescription(_description);
|
|
}
|
|
setTimeout(() => {
|
|
if ((props.inputType === "email" || props.inputType === "string" || props.inputType === "slug" || props.inputType === "password") && inputValue.current) {
|
|
inputValue.current.focus();
|
|
inputValue.current.select();
|
|
}
|
|
}, 0);
|
|
setOpen(true);
|
|
setInputString(_inputString);
|
|
}
|
|
|
|
return (
|
|
<AlertDialog open={open} onOpenChange={setOpen}>
|
|
<AlertDialogContent data-ui-id={props.contentUiId}>
|
|
<AlertDialogHeader>
|
|
<AlertDialogTitle>{title}</AlertDialogTitle>
|
|
{description && <AlertDialogDescription>{description}</AlertDialogDescription>}
|
|
</AlertDialogHeader>
|
|
|
|
<div>
|
|
{/* Subframe ConfirmDialog spacing handled by AlertDialogContent gap-6 grid; per-input top margins removed. */}
|
|
{props.inputType === "email" && (
|
|
<div>
|
|
<div className="relative rounded-md shadow-2xs">
|
|
<div className="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
|
|
<MailIcon className="text-text-neutral-tertiary h-5 w-5" aria-hidden="true" />
|
|
</div>
|
|
<input
|
|
value={inputString}
|
|
onChange={(e) => setInputString(e.target.value)}
|
|
ref={inputValue}
|
|
type="email"
|
|
name="email"
|
|
id="email"
|
|
aria-label={title}
|
|
className="border-border-default focus:border-brand-brand-border-solid-default block w-full rounded-md pl-10 focus:shadow-focus sm:text-sm"
|
|
placeholder={props.placeholder ?? t("account.shared.emailPlaceholder")}
|
|
/>
|
|
</div>
|
|
</div>
|
|
)}
|
|
{props.inputType === "string" && (
|
|
<div>
|
|
<div className="relative rounded-md shadow-2xs">
|
|
<input
|
|
value={inputString}
|
|
onChange={(e) => setInputString(e.target.value)}
|
|
ref={inputValue}
|
|
type="text"
|
|
name="value"
|
|
id="value"
|
|
aria-label={title}
|
|
className="border-border-default focus:border-brand-brand-border-solid-default block w-full rounded-md focus:shadow-focus sm:text-sm"
|
|
placeholder={props.placeholder}
|
|
/>
|
|
</div>
|
|
</div>
|
|
)}
|
|
{props.inputType === "slug" && (
|
|
<div>
|
|
<div className="relative rounded-md shadow-2xs">
|
|
<input
|
|
value={inputString}
|
|
onChange={(e) => setInputString(e.target.value.toLowerCase())}
|
|
ref={inputValue}
|
|
type="text"
|
|
name="value"
|
|
id="value"
|
|
aria-label={title}
|
|
className="border-border-default focus:border-brand-brand-border-solid-default block w-full rounded-md focus:shadow-focus sm:text-sm"
|
|
placeholder={props.placeholder}
|
|
/>
|
|
</div>
|
|
</div>
|
|
)}
|
|
{props.inputType === "password" && (
|
|
<div>
|
|
<div className="relative rounded-md shadow-2xs">
|
|
<input
|
|
value={inputString}
|
|
onChange={(e) => setInputString(e.target.value)}
|
|
ref={inputValue}
|
|
type="password"
|
|
name="passwordCurrent"
|
|
id="passwordCurrent"
|
|
aria-label={title}
|
|
className="border-border-default focus:border-brand-brand-border-solid-default block w-full rounded-md focus:shadow-focus sm:text-sm"
|
|
placeholder={props.placeholder}
|
|
/>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<AlertDialogFooter>
|
|
<AlertDialogCancel onClick={no} className={cn("sm:col-start-1")} data-ui-id="ui.confirm.cancel">
|
|
{noTitle}
|
|
</AlertDialogCancel>
|
|
<AlertDialogAction onClick={yes} className={cn(isDestructive ? "bg-surface-feedback-solid-negative-default hover:bg-surface-feedback-solid-negative-hover" : undefined)} data-ui-id="ui.confirm.action">
|
|
{yesTitle}
|
|
</AlertDialogAction>
|
|
</AlertDialogFooter>
|
|
</AlertDialogContent>
|
|
</AlertDialog>
|
|
);
|
|
};
|
|
|
|
export default forwardRef(ConfirmModal);
|