diff --git a/frontend/components/ui/confirm-dialog.tsx b/frontend/components/ui/confirm-dialog.tsx new file mode 100644 index 0000000..0309c24 --- /dev/null +++ b/frontend/components/ui/confirm-dialog.tsx @@ -0,0 +1,150 @@ +"use client"; + +import React, { useEffect, useRef } from "react"; +import { X, Loader2 } from "lucide-react"; +import { Button } from "@/components/ui/button"; + +interface ConfirmDialogProps { + title?: string; + message?: React.ReactNode; + onConfirm: () => void; + onCancel: () => void; + loading?: boolean; +} + +export function ConfirmDialog({ + title = "Confirm", + message = "Are you sure?", + onConfirm, + onCancel, + loading = false, +}: ConfirmDialogProps) { + const dialogRef = useRef(null); + const previouslyFocused = useRef(null); + + useEffect(() => { + // save previously focused element to restore later + previouslyFocused.current = document.activeElement as HTMLElement | null; + + // lock scroll + const prevOverflow = document.body.style.overflow; + document.body.style.overflow = "hidden"; + + // focus first focusable element in dialog + const node = dialogRef.current; + const focusable = node?.querySelector( + 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' + ); + if (focusable) focusable.focus(); + + const onKeyDown = (e: KeyboardEvent) => { + if (e.key === "Escape") { + e.preventDefault(); + onCancel(); + } + + if (e.key === "Tab") { + // simple focus trap + const container = dialogRef.current; + if (!container) return; + const focusableEls = Array.from( + container.querySelectorAll( + 'a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, [tabindex]:not([tabindex="-1"]), [contenteditable]' + ) + ).filter((el) => el.offsetWidth || el.offsetHeight || el.getClientRects().length); + + if (focusableEls.length === 0) { + e.preventDefault(); + return; + } + + const first = focusableEls[0]; + const last = focusableEls[focusableEls.length - 1]; + + if (!e.shiftKey && document.activeElement === last) { + e.preventDefault(); + first.focus(); + } + + if (e.shiftKey && document.activeElement === first) { + e.preventDefault(); + last.focus(); + } + } + }; + + document.addEventListener("keydown", onKeyDown); + + return () => { + document.removeEventListener("keydown", onKeyDown); + document.body.style.overflow = prevOverflow; + // restore focus + try { + previouslyFocused.current?.focus(); + } catch (err) { + // ignore + } + }; + }, [onCancel]); + + return ( +
+ {/* Backdrop */} +
+ + {/* Dialog */} +
+
+
+

+ {title} +

+

+ {message} +

+
+ + +
+ +
+ + + +
+
+
+ ); +} + +export default ConfirmDialog; +