fix(web): restore product navigation coverage
All checks were successful
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 24s
CD Pipeline / build-and-deploy (push) Successful in 4m52s
CD Pipeline / post-deploy-checks (push) Successful in 58s
All checks were successful
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 24s
CD Pipeline / build-and-deploy (push) Successful in 4m52s
CD Pipeline / post-deploy-checks (push) Successful in 58s
This commit is contained in:
@@ -15,7 +15,7 @@
|
||||
|
||||
import React, { useEffect, useRef, useState, useCallback } from 'react'
|
||||
import { useTranslations } from 'next-intl'
|
||||
import { useRouter, usePathname } from 'next/navigation'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { useLocale } from 'next-intl'
|
||||
import {
|
||||
Search,
|
||||
@@ -26,6 +26,7 @@ import { Z_INDEX } from '@/lib/constants/z-index'
|
||||
import {
|
||||
PRODUCT_BOTTOM_NAV_ITEMS,
|
||||
PRODUCT_NAV_SECTIONS,
|
||||
type ProductNavItem,
|
||||
} from '@/lib/navigation/product-ia'
|
||||
|
||||
interface PaletteItem {
|
||||
@@ -47,12 +48,15 @@ const COMMAND_PALETTE_PRIMARY_NAV_IDS = new Set([
|
||||
'operations',
|
||||
])
|
||||
|
||||
type PaletteNavSource = ProductNavItem & {
|
||||
parentId?: string
|
||||
}
|
||||
|
||||
export function CommandPalette() {
|
||||
const t = useTranslations('commandPalette')
|
||||
const tNav = useTranslations('nav')
|
||||
const router = useRouter()
|
||||
const locale = useLocale()
|
||||
const pathname = usePathname()
|
||||
const { openTerminal } = useTerminalStore()
|
||||
|
||||
const [open, setOpen] = useState(false)
|
||||
@@ -66,10 +70,21 @@ export function CommandPalette() {
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
const navigationItems: PaletteItem[] = [
|
||||
...PRODUCT_NAV_SECTIONS.flatMap(section => section.items),
|
||||
const navigationSources: PaletteNavSource[] = [
|
||||
...PRODUCT_NAV_SECTIONS.flatMap(section => section.items.flatMap(item => [
|
||||
item,
|
||||
...(item.children ?? []).map(child => ({
|
||||
...child,
|
||||
id: `${item.id}-${child.id}`,
|
||||
Icon: item.Icon,
|
||||
parentId: item.id,
|
||||
surface: 'secondary' as const,
|
||||
})),
|
||||
])),
|
||||
...PRODUCT_BOTTOM_NAV_ITEMS,
|
||||
].map((item) => {
|
||||
]
|
||||
|
||||
const navigationItems: PaletteItem[] = navigationSources.map((item) => {
|
||||
const path = item.href === '/' ? '' : item.href
|
||||
|
||||
return {
|
||||
@@ -82,6 +97,7 @@ export function CommandPalette() {
|
||||
item.id,
|
||||
item.href,
|
||||
item.labelKey,
|
||||
item.parentId ?? '',
|
||||
...(item.aliases ?? []),
|
||||
...(item.relatedPaths ?? []),
|
||||
],
|
||||
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
import {
|
||||
PRODUCT_BOTTOM_NAV_ITEMS,
|
||||
PRODUCT_NAV_SECTIONS,
|
||||
type ProductNavChild,
|
||||
type ProductNavItem,
|
||||
} from '@/lib/navigation/product-ia'
|
||||
// Phase 8.0 #15: 改用 approval store SSE (移除 polling)
|
||||
@@ -103,8 +104,10 @@ export function Sidebar({
|
||||
const isActive = (item: ProductNavItem) => (
|
||||
isRouteActive(item.href, item.exact) ||
|
||||
item.aliases?.some(alias => isRouteActive(alias)) === true ||
|
||||
item.relatedPaths?.some(path => isRouteActive(path)) === true
|
||||
item.relatedPaths?.some(path => isRouteActive(path)) === true ||
|
||||
item.children?.some(child => isChildActive(child)) === true
|
||||
)
|
||||
const isChildActive = (item: ProductNavChild) => isRouteActive(item.href, item.exact)
|
||||
const sidebarWidth = width ?? (compact && collapsed ? 48 : collapsed ? 64 : 224)
|
||||
|
||||
return (
|
||||
@@ -150,48 +153,88 @@ export function Sidebar({
|
||||
const count = item.badge && mounted ? pendingCount : 0
|
||||
const secondary = item.surface === 'secondary'
|
||||
return (
|
||||
<Link
|
||||
key={item.id}
|
||||
href={`/${locale}${item.href === '/' ? '' : item.href}`}
|
||||
title={collapsed ? t(item.labelKey) : undefined}
|
||||
onClick={onNavigate}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: collapsed ? 0 : secondary ? 7 : 8,
|
||||
justifyContent: collapsed ? 'center' : 'flex-start',
|
||||
padding: collapsed ? '8px 0' : secondary ? '4px 12px 4px 30px' : '6px 12px',
|
||||
fontSize: secondary ? 13 : 14,
|
||||
color: active ? '#141413' : secondary ? '#77736a' : '#87867f',
|
||||
fontWeight: active ? 600 : 400,
|
||||
borderRight: active ? '2px solid #d97757' : '2px solid transparent',
|
||||
background: active ? 'rgba(217,119,87,0.08)' : 'transparent',
|
||||
textDecoration: 'none',
|
||||
transition: 'all 0.15s',
|
||||
position: 'relative' as const,
|
||||
minHeight: collapsed ? 36 : secondary ? 28 : 32,
|
||||
}}
|
||||
>
|
||||
<item.Icon size={secondary ? 14 : 15} aria-hidden="true" />
|
||||
{!collapsed && (
|
||||
<span style={{ minWidth: 0, overflowWrap: 'anywhere' }}>
|
||||
{t(item.labelKey)}
|
||||
</span>
|
||||
)}
|
||||
{item.badge && count > 0 && (
|
||||
<span style={{
|
||||
marginLeft: 'auto',
|
||||
fontSize: 12,
|
||||
background: '#d97757',
|
||||
color: 'white',
|
||||
borderRadius: 10,
|
||||
padding: '1px 5px',
|
||||
fontWeight: 700,
|
||||
}}>
|
||||
{count > 99 ? '99+' : count}
|
||||
</span>
|
||||
)}
|
||||
</Link>
|
||||
<div key={item.id}>
|
||||
<Link
|
||||
href={`/${locale}${item.href === '/' ? '' : item.href}`}
|
||||
title={collapsed ? t(item.labelKey) : undefined}
|
||||
onClick={onNavigate}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: collapsed ? 0 : secondary ? 7 : 8,
|
||||
justifyContent: collapsed ? 'center' : 'flex-start',
|
||||
padding: collapsed ? '8px 0' : secondary ? '4px 12px 4px 30px' : '6px 12px',
|
||||
fontSize: secondary ? 13 : 14,
|
||||
color: active ? '#141413' : secondary ? '#77736a' : '#87867f',
|
||||
fontWeight: active ? 600 : 400,
|
||||
borderRight: active ? '2px solid #d97757' : '2px solid transparent',
|
||||
background: active ? 'rgba(217,119,87,0.08)' : 'transparent',
|
||||
textDecoration: 'none',
|
||||
transition: 'all 0.15s',
|
||||
position: 'relative' as const,
|
||||
minHeight: collapsed ? 36 : secondary ? 28 : 32,
|
||||
}}
|
||||
>
|
||||
<item.Icon size={secondary ? 14 : 15} aria-hidden="true" />
|
||||
{!collapsed && (
|
||||
<span style={{ minWidth: 0, overflowWrap: 'anywhere' }}>
|
||||
{t(item.labelKey)}
|
||||
</span>
|
||||
)}
|
||||
{item.badge && count > 0 && (
|
||||
<span style={{
|
||||
marginLeft: 'auto',
|
||||
fontSize: 12,
|
||||
background: '#d97757',
|
||||
color: 'white',
|
||||
borderRadius: 10,
|
||||
padding: '1px 5px',
|
||||
fontWeight: 700,
|
||||
}}>
|
||||
{count > 99 ? '99+' : count}
|
||||
</span>
|
||||
)}
|
||||
</Link>
|
||||
{!collapsed && item.children?.length ? (
|
||||
<div
|
||||
aria-label={t(item.labelKey)}
|
||||
style={{
|
||||
display: 'grid',
|
||||
gap: 1,
|
||||
margin: '1px 0 5px',
|
||||
paddingLeft: secondary ? 44 : 35,
|
||||
paddingRight: 8,
|
||||
}}
|
||||
>
|
||||
{item.children.map(child => {
|
||||
const childActive = isChildActive(child)
|
||||
return (
|
||||
<Link
|
||||
key={`${item.id}-${child.id}`}
|
||||
href={`/${locale}${child.href}`}
|
||||
onClick={onNavigate}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
minHeight: 24,
|
||||
padding: '3px 8px',
|
||||
borderLeft: childActive ? '2px solid #d97757' : '2px solid #e7e2d8',
|
||||
color: childActive ? '#141413' : '#7d7a72',
|
||||
background: childActive ? 'rgba(217,119,87,0.08)' : 'transparent',
|
||||
fontSize: 12,
|
||||
fontWeight: childActive ? 650 : 450,
|
||||
lineHeight: 1.25,
|
||||
textDecoration: 'none',
|
||||
overflowWrap: 'anywhere',
|
||||
}}
|
||||
>
|
||||
{t(child.labelKey)}
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user