TopBar.tsx
3.46 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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
// REQ-USR-003 / REQ-USR-004: 顶栏(Logo + 全部导航按钮 + 标签条 + 右侧搜索/通知/当前用户/更多)。
import { MenuOutlined, SearchOutlined, BellOutlined, HomeOutlined } from '@ant-design/icons';
import type { AuthUser } from '../../api/types';
import type { TabItem } from './useTabStack';
import CurrentUserMenu from './CurrentUserMenu';
import styles from './AppLayout.module.css';
interface TopBarProps {
user: AuthUser | null;
tabs: TabItem[];
activeKey: string;
navOverlayOpen: boolean;
onToggleNav: () => void;
onSelectTab: (key: string) => void;
onCloseTab: (key: string) => void;
onLogout: () => void;
onLogoHome: () => void;
}
export default function TopBar({
user,
tabs,
activeKey,
navOverlayOpen,
onToggleNav,
onSelectTab,
onCloseTab,
onLogout,
onLogoHome,
}: TopBarProps) {
return (
<div className={styles.topbar}>
{/* 品牌 Logo(鹿角 SVG),点击回主页 */}
<button type="button" className={styles.logo} title="主页" onClick={onLogoHome} aria-label="品牌Logo 回到主页">
<svg viewBox="0 0 64 64" fill="currentColor" aria-hidden="true">
<path d="M14 10c2 4 1 8-1 11 3-1 7 0 10 3 1-4 4-7 8-7-3 3-4 7-3 11l4 1c-1 3 0 6 3 8-3 0-6 1-8 4-1-3-4-5-8-5 2-3 2-7 0-10-3 1-7 0-10-3 3 0 5-2 6-5l-1-8z" />
<path d="M48 14c-2 3-2 6-1 9-2-2-5-2-8-1 1 3 1 6-1 9 3 0 5 2 6 5 1-3 4-5 7-5-2-3-2-6 0-9 2 1 5 1 7-1-2 0-4-1-5-3-1-2-3-4-5-4z" />
<path d="M28 38c2 3 5 5 9 5 1 4 4 7 8 8-3 2-5 5-5 9-3-2-7-3-11-2 1-3 1-7-1-10-3 0-6-1-8-4 3-1 6-3 8-6z" />
</svg>
</button>
<div className={styles.tabs}>
<button
type="button"
className={`${styles.navBtn} ${navOverlayOpen ? styles.navBtnActive : ''}`}
aria-pressed={navOverlayOpen}
onClick={onToggleNav}
>
<MenuOutlined aria-hidden />
全部导航
</button>
{tabs.map((tab) => (
<button
type="button"
key={tab.key}
data-testid={`tab-${tab.key}`}
className={`${styles.tab} ${activeKey === tab.key ? styles.tabActive : ''}`}
aria-pressed={activeKey === tab.key}
onClick={() => onSelectTab(tab.key)}
>
{tab.key === 'home' && <HomeOutlined aria-hidden />}
{tab.title}
{tab.closable && (
<span
role="button"
tabIndex={0}
aria-label={`关闭${tab.title}`}
data-testid={`tab-close-${tab.key}`}
className={styles.tabClose}
onClick={(e) => {
e.stopPropagation();
onCloseTab(tab.key);
}}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.stopPropagation();
onCloseTab(tab.key);
}
}}
>
✕
</span>
)}
</button>
))}
</div>
<div className={styles.right}>
<span className={styles.rightIcon} title="搜索" aria-hidden="true">
<SearchOutlined />
</span>
<span className={styles.rightIcon} title="通知" aria-hidden="true">
<BellOutlined />
</span>
<CurrentUserMenu user={user} onLogout={onLogout} />
<span className={styles.more} aria-hidden="true">
⋯
</span>
</div>
</div>
);
}