From 0b945e601ecd01d89295fc9f2b5a76a51bd88f41 Mon Sep 17 00:00:00 2001 From: AbdulSnk Date: Wed, 25 Feb 2026 15:58:01 +0100 Subject: [PATCH 1/2] build confirmdialog reusable components --- frontend/components/ui/confirm-dialog.tsx | 149 ++++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 frontend/components/ui/confirm-dialog.tsx diff --git a/frontend/components/ui/confirm-dialog.tsx b/frontend/components/ui/confirm-dialog.tsx new file mode 100644 index 0000000..c8304bf --- /dev/null +++ b/frontend/components/ui/confirm-dialog.tsx @@ -0,0 +1,149 @@ +"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; From bf7a392f8ad46ae2104a1d594ae1f5df8dd81a78 Mon Sep 17 00:00:00 2001 From: AbdulSnk Date: Wed, 25 Feb 2026 17:35:46 +0100 Subject: [PATCH 2/2] build confirm dialog reusable componenets --- frontend/components/ui/confirm-dialog.tsx | 271 +++++++++++----------- 1 file changed, 136 insertions(+), 135 deletions(-) diff --git a/frontend/components/ui/confirm-dialog.tsx b/frontend/components/ui/confirm-dialog.tsx index c8304bf..0309c24 100644 --- a/frontend/components/ui/confirm-dialog.tsx +++ b/frontend/components/ui/confirm-dialog.tsx @@ -5,145 +5,146 @@ 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; + title?: string; + message?: React.ReactNode; + onConfirm: () => void; + onCancel: () => void; + loading?: boolean; } export function ConfirmDialog({ - title = "Confirm", - message = "Are you sure?", - onConfirm, - onCancel, - loading = false, + 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} -

-
- - -
- -
- - - -
-
-
- ); + 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; +