NavOverlay.tsx
2.52 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
// REQ-USR-003: 全部导航总览浮层(BR7/BR8/D4)。受控 open;左 NAV_SIDE 列 + 右 NAV_COLS 网格。
// 叶子据 routePath 分流:有 → onNavigate;无 → onPlaceholder(占位「功能开发中」由调用方提示)。
import { useEffect } from 'react';
import { NAV_SIDE, NAV_COLS } from './navConfig';
import type { NavLeaf } from './navConfig';
import styles from './AppLayout.module.css';
interface NavOverlayProps {
open: boolean;
onClose: () => void;
onNavigate: (routePath: string) => void;
onPlaceholder: () => void;
}
export default function NavOverlay({ open, onClose, onNavigate, onPlaceholder }: NavOverlayProps) {
// Esc 关闭
useEffect(() => {
if (!open) return;
const handler = (e: KeyboardEvent) => {
if (e.key === 'Escape') onClose();
};
window.addEventListener('keydown', handler);
return () => window.removeEventListener('keydown', handler);
}, [open, onClose]);
if (!open) return null;
const handleLeaf = (leaf: NavLeaf) => {
if (leaf.routePath) onNavigate(leaf.routePath);
else onPlaceholder();
};
return (
<div
className={styles.navOverlay}
data-testid="nav-overlay"
role="dialog"
aria-modal="true"
>
{/* 遮罩:点击空白处关闭(仅自身触发,不冒泡子项) */}
<div
className={styles.navOverlayMask}
data-testid="nav-overlay-mask"
onClick={onClose}
aria-hidden="true"
/>
<div className={styles.navOverlayBody}>
<div className={styles.navSide} data-testid="nav-side">
{NAV_SIDE.map((s) => (
<div
key={s.key}
className={`${styles.navSideItem} ${s.active ? styles.navSideItemActive : ''}`}
aria-current={s.active ? 'true' : undefined}
>
{s.label}
</div>
))}
</div>
<div className={styles.navGrid}>
{NAV_COLS.map((col) => (
<div key={col.title} className={styles.navCol}>
<h3 className={styles.navColTitle}>{col.title}</h3>
{col.items.map((leaf) => (
<button
type="button"
key={leaf.label}
className={styles.navLeaf}
onClick={() => handleLeaf(leaf)}
>
{leaf.label}
{leaf.star && <span className={styles.navStar}>★</span>}
</button>
))}
</div>
))}
</div>
</div>
</div>
);
}