mirror of
https://github.com/olehomelchenko/astrolabe.git
synced 2026-08-08 10:12:34 +00:00
Add in-app confirmation dialog replacing window.confirm
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
const FOCUSABLE =
|
||||
'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), ' +
|
||||
'textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
|
||||
|
||||
/**
|
||||
* Trap keyboard focus within an overlay while `active` (docs/architecture/03 →
|
||||
* Shell). On activation it remembers the previously focused element and moves
|
||||
* focus into the container; `Tab`/`Shift+Tab` cycle within it; on deactivation
|
||||
* focus returns to the trigger. Shared by every overlay (the confirm dialog now,
|
||||
* the feature-modal shell later) so accessibility is implemented once.
|
||||
*
|
||||
* `initialSelector` optionally picks which child receives focus on open
|
||||
* (e.g. the Cancel button for a destructive confirm); it falls back to the
|
||||
* first focusable child.
|
||||
*/
|
||||
export function useFocusTrap<T extends HTMLElement = HTMLDivElement>(
|
||||
active: boolean,
|
||||
initialSelector?: string,
|
||||
) {
|
||||
const ref = useRef<T>(null);
|
||||
const returnTo = useRef<Element | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const el = ref.current;
|
||||
if (!active || !el) return;
|
||||
|
||||
returnTo.current = document.activeElement;
|
||||
const initial =
|
||||
(initialSelector ? el.querySelector<HTMLElement>(initialSelector) : null) ??
|
||||
el.querySelector<HTMLElement>(FOCUSABLE);
|
||||
initial?.focus();
|
||||
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
if (e.key !== 'Tab') return;
|
||||
const f = el.querySelectorAll<HTMLElement>(FOCUSABLE);
|
||||
if (!f.length) return;
|
||||
const first = f[0];
|
||||
const last = f[f.length - 1];
|
||||
if (e.shiftKey && document.activeElement === first) {
|
||||
e.preventDefault();
|
||||
last.focus();
|
||||
} else if (!e.shiftKey && document.activeElement === last) {
|
||||
e.preventDefault();
|
||||
first.focus();
|
||||
}
|
||||
};
|
||||
|
||||
el.addEventListener('keydown', onKey);
|
||||
return () => {
|
||||
el.removeEventListener('keydown', onKey);
|
||||
(returnTo.current as HTMLElement | null)?.focus();
|
||||
};
|
||||
}, [active, initialSelector]);
|
||||
|
||||
return ref;
|
||||
}
|
||||
Reference in New Issue
Block a user