From 8eb83d13ecb291983070ee36e37e1928025f4852 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 16 Oct 2026 12:48:27 +0000 Subject: [PATCH 1/5] feat(apollo-vertex): backport invoice shell nav Let a nav group link to a default child and stay active for its prefix, while the matching child keeps the current pill. Resolve nav labels from the app catalog before the vertex catalog. Also let LocaleProvider take the app i18n init, keep ApolloShell on the loading skeleton until the token query finishes, paint the sidebar hairline with var(--border), and name the collapse button. --- .../content/docs/patterns/shell.mdx | 25 ++- packages/apollo-vertex/registry.json | 8 + .../registry/shell/shell-company.tsx | 3 +- .../registry/shell/shell-locale-provider.tsx | 18 +- .../registry/shell/shell-minimal-nav-item.tsx | 4 +- .../registry/shell/shell-nav-active.test.tsx | 145 ++++++++++++++ .../registry/shell/shell-nav-active.ts | 30 +++ .../registry/shell/shell-nav-label.ts | 67 +++++++ .../registry/shell/shell-sidebar.tsx | 188 +++++++++--------- .../registry/shell/shell-text.tsx | 9 +- .../registry/shell/shell-translation-key.ts | 6 + .../apollo-vertex/registry/shell/shell.tsx | 33 ++- 12 files changed, 421 insertions(+), 115 deletions(-) create mode 100644 packages/apollo-vertex/registry/shell/shell-nav-active.test.tsx create mode 100644 packages/apollo-vertex/registry/shell/shell-nav-active.ts create mode 100644 packages/apollo-vertex/registry/shell/shell-nav-label.ts diff --git a/apps/apollo-vertex/content/docs/patterns/shell.mdx b/apps/apollo-vertex/content/docs/patterns/shell.mdx index 51af506f41..0e966535b5 100644 --- a/apps/apollo-vertex/content/docs/patterns/shell.mdx +++ b/apps/apollo-vertex/content/docs/patterns/shell.mdx @@ -52,12 +52,35 @@ function App() { } ``` +## Nested navigation + +A group can set `to` and `subItems`. The row links to `to`, or to `path` when `to` is omitted, and stays in the active color for `path` and every route under it. The matching child gets the current background. While the sidebar is collapsed, that background stays off the parent: the parent icon keeps the active color. + +Labels are translation keys. A key in the app's default catalog is used first, including keys that are not in Apollo Vertex. Vertex keys such as `dashboard` still resolve when the app catalog does not define them. + +```tsx +{ + path: '/rules', + to: '/rules/exceptions', + label: 'rules', + icon: ListTree, + subItems: [ + { path: '/rules/feedback', label: 'feedback' }, + { path: '/rules/exceptions', label: 'exceptions' }, + ], +} +``` + +## App translations + +Pass `configureI18n` to `ApolloShell` or `LocaleProvider` when the app initializes i18next itself. The shell then runs that function instead of replacing the app catalog with the Vertex one. + ## Adding Authentication To enable built-in OAuth2 authentication with PKCE, wrap the shell with `ShellAuthProvider`. Both `ShellAuthProvider` and `ApolloShell` must be inside a `QueryClientProvider` from `@tanstack/react-query`. -When a `ShellAuthProvider` is present, the shell automatically shows a login screen until the user is authenticated. +When a `ShellAuthProvider` is present, the shell automatically shows a login screen until the user is authenticated. While the token is still loading, it shows the loading skeleton instead of that login screen. ```tsx import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; diff --git a/packages/apollo-vertex/registry.json b/packages/apollo-vertex/registry.json index 64fca8247a..2399496085 100644 --- a/packages/apollo-vertex/registry.json +++ b/packages/apollo-vertex/registry.json @@ -2406,6 +2406,14 @@ "path": "registry/shell/shell-sidebar.tsx", "type": "registry:ui" }, + { + "path": "registry/shell/shell-nav-active.ts", + "type": "registry:ui" + }, + { + "path": "registry/shell/shell-nav-label.ts", + "type": "registry:ui" + }, { "path": "registry/shell/shell-company.tsx", "type": "registry:ui" diff --git a/packages/apollo-vertex/registry/shell/shell-company.tsx b/packages/apollo-vertex/registry/shell/shell-company.tsx index 28eb021202..ea0cd790c5 100644 --- a/packages/apollo-vertex/registry/shell/shell-company.tsx +++ b/packages/apollo-vertex/registry/shell/shell-company.tsx @@ -2,7 +2,6 @@ import { Link } from "@tanstack/react-router"; import { AnimatePresence, motion } from "framer-motion"; import { PanelLeft } from "lucide-react"; import { useState } from "react"; -import { useVertexTranslation } from "@/lib/vertex-translation"; import { Button } from "@/components/ui/button"; import { useSidebar } from "@/components/ui/sidebar"; import { @@ -12,6 +11,7 @@ import { TooltipTrigger, } from "@/components/ui/tooltip"; import { cn } from "@/lib/utils"; +import { useVertexTranslation } from "@/lib/vertex-translation"; import type { CompanyLogo } from "./shell"; import { fastFadeTransition, @@ -187,6 +187,7 @@ export const Company = ({ + + )} + {item.subItems.map((subItem) => { - const subActive = isActive(subItem.path); + const sub = navItemState(pathname, { + path: subItem.path, + to: subItem.to, + }); return ( - + @@ -301,11 +309,11 @@ function SidebarNav({ - + diff --git a/packages/apollo-vertex/registry/shell/shell-text.tsx b/packages/apollo-vertex/registry/shell/shell-text.tsx index eea2388aed..58c3d80c71 100644 --- a/packages/apollo-vertex/registry/shell/shell-text.tsx +++ b/packages/apollo-vertex/registry/shell/shell-text.tsx @@ -3,12 +3,13 @@ import { useVertexTranslation, VERTEX_NAMESPACE, } from "@/lib/vertex-translation"; -import type { TranslationKey } from "./shell-translation-key"; +import { navCatalogFromHook, readNavLabel } from "./shell-nav-label"; +import type { ShellNavLabel } from "./shell-translation-key"; -export const Text = ({ value }: { value: TranslationKey }) => { - const { t } = useVertexTranslation(); +export const Text = ({ value }: { value: ShellNavLabel }) => { + const { i18n } = useVertexTranslation(); if (typeof value === "string") { - return t(value); + return readNavLabel(navCatalogFromHook(i18n), value); } return ( ; } | ParseKeys; + +type TranslationObject = Extract; + +// Plain strings are app-catalog keys or vertex keys. Vertex keys are strings, +// so they still typecheck; the app catalog is what makes invoice labels resolve. +export type ShellNavLabel = string | TranslationObject; diff --git a/packages/apollo-vertex/registry/shell/shell.tsx b/packages/apollo-vertex/registry/shell/shell.tsx index 8425c7b4c0..f7fef7543a 100644 --- a/packages/apollo-vertex/registry/shell/shell.tsx +++ b/packages/apollo-vertex/registry/shell/shell.tsx @@ -8,9 +8,18 @@ import { AuthContext, useAuth } from "./shell-auth-provider"; import { ShellLayout } from "./shell-layout"; import { LocaleProvider } from "./shell-locale-provider"; import { ShellLogin } from "./shell-login"; -import type { TranslationKey } from "./shell-translation-key"; +import type { ShellNavLabel } from "./shell-translation-key"; import { ShellUserProvider } from "./shell-user-provider"; +function ShellLoadingSkeleton() { + return ( +
+ + +
+ ); +} + export interface CompanyLogo { url: string; darkUrl?: string; @@ -20,12 +29,14 @@ export interface CompanyLogo { export interface ShellSubNavItem { path: string; - label: TranslationKey; + to?: string; + label: ShellNavLabel; } export interface ShellNavItem { path: string; - label: TranslationKey; + to?: string; + label: ShellNavLabel; icon: LucideIcon; subItems?: ShellSubNavItem[]; } @@ -37,6 +48,7 @@ export interface ApolloShellProps extends PropsWithChildren { companyLogo?: CompanyLogo; navItems: ShellNavItem[]; loginDescription?: string; + configureI18n?: () => Promise; } const ApolloShellContent: FC = ({ @@ -49,7 +61,11 @@ const ApolloShellContent: FC = ({ loginDescription, }) => { const authContext = useContext(AuthContext); - const { accessToken } = useAuth(); + const { accessToken, isLoading } = useAuth(); + + if (authContext && isLoading) { + return ; + } if (authContext && !accessToken) { return ; @@ -78,15 +94,12 @@ export const ApolloShell: FC = ({ variant, navItems, loginDescription, + configureI18n, }) => { return ( - - - - } + configureI18n={configureI18n} + loadingElement={} > Date: Fri, 16 Oct 2026 12:59:03 +0000 Subject: [PATCH 2/5] fix(apollo-vertex): keep nav labels and active color Walk a defaultNS list in order before the vertex catalog. Keep a prefix-active parent's primary color on hover and press. Highlight minimal nav items by path, and use to only as the link target. --- .../content/docs/patterns/shell.mdx | 2 +- .../registry/shell/shell-minimal-nav-item.tsx | 6 +- .../registry/shell/shell-nav-active.test.tsx | 68 ++++++++++++++++++- .../registry/shell/shell-nav-label.ts | 52 ++++++++------ .../registry/shell/shell-sidebar.tsx | 8 ++- 5 files changed, 108 insertions(+), 28 deletions(-) diff --git a/apps/apollo-vertex/content/docs/patterns/shell.mdx b/apps/apollo-vertex/content/docs/patterns/shell.mdx index 0e966535b5..66dc24faf5 100644 --- a/apps/apollo-vertex/content/docs/patterns/shell.mdx +++ b/apps/apollo-vertex/content/docs/patterns/shell.mdx @@ -54,7 +54,7 @@ function App() { ## Nested navigation -A group can set `to` and `subItems`. The row links to `to`, or to `path` when `to` is omitted, and stays in the active color for `path` and every route under it. The matching child gets the current background. While the sidebar is collapsed, that background stays off the parent: the parent icon keeps the active color. +A group can set `to` and `subItems`. The row links to `to`, or to `path` when `to` is omitted, and stays in the active color for `path` and every route under it, including on hover and press. The matching child gets the current background. While the sidebar is collapsed, that background stays off the parent: the parent icon keeps the active color. The minimal header uses the same path match. `to` is only the link target. Labels are translation keys. A key in the app's default catalog is used first, including keys that are not in Apollo Vertex. Vertex keys such as `dashboard` still resolve when the app catalog does not define them. diff --git a/packages/apollo-vertex/registry/shell/shell-minimal-nav-item.tsx b/packages/apollo-vertex/registry/shell/shell-minimal-nav-item.tsx index f8abcff598..893fdadc29 100644 --- a/packages/apollo-vertex/registry/shell/shell-minimal-nav-item.tsx +++ b/packages/apollo-vertex/registry/shell/shell-minimal-nav-item.tsx @@ -1,16 +1,18 @@ import { Link, useLocation } from "@tanstack/react-router"; import { cn } from "@/lib/utils"; +import { navPathIsActive } from "./shell-nav-active"; import { Text } from "./shell-text"; import type { ShellNavLabel } from "./shell-translation-key"; interface MinimalNavItemProps { to: string; + path: string; label: ShellNavLabel; } -export const MinimalNavItem = ({ to, label }: MinimalNavItemProps) => { +export const MinimalNavItem = ({ to, path, label }: MinimalNavItemProps) => { const { pathname } = useLocation(); - const isActive = pathname === to; + const isActive = navPathIsActive(pathname, path); return ( { it("matches the path and its nested pages", () => { @@ -95,13 +99,29 @@ describe("navItemState", () => { }); function catalog(bundles: Record>): NavCatalog { + const configured = Object.keys(bundles).filter( + (namespace) => namespace !== VERTEX_NAMESPACE, + ); return { - defaultNamespace: "translation", + namespaces: [...configured, VERTEX_NAMESPACE], exists: (key, namespace) => Object.hasOwn(bundles[namespace] ?? {}, key), translate: (key, namespace) => bundles[namespace]?.[key] ?? key, }; } +function hookI18n( + defaultNS: string | false | readonly string[] | undefined, + bundles: Record>, +) { + return { + options: { defaultNS }, + exists: (key: string, options?: { ns?: string }) => + Object.hasOwn(bundles[options?.ns ?? ""] ?? {}, key), + t: (key: string, options?: { ns?: string }) => + bundles[options?.ns ?? ""]?.[key] ?? key, + }; +} + describe("readNavLabel", () => { it("reads an app key that is not in the vertex catalog", () => { expect( @@ -143,3 +163,47 @@ describe("readNavLabel", () => { expect(readNavLabel(catalog({ translation: {} }), "queue")).toBe("queue"); }); }); + +describe("navCatalogFromHook", () => { + it("walks an array defaultNS in order before the vertex catalog", () => { + const resolved = navCatalogFromHook( + hookI18n(["billing", "shared"], { + shared: { queue: "My work" }, + [VERTEX_NAMESPACE]: { queue: "Queue" }, + }), + ); + + expect(readNavLabel(resolved, "queue")).toBe("My work"); + }); + + it("keeps an app-only namespace instead of replacing the list with translation", () => { + const resolved = navCatalogFromHook( + hookI18n(["invoice"], { + invoice: { queue: "My work" }, + }), + ); + + expect(readNavLabel(resolved, "queue")).toBe("My work"); + }); + + it("prefers an earlier configured namespace over a later one", () => { + const resolved = navCatalogFromHook( + hookI18n(["first", "second"], { + first: { queue: "First" }, + second: { queue: "Second" }, + }), + ); + + expect(readNavLabel(resolved, "queue")).toBe("First"); + }); + + it("falls back to the vertex catalog when no configured namespace has the key", () => { + const resolved = navCatalogFromHook( + hookI18n(["billing", "shared"], { + [VERTEX_NAMESPACE]: { dashboard: "Dashboard" }, + }), + ); + + expect(readNavLabel(resolved, "dashboard")).toBe("Dashboard"); + }); +}); diff --git a/packages/apollo-vertex/registry/shell/shell-nav-label.ts b/packages/apollo-vertex/registry/shell/shell-nav-label.ts index 53e57b5de3..ef9ed55e11 100644 --- a/packages/apollo-vertex/registry/shell/shell-nav-label.ts +++ b/packages/apollo-vertex/registry/shell/shell-nav-label.ts @@ -1,7 +1,7 @@ import { VERTEX_NAMESPACE } from "@/lib/vertex-translation"; export interface NavCatalog { - defaultNamespace: string; + namespaces: readonly string[]; exists: (key: string, namespace: string) => boolean; translate: (key: string, namespace: string) => string; } @@ -12,11 +12,29 @@ interface NamespacedI18n { t: (key: string, options?: { ns?: string }) => unknown; } -function defaultNamespaceOf( - defaultNS?: string | false | readonly string[], -): string { - if (typeof defaultNS === "string" && defaultNS.length > 0) return defaultNS; - return "translation"; +function configuredNamespaces( + defaultNS: string | false | readonly string[] | undefined, +): readonly string[] { + if (typeof defaultNS === "string") { + return defaultNS.length > 0 ? [defaultNS] : ["translation"]; + } + if (!Array.isArray(defaultNS)) return ["translation"]; + + const namespaces: string[] = []; + for (const namespace of defaultNS) { + if (typeof namespace === "string" && namespace.length > 0) { + namespaces.push(namespace); + } + } + return namespaces.length > 0 ? namespaces : ["translation"]; +} + +function lookupNamespaces( + defaultNS: string | false | readonly string[] | undefined, +): readonly string[] { + const configured = configuredNamespaces(defaultNS); + if (configured.includes(VERTEX_NAMESPACE)) return configured; + return [...configured, VERTEX_NAMESPACE]; } function isNamespacedI18n(value: object): value is NamespacedI18n { @@ -32,14 +50,14 @@ function isNamespacedI18n(value: object): value is NamespacedI18n { export function navCatalogFromHook(i18n: object): NavCatalog { if (!isNamespacedI18n(i18n)) { return { - defaultNamespace: "translation", + namespaces: ["translation", VERTEX_NAMESPACE], exists: () => false, translate: (key) => key, }; } return { - defaultNamespace: defaultNamespaceOf(i18n.options.defaultNS), + namespaces: lookupNamespaces(i18n.options.defaultNS), exists: (key, namespace) => i18n.exists(key, { ns: namespace }), translate: (key, namespace) => { const value = i18n.t(key, { ns: namespace }); @@ -48,20 +66,12 @@ export function navCatalogFromHook(i18n: object): NavCatalog { }; } -// App catalog first, then the vertex catalog. A missing key stays the raw key. +// Configured namespaces in order, then the vertex catalog. A missing key stays the raw key. export function readNavLabel(catalog: NavCatalog, key: string): string { - const { defaultNamespace } = catalog; - if ( - defaultNamespace !== VERTEX_NAMESPACE && - catalog.exists(key, defaultNamespace) - ) { - return catalog.translate(key, defaultNamespace); - } - if (catalog.exists(key, VERTEX_NAMESPACE)) { - return catalog.translate(key, VERTEX_NAMESPACE); - } - if (catalog.exists(key, defaultNamespace)) { - return catalog.translate(key, defaultNamespace); + for (const namespace of catalog.namespaces) { + if (catalog.exists(key, namespace)) { + return catalog.translate(key, namespace); + } } return key; } diff --git a/packages/apollo-vertex/registry/shell/shell-sidebar.tsx b/packages/apollo-vertex/registry/shell/shell-sidebar.tsx index ce76ee76d1..545b64f6dc 100644 --- a/packages/apollo-vertex/registry/shell/shell-sidebar.tsx +++ b/packages/apollo-vertex/registry/shell/shell-sidebar.tsx @@ -45,8 +45,11 @@ const activeNavClass = "text-sidebar-foreground/85 hover:text-sidebar-foreground data-[active=true]:text-primary-700 dark:data-[active=true]:text-primary-400 data-[active=true]:font-semibold data-[active=true]:bg-primary-100/40 dark:data-[active=true]:bg-primary-900/30"; const navButtonClass = `font-medium ${activeNavClass}`; const subButtonClass = activeNavClass; -const prefixActiveClass = - "text-primary-700 dark:text-primary-400 font-semibold"; +const prefixActiveClass = [ + "font-semibold text-primary-700 dark:text-primary-400", + "hover:text-primary-700 dark:hover:text-primary-400", + "active:text-primary-700 dark:active:text-primary-400", +].join(" "); const currentPillClass = "bg-primary-100/40 dark:bg-primary-900/30"; interface ShellSidebarProps { @@ -79,6 +82,7 @@ export const ShellSidebar = ({ ))} From 7282e091362aa2fd541ea9b1f9c7cab5e2998150 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 16 Oct 2026 13:07:36 +0000 Subject: [PATCH 3/5] fix(apollo-vertex): pick one minimal nav item Highlight only the longest matching minimal-header path, so a section does not also select its landing page. Name each group disclosure button with the translated group label. --- .../content/docs/patterns/shell.mdx | 2 +- packages/apollo-vertex/locales/en.json | 2 ++ .../registry/shell/shell-minimal-nav-item.tsx | 12 +++---- .../registry/shell/shell-nav-active.test.tsx | 33 +++++++++++++++++++ .../registry/shell/shell-nav-active.ts | 14 ++++++++ .../registry/shell/shell-sidebar.tsx | 18 +++++++--- 6 files changed, 67 insertions(+), 14 deletions(-) diff --git a/apps/apollo-vertex/content/docs/patterns/shell.mdx b/apps/apollo-vertex/content/docs/patterns/shell.mdx index 66dc24faf5..4c642cd365 100644 --- a/apps/apollo-vertex/content/docs/patterns/shell.mdx +++ b/apps/apollo-vertex/content/docs/patterns/shell.mdx @@ -54,7 +54,7 @@ function App() { ## Nested navigation -A group can set `to` and `subItems`. The row links to `to`, or to `path` when `to` is omitted, and stays in the active color for `path` and every route under it, including on hover and press. The matching child gets the current background. While the sidebar is collapsed, that background stays off the parent: the parent icon keeps the active color. The minimal header uses the same path match. `to` is only the link target. +A group can set `to` and `subItems`. The row links to `to`, or to `path` when `to` is omitted, and stays in the active color for `path` and every route under it, including on hover and press. The matching child gets the current background. While the sidebar is collapsed, that background stays off the parent: the parent icon keeps the active color. The minimal header highlights only the most specific matching path, so a landing page is not selected together with a section under it. A nested page still highlights that section. `to` is only the link target. Labels are translation keys. A key in the app's default catalog is used first, including keys that are not in Apollo Vertex. Vertex keys such as `dashboard` still resolve when the app catalog does not define them. diff --git a/packages/apollo-vertex/locales/en.json b/packages/apollo-vertex/locales/en.json index 13e05ef3fd..7578e8f4bd 100644 --- a/packages/apollo-vertex/locales/en.json +++ b/packages/apollo-vertex/locales/en.json @@ -186,6 +186,8 @@ "more_pages": "More pages", "multi_line_too_few_metrics": "Multi-line needs two distinct metrics, but the request resolved to fewer than two.", "my_template": "My Template", + "nav_collapse": "Collapse {{label}}", + "nav_expand": "Expand {{label}}", "need_help": "Need help? Contact your administrator", "new_conversation": "New conversation", "new_conversation_confirm_description": "This will clear all messages and cannot be undone.", diff --git a/packages/apollo-vertex/registry/shell/shell-minimal-nav-item.tsx b/packages/apollo-vertex/registry/shell/shell-minimal-nav-item.tsx index 893fdadc29..6f2a099ea2 100644 --- a/packages/apollo-vertex/registry/shell/shell-minimal-nav-item.tsx +++ b/packages/apollo-vertex/registry/shell/shell-minimal-nav-item.tsx @@ -1,25 +1,21 @@ -import { Link, useLocation } from "@tanstack/react-router"; +import { Link } from "@tanstack/react-router"; import { cn } from "@/lib/utils"; -import { navPathIsActive } from "./shell-nav-active"; import { Text } from "./shell-text"; import type { ShellNavLabel } from "./shell-translation-key"; interface MinimalNavItemProps { to: string; - path: string; + active: boolean; label: ShellNavLabel; } -export const MinimalNavItem = ({ to, path, label }: MinimalNavItemProps) => { - const { pathname } = useLocation(); - const isActive = navPathIsActive(pathname, path); - +export const MinimalNavItem = ({ to, active, label }: MinimalNavItemProps) => { return ( { }); }); +describe("mostSpecificActivePath", () => { + const header = [ + "/preview/shell-minimal", + "/preview/shell-minimal/projects", + "/preview/shell-minimal/analytics", + ]; + + it("selects the section and not the landing page", () => { + expect( + mostSpecificActivePath("/preview/shell-minimal/projects", header), + ).toBe("/preview/shell-minimal/projects"); + }); + + it("keeps prefix highlighting for a nested detail route", () => { + expect( + mostSpecificActivePath("/preview/shell-minimal/projects/42", header), + ).toBe("/preview/shell-minimal/projects"); + }); + + it("selects the landing page on its own route", () => { + expect(mostSpecificActivePath("/preview/shell-minimal", header)).toBe( + "/preview/shell-minimal", + ); + }); + + it("highlights a group when no more specific item exists", () => { + expect(mostSpecificActivePath("/rules/exceptions", ["/rules"])).toBe( + "/rules", + ); + }); +}); + describe("navItemState", () => { it("links a group to its default child and keeps the prefix active", () => { expect( diff --git a/packages/apollo-vertex/registry/shell/shell-nav-active.ts b/packages/apollo-vertex/registry/shell/shell-nav-active.ts index 040c1f1f61..3017dd6067 100644 --- a/packages/apollo-vertex/registry/shell/shell-nav-active.ts +++ b/packages/apollo-vertex/registry/shell/shell-nav-active.ts @@ -17,6 +17,20 @@ export function navItemIsCurrent( ); } +// Among prefix matches, the longest path wins. A detail route highlights its +// section without also selecting a shorter landing-page link. +export function mostSpecificActivePath( + pathname: string, + paths: readonly string[], +): string | null { + let match: string | null = null; + for (const path of paths) { + if (!navPathIsActive(pathname, path)) continue; + if (match === null || path.length > match.length) match = path; + } + return match; +} + export function navItemState( pathname: string, item: { path: string; to?: string; childPaths?: readonly string[] }, diff --git a/packages/apollo-vertex/registry/shell/shell-sidebar.tsx b/packages/apollo-vertex/registry/shell/shell-sidebar.tsx index 545b64f6dc..58e1e3f931 100644 --- a/packages/apollo-vertex/registry/shell/shell-sidebar.tsx +++ b/packages/apollo-vertex/registry/shell/shell-sidebar.tsx @@ -35,7 +35,7 @@ import { import { Company } from "./shell-company"; import { MinimalCompany } from "./shell-minimal-company"; import { MinimalNavItem } from "./shell-minimal-nav-item"; -import { navItemState } from "./shell-nav-active"; +import { mostSpecificActivePath, navItemState } from "./shell-nav-active"; import { navCatalogFromHook, readNavLabel } from "./shell-nav-label"; import { Text } from "./shell-text"; import type { ShellNavLabel } from "./shell-translation-key"; @@ -67,7 +67,14 @@ export const ShellSidebar = ({ companyLogo, navItems, }: ShellSidebarProps) => { + const { pathname } = useLocation(); + if (variant === "minimal") { + const activePath = mostSpecificActivePath( + pathname, + navItems.map((item) => item.path), + ); + return (
))} @@ -263,9 +270,10 @@ function SidebarNav({