feat(web): 命令面板 combobox+可见入口 + sticky 导航壳 + 焦点环统一 + 抽屉锁滚动/关闭按钮 + 主题首帧修复 + 设置徽标/datalist
This commit is contained in:
@@ -1,8 +1,12 @@
|
||||
"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 { useBodyScrollLock } from "@/lib/ui/useBodyScrollLock";
|
||||
import { overlayScrim } from "@/lib/ui/variants";
|
||||
|
||||
interface DrawerProps {
|
||||
open: boolean;
|
||||
@@ -15,7 +19,7 @@ interface DrawerProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
// 移动端侧滑抽屉(<lg;遮罩点击 / Esc 关闭 + 打开聚焦面板)。
|
||||
// 移动端侧滑抽屉(<lg;遮罩点击 / Esc 关闭 + 打开聚焦具名关闭按钮)。
|
||||
// 复用命令面板的 a11y 模式(CommandPalette);桌面用静态布局,不渲染本抽屉。
|
||||
export function Drawer({
|
||||
open,
|
||||
@@ -26,8 +30,11 @@ export function Drawer({
|
||||
children,
|
||||
}: DrawerProps) {
|
||||
const panelRef = useRef<HTMLDivElement>(null);
|
||||
const closeRef = useRef<HTMLButtonElement>(null);
|
||||
const wasOpenRef = useRef(false);
|
||||
|
||||
useBodyScrollLock(open);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) {
|
||||
if (wasOpenRef.current) {
|
||||
@@ -44,7 +51,8 @@ export function Drawer({
|
||||
}
|
||||
};
|
||||
window.addEventListener("keydown", onKey);
|
||||
panelRef.current?.focus();
|
||||
// 焦点陷阱首焦点落在具名关闭按钮上(而非整个面板)。
|
||||
closeRef.current?.focus();
|
||||
return () => window.removeEventListener("keydown", onKey);
|
||||
}, [open, onClose, triggerRef]);
|
||||
|
||||
@@ -53,10 +61,9 @@ export function Drawer({
|
||||
const sideClass = side === "left" ? "left-0 border-r" : "right-0 border-l";
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 bg-black/30 lg:hidden" onClick={onClose}>
|
||||
<div className={`${overlayScrim} lg:hidden`} onClick={onClose}>
|
||||
<div
|
||||
ref={panelRef}
|
||||
tabIndex={-1}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label={label}
|
||||
@@ -65,8 +72,19 @@ export function Drawer({
|
||||
// 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 border-line bg-panel py-4 shadow-paper outline-none ${sideClass}`}
|
||||
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}`}
|
||||
>
|
||||
<div className="mb-2 flex justify-end px-2">
|
||||
<Button
|
||||
ref={closeRef}
|
||||
onClick={onClose}
|
||||
aria-label={`关闭${label}`}
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
>
|
||||
<X className="h-5 w-5" aria-hidden="true" />
|
||||
</Button>
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user