2026-06-23 16:55:10 +00:00
|
|
|
|
import { NavLink, useLocation } from 'react-router-dom'
|
2026-06-24 06:04:54 +00:00
|
|
|
|
import { navGroupsForOnboarding, onboardingGlowClass, shouldGlowNav } from '../lib/onboarding'
|
2026-06-23 16:55:10 +00:00
|
|
|
|
import type { AcAppKey } from '../lib/acAssets'
|
|
|
|
|
|
import { useOnboarding } from '../onboarding/OnboardingContext'
|
|
|
|
|
|
import { AcIcon } from './AcIcon'
|
|
|
|
|
|
|
|
|
|
|
|
function SidebarNavItem({
|
|
|
|
|
|
to,
|
|
|
|
|
|
label,
|
|
|
|
|
|
icon,
|
|
|
|
|
|
end,
|
|
|
|
|
|
matchPrefix,
|
2026-06-24 06:04:54 +00:00
|
|
|
|
guideGlow,
|
2026-06-23 16:55:10 +00:00
|
|
|
|
}: {
|
|
|
|
|
|
to: string
|
|
|
|
|
|
label: string
|
|
|
|
|
|
icon: AcAppKey
|
|
|
|
|
|
end?: boolean
|
|
|
|
|
|
matchPrefix?: string
|
2026-06-24 06:04:54 +00:00
|
|
|
|
guideGlow?: boolean
|
2026-06-23 16:55:10 +00:00
|
|
|
|
}) {
|
|
|
|
|
|
const { pathname } = useLocation()
|
|
|
|
|
|
return (
|
|
|
|
|
|
<NavLink
|
|
|
|
|
|
to={to}
|
|
|
|
|
|
end={end}
|
|
|
|
|
|
className={({ isActive }) => {
|
|
|
|
|
|
const prefixActive = matchPrefix ? pathname.startsWith(matchPrefix) : false
|
|
|
|
|
|
const active = isActive || prefixActive
|
2026-06-24 06:04:54 +00:00
|
|
|
|
return `ac-sidebar-nav-item relative ${active ? 'ac-sidebar-nav-item--active' : ''} ${onboardingGlowClass(!!guideGlow)}`
|
2026-06-23 16:55:10 +00:00
|
|
|
|
}}
|
|
|
|
|
|
>
|
2026-06-24 06:04:54 +00:00
|
|
|
|
{guideGlow ? (
|
|
|
|
|
|
<span className="hx-guide-badge" aria-hidden>
|
|
|
|
|
|
下一步
|
|
|
|
|
|
</span>
|
|
|
|
|
|
) : null}
|
2026-06-23 16:55:10 +00:00
|
|
|
|
<AcIcon app={icon} size="sm" className="ac-sidebar-nav-icon shrink-0" />
|
|
|
|
|
|
<span className="min-w-0 truncate">{label}</span>
|
|
|
|
|
|
</NavLink>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function AppSidebar() {
|
2026-06-24 06:04:54 +00:00
|
|
|
|
const { pathname } = useLocation()
|
|
|
|
|
|
const { isComplete, nextStep } = useOnboarding()
|
2026-06-23 16:55:10 +00:00
|
|
|
|
const groups = navGroupsForOnboarding(isComplete)
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<aside className="ac-sidebar hidden lg:flex" aria-label="側欄導覽">
|
|
|
|
|
|
{!isComplete ? (
|
|
|
|
|
|
<p className="ac-sidebar-onboarding-hint px-4 pb-2 pt-3 text-xs leading-relaxed text-ink-secondary">
|
|
|
|
|
|
完成入門設定後,會解鎖總覽、任務與更多功能。
|
|
|
|
|
|
</p>
|
|
|
|
|
|
) : null}
|
|
|
|
|
|
<nav className="ac-sidebar-nav ac-sidebar-nav--top">
|
|
|
|
|
|
{groups.map((group, index) => (
|
|
|
|
|
|
<div key={group.label} className="ac-sidebar-nav-group">
|
|
|
|
|
|
{index > 0 ? <div className="ac-sidebar-nav-divider" aria-hidden /> : null}
|
|
|
|
|
|
<p className="ac-sidebar-nav-label">{group.label}</p>
|
|
|
|
|
|
<ul className="space-y-0.5">
|
|
|
|
|
|
{group.items.map((item) => (
|
|
|
|
|
|
<li key={item.to}>
|
|
|
|
|
|
<SidebarNavItem
|
|
|
|
|
|
to={item.to}
|
|
|
|
|
|
label={item.label}
|
|
|
|
|
|
icon={item.icon}
|
|
|
|
|
|
end={item.end}
|
|
|
|
|
|
matchPrefix={item.matchPrefix}
|
2026-06-24 06:04:54 +00:00
|
|
|
|
guideGlow={shouldGlowNav(nextStep, item.to, pathname)}
|
2026-06-23 16:55:10 +00:00
|
|
|
|
/>
|
|
|
|
|
|
</li>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</ul>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</nav>
|
|
|
|
|
|
</aside>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|