mirror of
https://github.com/codeaashu/claude-code.git
synced 2026-04-08 22:28:48 +03:00
claude-code
This commit is contained in:
72
web/components/a11y/FocusTrap.tsx
Normal file
72
web/components/a11y/FocusTrap.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef, type ReactNode } from "react";
|
||||
|
||||
interface FocusTrapProps {
|
||||
children: ReactNode;
|
||||
/** When false, the trap is inactive (e.g. when the panel is hidden) */
|
||||
active?: boolean;
|
||||
}
|
||||
|
||||
const FOCUSABLE_SELECTORS = [
|
||||
"a[href]",
|
||||
"button:not([disabled])",
|
||||
"input:not([disabled])",
|
||||
"select:not([disabled])",
|
||||
"textarea:not([disabled])",
|
||||
'[tabindex]:not([tabindex="-1"])',
|
||||
].join(", ");
|
||||
|
||||
/**
|
||||
* Traps keyboard focus within its children when `active` is true.
|
||||
* Use for modals, drawers, and other overlay patterns.
|
||||
* Note: Radix Dialog handles focus trapping natively — use this only for
|
||||
* custom overlay components that don't use Radix primitives.
|
||||
*/
|
||||
export function FocusTrap({ children, active = true }: FocusTrapProps) {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!active) return;
|
||||
|
||||
const container = containerRef.current;
|
||||
if (!container) return;
|
||||
|
||||
const focusable = () =>
|
||||
Array.from(container.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTORS)).filter(
|
||||
(el) => !el.closest("[aria-hidden='true']")
|
||||
);
|
||||
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key !== "Tab") return;
|
||||
const els = focusable();
|
||||
if (els.length === 0) return;
|
||||
|
||||
const first = els[0];
|
||||
const last = els[els.length - 1];
|
||||
|
||||
if (e.shiftKey) {
|
||||
if (document.activeElement === first) {
|
||||
e.preventDefault();
|
||||
last.focus();
|
||||
}
|
||||
} else {
|
||||
if (document.activeElement === last) {
|
||||
e.preventDefault();
|
||||
first.focus();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Move focus into the trap on mount
|
||||
const els = focusable();
|
||||
if (els.length > 0 && !container.contains(document.activeElement)) {
|
||||
els[0].focus();
|
||||
}
|
||||
|
||||
document.addEventListener("keydown", handleKeyDown);
|
||||
return () => document.removeEventListener("keydown", handleKeyDown);
|
||||
}, [active]);
|
||||
|
||||
return <div ref={containerRef}>{children}</div>;
|
||||
}
|
||||
Reference in New Issue
Block a user