openreader/src/components/ConfirmDialog.tsx
Richard R 9561d5a816 fix(ui): adjust modal positioning for responsive design
Update modal components to start from top on mobile devices and center on larger screens, improving usability across screen sizes. Also refine transition handling in privacy popup for cleaner unmounting.
2026-01-25 15:13:02 -07:00

107 lines
3.8 KiB
TypeScript

import { Fragment, KeyboardEvent } from 'react';
import { Dialog, DialogPanel, DialogTitle, Transition, TransitionChild } from '@headlessui/react';
interface ConfirmDialogProps {
isOpen: boolean;
onClose: () => void;
onConfirm: () => void;
title: string;
message: string;
confirmText?: string;
cancelText?: string;
isDangerous?: boolean;
}
export function ConfirmDialog({
isOpen,
onClose,
onConfirm,
title,
message,
confirmText = 'Confirm',
cancelText = 'Cancel',
isDangerous = false,
}: ConfirmDialogProps) {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Enter') {
e.preventDefault();
onConfirm();
}
};
return (
<Transition appear show={isOpen} as={Fragment}>
<Dialog
as="div"
role={undefined}
className="relative z-50"
onClose={onClose}
onKeyDown={handleKeyDown}
>
<TransitionChild
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 overlay-dim backdrop-blur-sm" />
</TransitionChild>
<div className="fixed inset-0 overflow-y-auto">
<div className="flex min-h-full items-start justify-center p-4 pt-6 text-center sm:items-center sm:pt-4">
<TransitionChild
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<DialogPanel role='dialog' className="w-full max-w-md transform rounded-2xl bg-base p-6 text-left align-middle shadow-xl transition-all">
<DialogTitle
as="h3"
className="text-lg font-semibold leading-6 text-foreground"
>
{title}
</DialogTitle>
<div className="mt-2">
<p className="text-sm text-foreground/90">{message}</p>
</div>
<div className="mt-6 flex justify-end space-x-3">
<button
type="button"
className="inline-flex justify-center rounded-lg bg-background px-3 py-1.5 text-sm
font-medium text-foreground hover:bg-offbase focus:outline-none
focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2
transform transition-transform duration-200 ease-in-out hover:scale-[1.04] hover:text-accent"
onClick={onClose}
>
{cancelText}
</button>
<button
type="button"
className={`inline-flex justify-center rounded-lg px-3 py-1.5 text-sm
font-medium focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2
transform transition-transform duration-200 ease-in-out hover:scale-[1.04]
${isDangerous
? 'bg-accent text-background hover:bg-secondary-accent focus-visible:ring-accent'
: 'bg-accent text-background hover:bg-secondary-accent focus-visible:ring-accent'
}`}
onClick={onConfirm}
>
{confirmText}
</button>
</div>
</DialogPanel>
</TransitionChild>
</div>
</div>
</Dialog>
</Transition>
);
}