Ports crewli-starter's sidebar nav into the SPA as production TS: V2NavGroup/V2NavItem types, a pure toV2NavGroups adapter wrapped by useV2Nav(items) (composables zone can't import @/navigation, so the v1 nav array is passed in — the layout supplies orgNavItems in Task 7), a pure isNavItemActive helper, and SidebarNav.vue (props-only, router-driven nav, route-based active state, collapsed mode, main.css translated to Tailwind inline). 16 unit tests. Icon import is allowed via the components-foundation bridge (no eslint-disable). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
38 lines
1.2 KiB
TypeScript
38 lines
1.2 KiB
TypeScript
import type { V2NavItem } from '@/types/v2/nav'
|
|
|
|
/**
|
|
* Pure helper — determines whether a V2NavItem is "active" given the current
|
|
* route name.
|
|
*
|
|
* Active rules (simplest correct definition):
|
|
* 1. Exact match: currentRouteName === item.to.name
|
|
* 2. Prefix match for nested routes: currentRouteName starts with
|
|
* item.to.name + '-' (e.g. item "organisation" is active on
|
|
* "organisation-settings"). The dash boundary prevents "org" from
|
|
* spuriously matching "organisation-settings".
|
|
*
|
|
* Only `to` values that are a plain object with a string `name` property are
|
|
* compared — string/array `to` values always return false (router-push
|
|
* style not used in v1 nav).
|
|
*/
|
|
export function isNavItemActive(
|
|
item: V2NavItem,
|
|
currentRouteName: string | symbol | null | undefined,
|
|
): boolean {
|
|
if (typeof currentRouteName !== 'string')
|
|
return false
|
|
|
|
const to = item.to
|
|
|
|
if (typeof to !== 'object' || to === null || Array.isArray(to))
|
|
return false
|
|
|
|
const itemName = (to as { name?: unknown }).name
|
|
|
|
if (typeof itemName !== 'string')
|
|
return false
|
|
|
|
return currentRouteName === itemName
|
|
|| currentRouteName.startsWith(`${itemName}-`)
|
|
}
|