"use client"; import { useEffect, useRef, type ReactNode, type RefObject } from "react"; import { X } from "lucide-react"; import { Button } from "@/components/ui/Button"; import { handleTabTrap } from "@/lib/a11y/focusTrap"; import { useRestoreFocus } from "@/lib/a11y/useRestoreFocus"; import { useBodyScrollLock } from "@/lib/ui/useBodyScrollLock"; import { overlayScrim } from "@/lib/ui/variants"; interface DrawerProps { open: boolean; onClose: () => void; // 滑出方向:导航在左、本章助手在右。 side?: "left" | "right"; // 无障碍标签(role=dialog)。 label: string; triggerRef?: RefObject; children: ReactNode; } // 移动端侧滑抽屉((null); const closeRef = useRef(null); useBodyScrollLock(open); useRestoreFocus(open, triggerRef); useEffect(() => { if (!open) return; const onKey = (e: KeyboardEvent): void => { if (e.key === "Escape") { e.preventDefault(); onClose(); } }; window.addEventListener("keydown", onKey); // 焦点陷阱首焦点落在具名关闭按钮上(而非整个面板)。 closeRef.current?.focus(); return () => window.removeEventListener("keydown", onKey); }, [open, onClose]); if (!open) return null; const sideClass = side === "left" ? "left-0 border-r" : "right-0 border-l"; return (
e.stopPropagation()} onKeyDown={(e) => { // focus trap:Tab/Shift+Tab 在抽屉内循环,不逃逸到背景(WCAG 2.1.2)。 if (panelRef.current) handleTabTrap(panelRef.current, e); }} className={`absolute top-0 h-full w-64 max-w-[80vw] overflow-auto overscroll-contain border-line bg-panel py-4 shadow-paper outline-none ${sideClass}`} >
{children}
); }