80 lines
2.7 KiB
TypeScript
80 lines
2.7 KiB
TypeScript
import { useState } from 'react'
|
||
import { NAV_GROUPS, NAV_TOP_LEVEL, type NavTarget } from '../../data/nav'
|
||
|
||
/**
|
||
* 分类导航树:顶层直达链接 + 可折叠分组。
|
||
* 点击叶子节点不跳转页面,而是把目标(分类 + 可选分组)交给父组件的 onNavigate,
|
||
* 由父组件切换右侧的明细视图。
|
||
*/
|
||
type CategoryNavProps = {
|
||
/** 当前明细视图的分类 / 分组,用于高亮 */
|
||
activeCategoryId?: string
|
||
activeGroupId?: string
|
||
onNavigate: (target: NavTarget) => void
|
||
}
|
||
|
||
export function CategoryNav({ activeCategoryId, activeGroupId, onNavigate }: CategoryNavProps) {
|
||
const [openGroups, setOpenGroups] = useState<string[]>([])
|
||
|
||
const toggle = (id: string) => {
|
||
setOpenGroups((previous) =>
|
||
previous.includes(id) ? previous.filter((item) => item !== id) : [...previous, id],
|
||
)
|
||
}
|
||
|
||
const isOpen = (id: string) => openGroups.includes(id)
|
||
|
||
return (
|
||
<nav className="nav-card card-box" aria-label="站点分类">
|
||
<ul className="nav-group">
|
||
{NAV_TOP_LEVEL.map((leaf) => (
|
||
<li className="nav-item" key={leaf.id}>
|
||
<button
|
||
type="button"
|
||
className={`nav-link${
|
||
activeCategoryId === leaf.target.categoryId && activeGroupId === undefined
|
||
? ' active'
|
||
: ''
|
||
}`}
|
||
onClick={() => onNavigate(leaf.target)}
|
||
>
|
||
<span>{leaf.label}</span>
|
||
</button>
|
||
</li>
|
||
))}
|
||
|
||
{NAV_GROUPS.map((group) => (
|
||
<li className={`nav-item${isOpen(group.id) ? ' open' : ''}`} key={group.id}>
|
||
<button
|
||
type="button"
|
||
className="nav-link has-children"
|
||
aria-expanded={isOpen(group.id)}
|
||
onClick={() => toggle(group.id)}
|
||
>
|
||
<span>{group.label}</span>
|
||
</button>
|
||
|
||
{/* 用 grid-template-rows 做高度动画:不需要猜 max-height,
|
||
也不会在过渡期间出现滚动条 */}
|
||
<div className={`submenu-wrap${isOpen(group.id) ? ' is-open' : ''}`}>
|
||
<ul className="submenu">
|
||
{group.leaves.map((leaf) => (
|
||
<li key={leaf.id}>
|
||
<button
|
||
type="button"
|
||
className={`nav-link${activeGroupId === leaf.target.groupId ? ' active' : ''}`}
|
||
onClick={() => onNavigate(leaf.target)}
|
||
>
|
||
<span>{leaf.label}</span>
|
||
</button>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
</div>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
</nav>
|
||
)
|
||
}
|