"use client" /** * Shared building blocks for hub secondary navs (Library, Learning activities, …). * Every `*-secondary-nav.tsx` MUST use these so compact-rail parity cannot drift. * * Checklist: `docs/exxat-ds/library-nav-ia-pattern.md` § Hub secondary-nav parity. */ import * as React from "react" import { Link } from "react-router-dom" import { Button } from "@/components/ui/button" import { SidebarNavLabel } from "@/components/ui/sidebar-nav-label" import { Tip } from "@/components/ui/tip" import { useSidebar } from "@/components/ui/sidebar" import { cn } from "@/lib/utils" import { useSecondaryPanel } from "@/components/sidebar/secondary-panel" import { useIsMobile } from "@/hooks/use-mobile" import { useSidebarReflowZoom } from "@/hooks/use-sidebar-reflow-zoom" /** Desktop pinned-rail vs flyout — same logic as `library-secondary-nav.tsx`. */ export function useSecondaryHubNavChrome() { const { openPanel, secondaryPanelCompact } = useSecondaryPanel() const { dismissNavFlyout } = useSidebar() const isMobile = useIsMobile() const reflowZoom = useSidebarReflowZoom() const navFlyout = isMobile || reflowZoom const showCompactRail = secondaryPanelCompact && !navFlyout return { openPanel, dismissNavFlyout, navFlyout, showCompactRail, secondaryPanelCompact, } } /** Expanded secondary row — icon + wrapping label. */ export function SecondaryHubNavRow({ href, active, iconClass, label, onClick, }: { href: string active: boolean iconClass: string label: string onClick?: () => void }) { const { dismissNavFlyout } = useSidebar() return (
  • { onClick?.() dismissNavFlyout() }} aria-current={active ? "page" : undefined} className={cn( "flex h-8 w-full min-w-0 items-center gap-2 rounded-md px-2 text-left text-sm transition-colors", "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset", active ? "bg-sidebar-accent font-medium text-sidebar-accent-foreground ring-1 ring-inset ring-sidebar-border/80" : "text-sidebar-foreground hover:bg-sidebar-accent/50", )} > {label}
  • ) } /** Compact icon-rail row — `size-9` hit target (primary sidebar collapsed parity). */ export function SecondaryHubIconNavRow({ href, active, iconClass, label, onClick, }: { href: string active: boolean iconClass: string label: string onClick?: () => void }) { const { dismissNavFlyout } = useSidebar() return (
  • { onClick?.() dismissNavFlyout() }} aria-current={active ? "page" : undefined} className={cn( "flex size-9 shrink-0 items-center justify-center rounded-md transition-colors", "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset", active ? "bg-sidebar-accent text-sidebar-accent-foreground" : "text-sidebar-foreground hover:bg-sidebar-accent/50", )} >
  • ) } /** Icon-only shell when primary sidebar expands (`secondaryPanelCompact`). */ export function SecondaryHubNavCompactShell({ ariaLabel, panelId, footer, children, }: { ariaLabel: string /** `PANELS` registry id — expand chevron calls `openPanel(panelId)`. */ panelId: string footer?: React.ReactNode children: React.ReactNode }) { const { openPanel } = useSecondaryPanel() return (