"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 (
)
}
/** Section eyebrow + optional action in expanded secondary nav lists. */
export function SecondaryHubNavSectionHeader({
label,
action,
}: {
label: string
action?: React.ReactNode
}) {
return (
{label}
{action}
)
}