@tradalab/lyra 3.6.0 → 3.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -368,7 +368,9 @@ interface NavUserProps {
368
368
  * `TooltipProvider` (the avatar button's collapsed-mode tooltip requires one —
369
369
  * app root providers normally mount it). The theme submenu needs a
370
370
  * `next-themes` provider mounted (it no-ops without one); pass
371
- * `showTheme={false}` for apps with a fixed theme.
371
+ * `showTheme={false}` for apps with a fixed theme. It drops the three theme rows and nothing else:
372
+ * the Appearance submenu stays, because accent and density are per-device choices that a fixed
373
+ * theme has no opinion about.
372
374
  *
373
375
  * @example
374
376
  * import { NavUser } from "@tradalab/lyra/shell"
@@ -372,7 +372,7 @@ function NavUser({
372
372
  user?.image ? /* @__PURE__ */ jsx(AvatarImage, { className: "rounded-none", src: user.image, alt: name }) : null,
373
373
  /* @__PURE__ */ jsx(AvatarFallback, { className: "rounded-none", children: user ? initialsOf(name) : /* @__PURE__ */ jsx(CircleUser, { className: "size-4" }) })
374
374
  ] });
375
- const appearanceSubmenu = showTheme ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
375
+ const appearanceSubmenu = /* @__PURE__ */ jsxs(Fragment$1, { children: [
376
376
  /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
377
377
  /* @__PURE__ */ jsxs(DropdownMenuSub, { children: [
378
378
  /* @__PURE__ */ jsxs(DropdownMenuSubTrigger, { children: [
@@ -380,11 +380,13 @@ function NavUser({
380
380
  t.appearance
381
381
  ] }),
382
382
  /* @__PURE__ */ jsxs(DropdownMenuSubContent, { className: "w-56", children: [
383
- /* @__PURE__ */ jsx(DropdownMenuRadioGroup, { value: theme ?? "system", onValueChange: setTheme, children: THEMES.map(({ value, key, icon: Icon }) => /* @__PURE__ */ jsxs(DropdownMenuRadioItem, { value, children: [
384
- /* @__PURE__ */ jsx(Icon, { className: "mr-2 size-4" }),
385
- t[key]
386
- ] }, value)) }),
387
- /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
383
+ showTheme ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
384
+ /* @__PURE__ */ jsx(DropdownMenuRadioGroup, { value: theme ?? "system", onValueChange: setTheme, children: THEMES.map(({ value, key, icon: Icon }) => /* @__PURE__ */ jsxs(DropdownMenuRadioItem, { value, children: [
385
+ /* @__PURE__ */ jsx(Icon, { className: "mr-2 size-4" }),
386
+ t[key]
387
+ ] }, value)) }),
388
+ /* @__PURE__ */ jsx(DropdownMenuSeparator, {})
389
+ ] }) : null,
388
390
  /* @__PURE__ */ jsx(DropdownMenuLabel, { className: "text-muted-foreground text-xs font-normal", children: t.accent }),
389
391
  /* @__PURE__ */ jsx("div", { className: "px-2 pb-1.5", children: /* @__PURE__ */ jsx(
390
392
  PlatePicker,
@@ -404,7 +406,7 @@ function NavUser({
404
406
  ] })
405
407
  ] })
406
408
  ] })
407
- ] }) : null;
409
+ ] });
408
410
  return /* @__PURE__ */ jsx(SidebarMenu, { children: /* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsxs(DropdownMenu, { children: [
409
411
  /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
410
412
  SidebarMenuButton,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/shell/app-shell.tsx","../../src/shell/stacked-sidebar.tsx","../../src/shell/page-shell.tsx","../../src/shell/page-header.tsx","../../src/shell/nav-group.tsx","../../src/shell/use-active-route.ts","../../src/shell/nav-item.tsx","../../src/shell/nav-section.tsx","../../src/shell/app-glyph.tsx","../../src/shell/nav-brand.tsx","../../src/shell/app-switcher.tsx","../../src/shell/nav-user.tsx","../../src/shell/tab-bar.tsx","../../src/shell/breadcrumbs.tsx","../../src/shell/app-grid.tsx"],"names":["jsx","jsxs","Link","Fragment","ChevronsUpDown","useRef","useEffect"],"mappings":"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","file":"index.js","sourcesContent":["\"use client\"\n\nimport type { ComponentProps, ReactNode } from \"react\"\nimport {\n SidebarInset,\n SidebarProvider,\n} from \"~/ui/primitives/sidebar.js\"\nimport { cn } from \"~/utils/cn.js\"\n\nexport interface AppShellProps\n extends Omit<ComponentProps<typeof SidebarProvider>, \"children\"> {\n children: ReactNode\n}\n\n/**\n * App shell composition root. Wires `SidebarProvider`. Pair with\n * `<Sidebar>` from `@tradalab/lyra/ui` and `<AppShellMain>` for the\n * right-hand pane.\n *\n * @example\n * import { AppShell, AppShellMain, AppShellHeader, AppShellBody, NavGroup, NavItem, Breadcrumbs } from \"@tradalab/lyra/shell\"\n * import { Sidebar, SidebarHeader, SidebarContent, SidebarFooter, SidebarTrigger } from \"@tradalab/lyra/ui\"\n *\n * <AppShell>\n * <Sidebar collapsible=\"icon\">\n * <SidebarHeader>{logo}</SidebarHeader>\n * <SidebarContent>\n * <NavGroup label=\"Primitives\">{...}</NavGroup>\n * </SidebarContent>\n * <SidebarFooter>{user}</SidebarFooter>\n * </Sidebar>\n * <AppShellMain>\n * <AppShellHeader><SidebarTrigger /><Breadcrumbs /></AppShellHeader>\n * <AppShellBody>{children}</AppShellBody>\n * </AppShellMain>\n * </AppShell>\n */\nexport function AppShell({ children, ...rest }: AppShellProps) {\n return <SidebarProvider {...rest}>{children}</SidebarProvider>\n}\n\n/** Right-hand pane container. Alias for `SidebarInset`. */\nexport const AppShellMain = SidebarInset\n\n/** Top bar inside `<AppShellMain>`. Standardizes h-12 + border-b + flex row. */\nexport function AppShellHeader({\n className,\n children,\n ...rest\n}: ComponentProps<\"header\">) {\n return (\n <header\n className={cn(\n \"flex h-12 shrink-0 items-center gap-2 border-b px-3\",\n className,\n )}\n {...rest}\n >\n {children}\n </header>\n )\n}\n\n/** Scroll body inside `<AppShellMain>`. Sibling of `<AppShellHeader>`. */\nexport function AppShellBody({\n className,\n children,\n ...rest\n}: ComponentProps<\"div\">) {\n return (\n <div className={cn(\"flex-1 overflow-auto\", className)} {...rest}>\n {children}\n </div>\n )\n}\n","\"use client\"\n\nimport type { ComponentProps } from \"react\"\nimport { Sidebar } from \"~/ui/primitives/sidebar.js\"\nimport { cn } from \"~/utils/cn.js\"\n\n/**\n * Dual / stacked sidebar wrapper — an icon rail plus one or more content\n * panels rendered side-by-side inside a single `<Sidebar>`. This is the\n * shadcn \"sidebar-07\" pattern.\n *\n * The `*:data-[sidebar=sidebar]:flex-row` trick lays the inner\n * `[data-sidebar=sidebar]` wrappers of the nested {@link SidebarDock} /\n * {@link SidebarPanel} columns out horizontally. `variant`/`collapsible`\n * default to `\"sidebar\"`/`\"icon\"` but can be overridden.\n *\n * @example\n * import { AppShell, AppShellMain, StackedSidebar, SidebarDock, SidebarPanel } from \"@tradalab/lyra/shell\"\n * import { SidebarHeader, SidebarContent, SidebarMenu, SidebarMenuItem, SidebarMenuButton } from \"@tradalab/lyra/ui\"\n *\n * <AppShell>\n * <StackedSidebar>\n * <SidebarDock>\n * <SidebarHeader>{logo}</SidebarHeader>\n * <SidebarContent>{iconButtons}</SidebarContent>\n * </SidebarDock>\n * {tab === \"files\" ? <SidebarPanel>{tree}</SidebarPanel> : null}\n * </StackedSidebar>\n * <AppShellMain>{children}</AppShellMain>\n * </AppShell>\n */\nexport function StackedSidebar({\n className,\n ...rest\n}: ComponentProps<typeof Sidebar>) {\n return (\n <Sidebar\n variant=\"sidebar\"\n collapsible=\"icon\"\n {...rest}\n className={cn(\n \"overflow-hidden *:data-[sidebar=sidebar]:flex-row\",\n className,\n )}\n />\n )\n}\n\n/**\n * Narrow icon-rail column inside a {@link StackedSidebar}. Fixed to the icon\n * width, never collapses on its own (the parent `StackedSidebar` owns the\n * icon-mode toggle).\n *\n * Named `SidebarDock` — not `SidebarRail` — because `SidebarRail` already\n * exists in `@tradalab/lyra/ui` as shadcn's drag-to-resize edge handle.\n */\nexport function SidebarDock({\n className,\n ...rest\n}: ComponentProps<typeof Sidebar>) {\n return (\n <Sidebar\n {...rest}\n collapsible=\"none\"\n className={cn(\n \"w-[calc(var(--sidebar-width-icon)+1px)]! overflow-hidden border-r\",\n className,\n )}\n />\n )\n}\n\n/**\n * Wider content column inside a {@link StackedSidebar} (tree, list, …).\n * Fills remaining width; render conditionally to show/hide per app state.\n */\nexport function SidebarPanel({\n className,\n ...rest\n}: ComponentProps<typeof Sidebar>) {\n return (\n <Sidebar\n {...rest}\n collapsible=\"none\"\n className={cn(\"flex-1 overflow-hidden\", className)}\n />\n )\n}\n","import type { ReactNode } from \"react\"\nimport { cn } from \"~/utils/cn.js\"\n\n/**\n * Width is decided by what is inside the page, not one page at a time.\n *\n * Left-aligned, not centred. `mx-auto` floats a form in the middle of a wide monitor with the nav\n * stranded on the far left, and the two stop reading as one page. Centring is what `Container`\n * offers, and it is right there: a standalone page owns the whole viewport. This one is the body of\n * a page inside `AppShellBody`, which already holds the scroll region and the gutters, so adding\n * either here doubles it.\n */\nconst MEASURE = {\n /**\n * Tables, trees, calendars and charts. A column nobody can read costs more than a long line: a\n * self-imposed cap is what makes a report collapse five facts into one cell while a third of the\n * monitor goes to margin.\n */\n table: \"max-w-none\",\n /** Prose, detail panes, dashboards. */\n read: \"max-w-5xl\",\n /** Stops where a line stops being comfortable to read, which is what a form field is. */\n form: \"max-w-3xl\",\n} as const\n\nexport type PageMeasure = keyof typeof MEASURE\n\nexport interface PageShellProps {\n /** Default `\"read\"`. */\n measure?: PageMeasure\n className?: string\n children?: ReactNode\n}\n\nexport function PageShell({ measure = \"read\", className, children }: PageShellProps) {\n return (\n <div className={cn(\"flex w-full flex-col gap-6\", MEASURE[measure], className)}>{children}</div>\n )\n}\n","import type { ReactNode } from \"react\"\nimport { cn } from \"~/utils/cn.js\"\n\n/**\n * Title, one line of context, and the actions that belong to the whole screen.\n *\n * Five apps carried this byte for byte and two more carried a variant, which is how the heading\n * level drifted: the same product rendered its page titles at three sizes depending on which app\n * you were standing in.\n *\n * The description is not decoration on a screen that names something a reader cannot infer (a pin,\n * a derived day, a rule pack); it is the sentence that stops them guessing. Leave it out rather\n * than restate the title.\n */\nexport interface PageHeaderProps {\n title: ReactNode\n description?: ReactNode\n actions?: ReactNode\n className?: string\n}\n\nexport function PageHeader({ title, description, actions, className }: PageHeaderProps) {\n return (\n <div\n className={cn(\n \"flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between\",\n className,\n )}\n >\n <div className=\"space-y-1\">\n <h1 className=\"text-2xl font-semibold tracking-tight\">{title}</h1>\n {description ? <p className=\"text-muted-foreground text-sm\">{description}</p> : null}\n </div>\n {actions ? <div className=\"flex shrink-0 items-center gap-2\">{actions}</div> : null}\n </div>\n )\n}\n","\"use client\"\n\nimport type { ReactNode } from \"react\"\nimport {\n SidebarGroup,\n SidebarGroupContent,\n SidebarGroupLabel,\n SidebarMenu,\n} from \"~/ui/primitives/sidebar.js\"\n\nexport interface NavGroupProps {\n label?: ReactNode\n children: ReactNode\n}\n\nexport function NavGroup({ label, children }: NavGroupProps) {\n return (\n <SidebarGroup>\n {label != null ? <SidebarGroupLabel>{label}</SidebarGroupLabel> : null}\n <SidebarGroupContent>\n <SidebarMenu>{children}</SidebarMenu>\n </SidebarGroupContent>\n </SidebarGroup>\n )\n}\n","\"use client\"\n\nimport { usePathname } from \"next/navigation\"\n\nexport type MatchMode = \"exact\" | \"prefix\"\n\nexport interface UseActiveRoute {\n pathname: string\n isActive: (href: string, mode?: MatchMode) => boolean\n}\n\n/**\n * App-Router pathname helper. Default `mode` is `\"prefix\"`.\n *\n * Root (`/`) is always exact-matched regardless of `mode` — otherwise the\n * root nav item would light up on every nested page.\n */\nexport function useActiveRoute(): UseActiveRoute {\n const pathname = usePathname() ?? \"/\"\n return {\n pathname,\n isActive: (href, mode = \"prefix\") => {\n if (mode === \"exact\" || href === \"/\") return pathname === href\n return pathname === href || pathname.startsWith(`${href}/`)\n },\n }\n}\n","\"use client\"\n\nimport Link from \"next/link\"\nimport { ArrowUpRight } from \"lucide-react\"\nimport type { ReactNode } from \"react\"\nimport {\n SidebarMenuButton,\n SidebarMenuItem,\n} from \"~/ui/primitives/sidebar.js\"\nimport { useActiveRoute, type MatchMode } from \"./use-active-route.js\"\n\nexport type NavIcon = ReactNode\n\n/** Absolute URLs and mail/tel links leave the app — they can't go through next/link. */\nconst looksExternal = (href: string) =>\n /^([a-z][a-z0-9+.-]*:)?\\/\\//i.test(href) || /^(mailto|tel):/i.test(href)\n\nexport interface NavItemProps {\n href: string\n label: string\n /** Pre-rendered icon element, e.g. `<Globe />`. Element form so the prop is serializable across the RSC boundary. */\n icon?: NavIcon\n /** Sidebar tooltip; visible in collapsed icon mode. */\n tooltip?: string\n /** Default `\"prefix\"` — matches `href` and any nested path. */\n matchMode?: MatchMode\n /**\n * Render a new-tab anchor instead of a `next/link`, and never mark the row\n * active. Defaults to auto-detection: absolute (`https://…`), protocol-\n * relative, `mailto:` and `tel:` hrefs count as external.\n */\n external?: boolean\n}\n\n/** Sidebar nav row. Must be rendered inside a {@link NavGroup}. */\nexport function NavItem({\n href,\n label,\n icon,\n tooltip,\n matchMode = \"prefix\",\n external,\n}: NavItemProps) {\n const { isActive } = useActiveRoute()\n const isExternal = external ?? looksExternal(href)\n return (\n <SidebarMenuItem>\n <SidebarMenuButton\n asChild\n isActive={!isExternal && isActive(href, matchMode)}\n tooltip={tooltip}\n >\n {isExternal ? (\n <a href={href} target=\"_blank\" rel=\"noopener noreferrer\">\n {icon}\n <span>{label}</span>\n <ArrowUpRight\n aria-hidden\n className=\"ms-auto size-3.5! shrink-0 opacity-50 group-data-[collapsible=icon]:hidden\"\n />\n </a>\n ) : (\n <Link href={href}>\n {icon}\n <span>{label}</span>\n </Link>\n )}\n </SidebarMenuButton>\n </SidebarMenuItem>\n )\n}\n","\"use client\"\n\nimport type { ReactNode } from \"react\"\nimport { NavGroup } from \"./nav-group.js\"\nimport { NavItem, type NavItemProps } from \"./nav-item.js\"\n\nexport interface NavSectionProps {\n /** Optional group heading. */\n label?: ReactNode\n /** Nav rows — each rendered as a {@link NavItem}. */\n items: NavItemProps[]\n}\n\n/**\n * Config-driven convenience over {@link NavGroup} + {@link NavItem}: pass a\n * data array instead of hand-mapping JSX. For dynamic or conditional rows,\n * compose `NavGroup`/`NavItem` directly.\n *\n * @example\n * const NAV = [\n * { href: \"/files\", label: \"Files\", icon: <Folder /> },\n * { href: \"/trash\", label: \"Trash\", icon: <Trash2 /> },\n * ]\n * <NavSection label=\"Workspace\" items={NAV} />\n */\nexport function NavSection({ label, items }: NavSectionProps) {\n return (\n <NavGroup label={label}>\n {items.map((item) => (\n <NavItem key={item.href} {...item} />\n ))}\n </NavGroup>\n )\n}\n","\"use client\"\n\n// The single renderer for a registered app's mark, shared by the app switcher, the launcher and\n// the Apps admin so one app looks identical everywhere. `icon` is the URL of the image the app\n// ships; anything else, and an image that fails to load, falls back to a name monogram.\n//\n// Lucide icon NAMES are deliberately no longer accepted: resolving any name at runtime meant\n// importing `lucide-react/dynamic`, one `import()` per icon, and Turbopack turned that into ~2000\n// chunk groups on every page of every app (doc/platform/PITFALLS.md).\n\nimport { useEffect, useRef, useState } from \"react\"\n\nimport { cn } from \"~/utils/cn.js\"\n\n// Absolute only: a relative path would resolve against whichever app is showing the launcher.\nexport function isIconUrl(s: string): boolean {\n return /^https?:\\/\\//i.test(s)\n}\n\n// One leading slash, never two. `//cdn.test/x.png` also starts with \"/\" and is a PROTOCOL-RELATIVE\n// URL pointing at another host - the one thing `ownOrigin` exists to exclude. A test that asserted\n// this was passing for the wrong reason until it stubbed the image state, and a mutation run is what\n// said so.\nfunction isOwnPath(s: string): boolean {\n return /^\\/(?!\\/)/.test(s)\n}\n\nexport interface AppGlyphProps {\n /** Absolute http(s) image URL; any other value draws the monogram. */\n icon?: string\n /** App name, drawn as the monogram when there is no image. */\n name?: string\n className?: string\n /**\n * Also accept a ROOT-RELATIVE `icon` (`/icon.svg`).\n *\n * Off by default, and that default is the point: a registry mark belongs to some OTHER app, so a\n * relative path would resolve against whichever app is drawing the launcher and quietly show the\n * wrong mark. app-glyph.test.tsx pins that.\n *\n * On for an app drawing its OWN mark - the sidebar header. There same-origin is the whole\n * intention: the file sits in this app's `public/`, so an absolute URL would add a cross-origin\n * round trip to fetch something already here, and one blip would swap the header for a letter.\n */\n ownOrigin?: boolean\n}\n\n/** An app's mark: its image when `icon` is a URL that loads, else a name monogram. */\nexport function AppGlyph({ icon, name, className, ownOrigin }: AppGlyphProps) {\n const box = \"flex size-9 shrink-0 items-center justify-center rounded-none\"\n const src = icon && (isIconUrl(icon) || (ownOrigin === true && isOwnPath(icon))) ? icon : \"\"\n const [failedSrc, setFailedSrc] = useState(\"\")\n const seen = useRef(src)\n const ref = useRef<HTMLImageElement>(null)\n\n // A new URL deserves its own attempt, including one that failed earlier: the admin form edits\n // this field a keystroke at a time, and a URL typed past a broken prefix would stay dead.\n if (seen.current !== src) {\n seen.current = src\n if (failedSrc) setFailedSrc(\"\")\n }\n\n // An image that fails before hydration never fires onError, so the mount check is what\n // catches a server-rendered tile whose host is down. It reads getAttribute, not `img.src`:\n // that property normalizes the URL (an upper-case scheme comes back lower-cased), and the\n // comparison below would then never match, leaving a broken image on screen.\n useEffect(() => {\n const img = ref.current\n if (img?.complete && img.naturalWidth === 0) setFailedSrc(img.getAttribute(\"src\") ?? \"\")\n }, [src])\n\n if (src && failedSrc !== src) {\n return (\n <img\n ref={ref}\n src={src}\n alt=\"\"\n aria-hidden\n className={cn(box, \"object-contain\", className)}\n onError={() => setFailedSrc(src)}\n />\n )\n }\n\n return (\n <span\n aria-hidden\n className={cn(box, \"bg-primary/10 text-sm font-semibold text-primary\", className)}\n >\n {(name || \"?\").charAt(0).toUpperCase()}\n </span>\n )\n}\n","\"use client\"\n\nimport { Fragment, type ReactNode } from \"react\"\nimport { ChevronsUpDown } from \"lucide-react\"\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from \"~/ui/primitives/dropdown-menu.js\"\nimport { AppGlyph } from \"./app-glyph.js\"\nimport {\n SidebarMenu,\n SidebarMenuButton,\n SidebarMenuItem,\n useSidebar,\n} from \"~/ui/primitives/sidebar.js\"\nimport { cn } from \"~/utils/cn.js\"\n\n/** An application shown in the {@link NavBrand} header / switcher. */\nexport interface NavBrandApp {\n /** Display name. */\n name: string\n /**\n * The app's mark, in one of three forms:\n *\n * - a ROOT-RELATIVE path (`\"/icon.svg\"`) - THIS app's own mark. Same origin by construction,\n * which is why the header uses it: the file is already in this app's `public/`, so an absolute\n * URL would add a cross-origin round trip to fetch something that is right here.\n * - an ABSOLUTE http(s) URL - another app's mark, as the registry stores it. Drawn by\n * {@link AppGlyph}, so a host that is down degrades to a name monogram rather than a broken\n * image.\n * - a pre-rendered element, e.g. `<Boxes />`. Element form (not a component reference) so the\n * prop stays serializable across the RSC boundary - same convention as {@link NavItem}'s\n * `icon`. Drawn on the brand-colour plate, inheriting its foreground.\n *\n * Either string form gets NO plate: the generated marks are opaque squircles, and a rounded mark\n * on a square colour tile shows four corners of the wrong colour behind it. Nor does the monogram\n * such a mark degrades to, which brings its own tint.\n */\n icon: ReactNode\n /** Navigation target. Usually an absolute cross-app URL (full navigation). */\n href: string\n /** Optional secondary line under the name (tagline / domain). */\n description?: string\n}\n\n/** A labeled section of apps in the {@link NavBrand} switcher. */\nexport interface NavBrandGroup {\n /** Stable React key. */\n key: string\n /** Section heading. Omit for an unlabeled section (e.g. ungrouped apps). */\n label?: string\n apps: NavBrandApp[]\n}\n\nexport interface NavBrandProps {\n /** The current app, rendered in the sidebar header. */\n app: NavBrandApp\n /**\n * Other apps to switch to (flat, single list). When omitted or empty - and no\n * `groups` - the header renders as a plain link to `app.href`.\n */\n apps?: NavBrandApp[]\n /**\n * Grouped apps - rendered as labeled sections separated by dividers. Takes\n * precedence over `apps`. The caller owns the grouping + ordering.\n */\n groups?: NavBrandGroup[]\n /** Heading above a flat `apps` list. Default `\"Applications\"`. Ignored for `groups`. */\n appsLabel?: string\n /** Extra menu content appended below the app list (e.g. an \"Add app\" row). */\n children?: ReactNode\n}\n\n/** Default English heading for the switcher's app list - override for i18n. */\nexport const defaultNavBrandAppsLabel = \"Applications\"\n\n/**\n * One renderer for every mark this component draws, so the header and the switcher rows cannot\n * drift apart. They did: the rows went through {@link AppGlyph} (image, monogram on failure) while\n * the header rendered its element raw, so opening the switcher showed the current app as a line\n * icon among everyone else's brand images.\n *\n * `own` says whose mark it is, and only that decides whether a root-relative path is honoured.\n */\nfunction Mark({ app, own, className }: { app: NavBrandApp; own?: boolean; className?: string }) {\n if (typeof app.icon !== \"string\") return <>{app.icon}</>\n return <AppGlyph icon={app.icon} name={app.name} className={className} ownOrigin={own} />\n}\n\n/**\n * Whether this mark needs chrome around it - the header's colour plate, a row's hairline box.\n *\n * Decided from the DATA, not from a `:has(img)` test. A string mark arrives with its own surface\n * either way: {@link AppGlyph} puts nothing behind an image, and `bg-primary/10` behind the monogram\n * it degrades to. Only the image branch contains an `<img>`, so a CSS test got that one right and\n * left the chrome around the fallback - in the header that meant the brand plate standing under a\n * letter painted in `text-primary`, a token for another surface, over two stacked backgrounds.\n * `typeof` answers before either branch renders.\n */\nfunction needsChrome(app: NavBrandApp): boolean {\n return typeof app.icon !== \"string\"\n}\n\n/** Icon tile + name/description shown inside the header button. */\nfunction BrandContent({ app }: { app: NavBrandApp }) {\n return (\n <>\n <div\n className={cn(\n \"flex aspect-square size-8 items-center justify-center rounded-none\",\n needsChrome(app) && \"bg-sidebar-primary text-sidebar-primary-foreground [&_svg]:size-4\",\n )}\n >\n <Mark app={app} own className=\"size-full text-xs\" />\n </div>\n <div className=\"grid flex-1 text-left text-sm leading-tight\">\n <span className=\"truncate font-semibold\">{app.name}</span>\n {app.description ? (\n <span className=\"text-muted-foreground truncate text-xs\">\n {app.description}\n </span>\n ) : null}\n </div>\n </>\n )\n}\n\n/** A single switch-to-app row (full-navigation link). */\nfunction AppRow({ app }: { app: NavBrandApp }) {\n return (\n <DropdownMenuItem asChild className=\"gap-2 p-2\">\n <a href={app.href}>\n <div\n className={cn(\n \"flex size-6 items-center justify-center rounded-none\",\n needsChrome(app) && \"border [&_svg]:size-3.5\",\n )}\n >\n <Mark app={app} className=\"size-full text-[10px]\" />\n </div>\n <span className=\"truncate\">{app.name}</span>\n </a>\n </DropdownMenuItem>\n )\n}\n\n/**\n * Sidebar-header brand block with an optional app switcher - the standardized\n * `SidebarHeader` content for app shells (pairs with {@link NavUser} in the\n * footer). Presentational: the app owns the config, icons, and labels.\n *\n * With `groups` (or `apps`), the header is a dropdown trigger listing the other\n * apps as links; `groups` renders labeled sections, `apps` a single flat list.\n * Without either, the header is a plain link to `app.href`. Cross-app links use\n * a plain `<a>` so switching does a full navigation.\n *\n * Must render inside an `AppShell`/`SidebarProvider`, and below a\n * `TooltipProvider` (the collapsed-mode tooltip needs one).\n *\n * @example\n * <NavBrand\n * app={{ name: \"Admin\", description: \"Platform console\", icon: <ShieldCheck />, href: \"/\" }}\n * groups={[\n * { key: \"workspace\", label: \"Workspace\", apps: [{ name: \"Drive\", icon: <HardDrive />, href: \"https://drive.tradalab.com\" }] },\n * ]}\n * />\n */\nexport function NavBrand({\n app,\n apps,\n groups,\n appsLabel = defaultNavBrandAppsLabel,\n children,\n}: NavBrandProps) {\n const { isMobile } = useSidebar()\n const groupTotal = groups?.reduce((n, g) => n + g.apps.length, 0) ?? 0\n const hasSwitcher = (apps?.length ?? 0) > 0 || groupTotal > 0 || children != null\n\n return (\n <SidebarMenu>\n <SidebarMenuItem>\n {hasSwitcher ? (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <SidebarMenuButton\n size=\"lg\"\n tooltip={app.name}\n className=\"data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground\"\n >\n <BrandContent app={app} />\n <ChevronsUpDown className=\"ml-auto size-4\" />\n </SidebarMenuButton>\n </DropdownMenuTrigger>\n <DropdownMenuContent\n className=\"w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-none\"\n align=\"start\"\n side={isMobile ? \"bottom\" : \"right\"}\n sideOffset={4}\n >\n {groupTotal > 0 ? (\n groups!.map((g, i) => (\n <Fragment key={g.key}>\n {i > 0 ? <DropdownMenuSeparator /> : null}\n {g.label ? (\n <DropdownMenuLabel className=\"text-muted-foreground text-xs\">\n {g.label}\n </DropdownMenuLabel>\n ) : null}\n {g.apps.map((item) => (\n <AppRow key={item.href} app={item} />\n ))}\n </Fragment>\n ))\n ) : (\n <>\n <DropdownMenuLabel className=\"text-muted-foreground text-xs\">\n {appsLabel}\n </DropdownMenuLabel>\n {apps?.map((item) => (\n <AppRow key={item.href} app={item} />\n ))}\n </>\n )}\n {children ? (\n <>\n <DropdownMenuSeparator />\n {children}\n </>\n ) : null}\n </DropdownMenuContent>\n </DropdownMenu>\n ) : (\n <SidebarMenuButton size=\"lg\" asChild tooltip={app.name}>\n <a href={app.href}>\n <BrandContent app={app} />\n </a>\n </SidebarMenuButton>\n )}\n </SidebarMenuItem>\n </SidebarMenu>\n )\n}\n","\"use client\"\n\nimport {\n NavBrand,\n type NavBrandApp,\n type NavBrandGroup,\n} from \"./nav-brand.js\"\n\n/**\n * A registry app in the {@link AppSwitcher} - the shape a launcher endpoint\n * returns (icon as a URL string; the glyph is rendered internally). Structurally\n * matches nekit's `LauncherApp`, so `groupApps(...)` output feeds straight in.\n */\nexport interface AppSwitcherApp {\n name: string\n description?: string\n /** Navigation target (usually an absolute cross-app URL). */\n url: string\n /** Image URL; anything else renders a monogram. */\n icon: string\n}\n\n/** A grouped, ordered section of apps (label omitted for the ungrouped bucket). */\nexport interface AppSwitcherSection {\n key: string\n label?: string\n apps: AppSwitcherApp[]\n}\n\nexport interface AppSwitcherProps {\n /** The current app, shown in the sidebar header. */\n app: NavBrandApp\n /** Grouped registry apps - e.g. nekit `groupApps(data.apps, data.groups)`. */\n sections: AppSwitcherSection[]\n /** Heading for the flat fallback list. */\n appsLabel?: string\n /** Shown when `sections` is empty (registry loading / unavailable) so the\n * switcher never collapses to a plain link. */\n fallback?: NavBrandApp[]\n}\n\n/**\n * The cross-app switcher: a batteries-included wrapper over {@link NavBrand} that\n * takes the launcher registry (grouped `sections` of `{ icon, url, … }`)\n * and renders it - labeled group sections, each app's mark drawn by `NavBrand`.\n * Consumers just fetch + group (nekit) and pass config. Empty registry → the `fallback` flat list.\n *\n * @example\n * const sections = groupApps(data.apps, data.groups) // @tradalab/nekit/data/launcher\n * <AppSwitcher app={{ name: \"Admin\", icon: <ShieldCheck/>, href: \"/\" }} sections={sections} />\n */\nexport function AppSwitcher({\n app,\n sections,\n appsLabel,\n fallback,\n}: AppSwitcherProps) {\n const groups: NavBrandGroup[] = sections\n .filter((s) => s.apps.length > 0)\n .map((s) => ({\n key: s.key,\n label: s.label,\n apps: s.apps.map((a) => ({\n name: a.name,\n description: a.description,\n href: a.url,\n // The registry URL, passed through. NavBrand draws every mark it shows - header and rows\n // alike - so wrapping it in AppGlyph here as well is the second renderer that let the two\n // drift apart in the first place.\n icon: a.icon,\n })),\n }))\n\n return (\n <NavBrand\n app={app}\n groups={groups.length ? groups : undefined}\n apps={groups.length ? undefined : fallback}\n appsLabel={appsLabel}\n />\n )\n}\n","\"use client\"\n\nimport type { ReactNode } from \"react\"\nimport {\n ChevronsUpDown,\n CircleUser,\n LogIn,\n LogOut,\n Monitor,\n Moon,\n Palette,\n Sun,\n UserRoundCog,\n} from \"lucide-react\"\nimport { useTheme } from \"next-themes\"\nimport { Avatar, AvatarFallback, AvatarImage } from \"~/ui/primitives/avatar.js\"\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuRadioGroup,\n DropdownMenuRadioItem,\n DropdownMenuSeparator,\n DropdownMenuSub,\n DropdownMenuSubContent,\n DropdownMenuSubTrigger,\n DropdownMenuTrigger,\n} from \"~/ui/primitives/dropdown-menu.js\"\nimport {\n SidebarMenu,\n SidebarMenuButton,\n SidebarMenuItem,\n useSidebar,\n} from \"~/ui/primitives/sidebar.js\"\nimport {\n defaultAccountMenuLabels,\n initialsOf,\n type AccountMenuLabels,\n type AccountMenuUser,\n} from \"~/ui/account-menu.js\"\nimport { PlatePicker, type PlatePickerProps } from \"~/ui/plate-picker.js\"\nimport { Switch } from \"~/ui/primitives/switch.js\"\nimport { useCompact, usePlate } from \"~/hooks/index.js\"\n\nexport interface NavUserThemeLabels {\n theme: string\n light: string\n dark: string\n system: string\n /** Submenu trigger. It covers theme, accent and density, so it is not called \"Theme\". */\n appearance: string\n accent: string\n /** The neutral option in the accent row. */\n accentDefault: string\n compact: string\n}\n\n/** Default English appearance-submenu labels - spread + override for i18n. */\nexport const defaultNavUserThemeLabels: NavUserThemeLabels = {\n theme: \"Theme\",\n light: \"Light\",\n dark: \"Dark\",\n system: \"System\",\n appearance: \"Appearance\",\n accent: \"Accent\",\n accentDefault: \"Default\",\n compact: \"Compact\",\n}\n\nexport interface NavUserProps {\n /**\n * Signed-in user. When `null`/`undefined`, renders a guest menu (icon +\n * theme + Sign in) if `onSignIn` is set, otherwise renders nothing.\n */\n user?: AccountMenuUser | null\n /** Account-action labels — reuses lyra's {@link AccountMenuLabels}. */\n labels?: Partial<AccountMenuLabels>\n /** Switch-account handler (signed-in only). Item hidden if omitted. */\n onSwitchAccount?: () => void\n /** Sign-out handler (signed-in only). Item hidden if omitted. */\n onSignOut?: () => void\n /**\n * Sign-in handler (guest only). When set, a null `user` renders the guest\n * menu with a Sign in action instead of rendering nothing.\n */\n onSignIn?: () => void\n /** Render the light/dark/system theme submenu. Default `true`. */\n showTheme?: boolean\n /** Appearance-submenu labels. */\n themeLabels?: Partial<NavUserThemeLabels>\n /**\n * Translated name per plate, passed through to the accent row. Falls back to the plate's own key,\n * which is English: fine while the swatch is the thing a reader picks by, wrong if an app wants the\n * names read out, and the reason this is a prop rather than 19 strings baked in here.\n */\n plateLabels?: PlatePickerProps[\"plateLabels\"]\n /** Extra menu content inserted below the appearance submenu. */\n children?: ReactNode\n}\n\nconst THEMES = [\n { value: \"light\", key: \"light\", icon: Sun },\n { value: \"dark\", key: \"dark\", icon: Moon },\n { value: \"system\", key: \"system\", icon: Monitor },\n] as const\n\n/**\n * Sidebar-footer account menu — the standardized NavUser for app shells.\n * Avatar trigger (size `lg`) + name/email opening a dropdown with an optional\n * theme switcher and switch-account / sign-out actions. Auth flows stay in the\n * app (callbacks); labels are props for i18n.\n *\n * Differs from {@link AccountMenu} in `@tradalab/lyra/ui`: that one is a\n * generic header dropdown; this is styled for the collapsible sidebar footer\n * and follows `useSidebar()` for mobile placement.\n *\n * Must render inside an `AppShell`/SidebarProvider, and below a\n * `TooltipProvider` (the avatar button's collapsed-mode tooltip requires one —\n * app root providers normally mount it). The theme submenu needs a\n * `next-themes` provider mounted (it no-ops without one); pass\n * `showTheme={false}` for apps with a fixed theme.\n *\n * @example\n * import { NavUser } from \"@tradalab/lyra/shell\"\n * import { SidebarFooter } from \"@tradalab/lyra/ui\"\n *\n * <SidebarFooter>\n * <NavUser\n * user={session.user}\n * onSwitchAccount={() => signIn(\"zitadel\", { redirectTo: \"/\" }, { prompt: \"select_account\" })}\n * onSignOut={() => signOut({ redirectTo: \"/\" })}\n * />\n * </SidebarFooter>\n */\nexport function NavUser({\n user,\n labels,\n onSwitchAccount,\n onSignOut,\n onSignIn,\n showTheme = true,\n themeLabels,\n plateLabels,\n children,\n}: NavUserProps) {\n const { isMobile } = useSidebar()\n const { theme, setTheme } = useTheme()\n // Both read localStorage and write an attribute on `<html>`: device-local, like the theme beside\n // them, and nothing here needs a session.\n const [plate, setPlate] = usePlate()\n const [compact, setCompact] = useCompact()\n const l = { ...defaultAccountMenuLabels, ...labels }\n const t = { ...defaultNavUserThemeLabels, ...themeLabels }\n\n // Guests render nothing unless a sign-in handler opts them into a guest menu.\n if (!user && !onSignIn) return null\n\n const name = user ? (user.name ?? user.email ?? l.account) : l.guest\n const email = user?.email ?? \"\"\n\n const avatar = (\n <Avatar className=\"size-8 rounded-none after:rounded-none\">\n {user?.image ? (\n <AvatarImage className=\"rounded-none\" src={user.image} alt={name} />\n ) : null}\n <AvatarFallback className=\"rounded-none\">\n {user ? initialsOf(name) : <CircleUser className=\"size-4\" />}\n </AvatarFallback>\n </Avatar>\n )\n\n // Theme, accent and density in one submenu, because they are one question - what this looks like\n // on THIS device - and because three separate rows would push the account actions out of reach.\n //\n // The accent row and the density switch are deliberately NOT `DropdownMenuItem`s. A menu item\n // closes the menu when it is selected, and picking a colour is something a reader does two or\n // three times before settling; a swatch grid that shut the menu on the first click would be worse\n // than no grid. Raw controls inside `DropdownMenuSubContent` keep it open.\n const appearanceSubmenu = showTheme ? (\n <>\n <DropdownMenuSeparator />\n <DropdownMenuSub>\n <DropdownMenuSubTrigger>\n <Palette className=\"mr-2 size-4\" />\n {t.appearance}\n </DropdownMenuSubTrigger>\n <DropdownMenuSubContent className=\"w-56\">\n <DropdownMenuRadioGroup value={theme ?? \"system\"} onValueChange={setTheme}>\n {THEMES.map(({ value, key, icon: Icon }) => (\n <DropdownMenuRadioItem key={value} value={value}>\n <Icon className=\"mr-2 size-4\" />\n {t[key]}\n </DropdownMenuRadioItem>\n ))}\n </DropdownMenuRadioGroup>\n\n <DropdownMenuSeparator />\n <DropdownMenuLabel className=\"text-muted-foreground text-xs font-normal\">\n {t.accent}\n </DropdownMenuLabel>\n <div className=\"px-2 pb-1.5\">\n <PlatePicker\n value={plate}\n onChange={setPlate}\n label={t.accent}\n resetLabel={t.accentDefault}\n plateLabels={plateLabels}\n className=\"gap-1.5\"\n />\n </div>\n\n <DropdownMenuSeparator />\n <label className=\"flex cursor-pointer items-center justify-between gap-2 px-2 py-1.5 text-sm\">\n {t.compact}\n <Switch checked={compact} onCheckedChange={setCompact} />\n </label>\n </DropdownMenuSubContent>\n </DropdownMenuSub>\n </>\n ) : null\n\n return (\n <SidebarMenu>\n <SidebarMenuItem>\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <SidebarMenuButton\n size=\"lg\"\n tooltip={name}\n className=\"data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground\"\n >\n {avatar}\n <div className=\"grid flex-1 text-left text-sm leading-tight\">\n <span className=\"truncate font-medium\">{name}</span>\n {email ? (\n <span className=\"text-muted-foreground truncate text-xs\">\n {email}\n </span>\n ) : null}\n </div>\n <ChevronsUpDown className=\"ml-auto size-4\" />\n </SidebarMenuButton>\n </DropdownMenuTrigger>\n <DropdownMenuContent\n className=\"w-(--radix-dropdown-menu-trigger-width) min-w-56\"\n side={isMobile ? \"bottom\" : \"right\"}\n align=\"end\"\n sideOffset={4}\n >\n <DropdownMenuLabel className=\"flex items-center gap-2\">\n {avatar}\n <div className=\"grid flex-1 text-left leading-tight\">\n <span className=\"truncate text-sm font-medium\">{name}</span>\n {email ? (\n <span className=\"text-muted-foreground truncate text-xs font-normal\">\n {email}\n </span>\n ) : null}\n </div>\n </DropdownMenuLabel>\n\n {appearanceSubmenu}\n\n {children ? (\n <>\n <DropdownMenuSeparator />\n {children}\n </>\n ) : null}\n\n {user ? (\n <>\n {onSwitchAccount || onSignOut ? <DropdownMenuSeparator /> : null}\n {onSwitchAccount ? (\n <DropdownMenuItem onSelect={onSwitchAccount}>\n <UserRoundCog className=\"mr-2 size-4\" />\n {l.switchAccount}\n </DropdownMenuItem>\n ) : null}\n {onSignOut ? (\n <DropdownMenuItem variant=\"destructive\" onSelect={onSignOut}>\n <LogOut className=\"mr-2 size-4\" />\n {l.signOut}\n </DropdownMenuItem>\n ) : null}\n </>\n ) : (\n <>\n <DropdownMenuSeparator />\n <DropdownMenuItem onSelect={onSignIn}>\n <LogIn className=\"mr-2 size-4\" />\n {l.signIn}\n </DropdownMenuItem>\n </>\n )}\n </DropdownMenuContent>\n </DropdownMenu>\n </SidebarMenuItem>\n </SidebarMenu>\n )\n}\n","\"use client\"\n\nimport { useEffect, useRef, type ReactNode, type WheelEvent } from \"react\"\nimport { ChevronDown, ListX, Pin, PinOff, Trash2, X } from \"lucide-react\"\nimport { Button } from \"~/ui/primitives/button.js\"\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from \"~/ui/primitives/dropdown-menu.js\"\nimport { ScrollArea, ScrollBar } from \"~/ui/primitives/scroll-area.js\"\nimport {\n ContextMenu,\n ContextMenuContent,\n ContextMenuItem,\n ContextMenuSeparator,\n ContextMenuTrigger,\n} from \"~/ui/primitives/context-menu.js\"\nimport { cn } from \"~/utils/cn.js\"\n\nexport interface TabItem {\n id: string\n title: string\n /** Secondary line under the title (e.g. connection/db, file path). */\n subtitle?: ReactNode\n /** Leading icon element — the app maps its own tab type → icon. */\n icon?: ReactNode\n pinned?: boolean\n}\n\nexport interface TabBarLabels {\n close: string\n pinTab: string\n unpinTab: string\n closeOthers: string\n closeAll: string\n}\n\n/** Default English labels — spread + override for i18n. */\nexport const defaultTabBarLabels: TabBarLabels = {\n close: \"Close\",\n pinTab: \"Pin tab\",\n unpinTab: \"Unpin tab\",\n closeOthers: \"Close others\",\n closeAll: \"Close all\",\n}\n\nexport interface TabBarProps {\n tabs: TabItem[]\n activeId: string | null\n onSelect: (id: string) => void\n onClose: (id: string) => void\n /** Pin row in the context menu is hidden when omitted. */\n onTogglePin?: (id: string) => void\n /** \"Close others\" row is hidden when omitted. */\n onCloseOthers?: (id: string) => void\n /** \"Close all\" row is hidden when omitted. */\n onCloseAll?: () => void\n labels?: Partial<TabBarLabels>\n}\n\n/**\n * Horizontal tab strip for the content area of stacked-sidebar apps\n * (redishub, loom). Presentational + store-agnostic: the app owns tab state\n * and passes `tabs` + callbacks; `icon`/`subtitle` are per-tab so apps map\n * their own tab types, and labels are props for i18n. Renders nothing when\n * `tabs` is empty.\n *\n * Sizes rows from the `--tab-height` CSS var — set it in your globals.\n *\n * @example\n * const s = useTabStore()\n * <TabBar\n * tabs={s.tabs.map(t => ({ id: t.id, title: t.title, pinned: t.pinned, icon: <Icon t={t.type}/>, subtitle: fmt(t) }))}\n * activeId={s.activeTabId}\n * onSelect={s.setActiveTabId}\n * onClose={s.removeTab}\n * onTogglePin={s.togglePin}\n * onCloseOthers={s.closeOthers}\n * onCloseAll={s.closeAll}\n * />\n */\nexport function TabBar({\n tabs,\n activeId,\n onSelect,\n onClose,\n onTogglePin,\n onCloseOthers,\n onCloseAll,\n labels,\n}: TabBarProps) {\n const l = { ...defaultTabBarLabels, ...labels }\n const activeTabRef = useRef<HTMLDivElement>(null)\n\n // Scroll the active tab into view when it changes.\n useEffect(() => {\n if (activeId && activeTabRef.current) {\n activeTabRef.current.scrollIntoView({\n behavior: \"smooth\",\n block: \"nearest\",\n inline: \"nearest\",\n })\n }\n }, [activeId])\n\n // Map vertical wheel scroll to horizontal scroll over the tab strip.\n const handleWheel = (e: WheelEvent<HTMLDivElement>) => {\n if (Math.abs(e.deltaY) > Math.abs(e.deltaX)) {\n const viewport = e.currentTarget.querySelector(\n '[data-slot=\"scroll-area-viewport\"]',\n )\n if (viewport) viewport.scrollLeft += e.deltaY * 0.5\n }\n }\n\n if (tabs.length === 0) return null\n\n const hasContextActions = Boolean(onTogglePin || onCloseOthers || onCloseAll)\n\n return (\n <div\n className=\"group/tabbar flex w-full shrink-0 items-center overflow-hidden border-b bg-muted/30\"\n style={{ height: \"var(--tab-height)\" }}\n >\n <ScrollArea\n type=\"hover\"\n className=\"min-w-0 flex-1 overflow-hidden [&_[data-orientation=vertical]]:hidden\"\n onWheel={handleWheel}\n >\n <div\n className=\"flex w-max items-center gap-0\"\n style={{ height: \"var(--tab-height)\" }}\n >\n {tabs.map((tab) => {\n const isActive = activeId === tab.id\n return (\n <ContextMenu key={tab.id}>\n <ContextMenuTrigger asChild>\n <div\n ref={isActive ? activeTabRef : null}\n onClick={() => onSelect(tab.id)}\n title={tab.title}\n className={cn(\n \"group relative flex min-w-[140px] max-w-[240px] shrink-0 cursor-pointer select-none items-center gap-2 border-r px-3 transition-colors\",\n isActive\n ? \"border-t-2 border-t-primary bg-background\"\n : \"border-t-2 border-t-transparent bg-transparent text-muted-foreground hover:bg-muted/50\",\n tab.pinned && \"min-w-[100px] bg-primary/5\",\n )}\n style={{ height: \"var(--tab-height)\" }}\n >\n {tab.icon ? (\n <div className=\"relative shrink-0\">\n <span\n className={cn(\n \"flex [&_svg]:size-3.5\",\n isActive && \"text-primary\",\n )}\n >\n {tab.icon}\n </span>\n {tab.pinned ? (\n <div className=\"absolute -right-1.5 -top-1.5 rounded-full bg-background p-0.5 shadow-sm\">\n <Pin className=\"size-2 fill-primary text-primary\" />\n </div>\n ) : null}\n </div>\n ) : null}\n <div className=\"flex min-w-0 flex-1 flex-col items-start justify-center overflow-hidden py-1\">\n <span\n className={cn(\n \"w-full truncate text-xs leading-tight\",\n isActive && \"font-medium text-foreground\",\n )}\n >\n {tab.title}\n </span>\n {tab.subtitle ? (\n <span className=\"mt-0.5 w-full truncate text-[10px] leading-none text-muted-foreground/70\">\n {tab.subtitle}\n </span>\n ) : null}\n </div>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n title={l.close}\n className={cn(\n \"size-5 shrink-0 rounded-md opacity-0 transition-opacity hover:bg-muted-foreground/20 group-hover:opacity-100\",\n isActive && \"opacity-100\",\n )}\n onClick={(e) => {\n e.stopPropagation()\n onClose(tab.id)\n }}\n >\n <X className=\"size-3\" />\n </Button>\n {isActive ? (\n <div className=\"absolute bottom-0 left-0 right-0 z-10 h-[2px] bg-background\" />\n ) : null}\n </div>\n </ContextMenuTrigger>\n {hasContextActions ? (\n <ContextMenuContent className=\"w-48\">\n {onTogglePin ? (\n <>\n <ContextMenuItem onClick={() => onTogglePin(tab.id)}>\n {tab.pinned ? (\n <>\n <PinOff className=\"mr-2 size-4\" />\n <span>{l.unpinTab}</span>\n </>\n ) : (\n <>\n <Pin className=\"mr-2 size-4\" />\n <span>{l.pinTab}</span>\n </>\n )}\n </ContextMenuItem>\n <ContextMenuSeparator />\n </>\n ) : null}\n <ContextMenuItem onClick={() => onClose(tab.id)}>\n <X className=\"mr-2 size-4\" />\n <span>{l.close}</span>\n </ContextMenuItem>\n {onCloseOthers ? (\n <ContextMenuItem onClick={() => onCloseOthers(tab.id)}>\n <ListX className=\"mr-2 size-4\" />\n <span>{l.closeOthers}</span>\n </ContextMenuItem>\n ) : null}\n {onCloseAll ? (\n <ContextMenuItem onClick={() => onCloseAll()}>\n <Trash2 className=\"mr-2 size-4\" />\n <span>{l.closeAll}</span>\n </ContextMenuItem>\n ) : null}\n </ContextMenuContent>\n ) : null}\n </ContextMenu>\n )\n })}\n </div>\n <ScrollBar orientation=\"horizontal\" className=\"z-20 h-1.5 hover:h-2\" />\n </ScrollArea>\n\n <div\n className=\"flex shrink-0 items-center border-l bg-muted/30 px-1\"\n style={{ height: \"var(--tab-height)\" }}\n >\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button variant=\"ghost\" size=\"icon\" className=\"size-8 shrink-0 rounded-sm\">\n <ChevronDown className=\"size-4\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent\n align=\"end\"\n className=\"max-h-[400px] w-[260px] overflow-y-auto\"\n >\n {tabs.map((tab) => (\n <DropdownMenuItem\n key={tab.id}\n onClick={() => onSelect(tab.id)}\n title={tab.title}\n className={cn(\n \"cursor-pointer gap-2 py-2\",\n activeId === tab.id && \"bg-accent\",\n )}\n >\n {tab.icon ? (\n <div className=\"relative shrink-0\">\n <span className=\"flex text-muted-foreground [&_svg]:size-4\">\n {tab.icon}\n </span>\n {tab.pinned ? (\n <Pin className=\"absolute -right-1 -top-1 size-2 fill-primary text-primary\" />\n ) : null}\n </div>\n ) : null}\n <div className=\"flex flex-1 flex-col items-start overflow-hidden leading-tight\">\n <span className=\"w-full flex-1 truncate text-sm\">{tab.title}</span>\n {tab.subtitle ? (\n <span className=\"w-full truncate text-[10px] text-muted-foreground\">\n {tab.subtitle}\n </span>\n ) : null}\n </div>\n <X\n className=\"size-4 shrink-0 rounded-sm p-0.5 hover:bg-red-50 hover:text-red-500\"\n onClick={(e) => {\n e.stopPropagation()\n onClose(tab.id)\n }}\n />\n </DropdownMenuItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n </div>\n )\n}\n","\"use client\"\n\nimport { Fragment, type ComponentProps, type ReactNode } from \"react\"\nimport Link from \"next/link\"\nimport {\n Breadcrumb,\n BreadcrumbItem,\n BreadcrumbLink,\n BreadcrumbList,\n BreadcrumbPage,\n BreadcrumbSeparator,\n} from \"~/ui/primitives/breadcrumb.js\"\nimport { useActiveRoute } from \"./use-active-route.js\"\n\nexport interface LeadingCrumb {\n /** Where the crumb links. Omit to render it as plain text. */\n href?: string\n label: ReactNode\n /** Hide on small screens, as app-name crumbs usually should. Default `true`. */\n hideOnMobile?: boolean\n}\n\nexport interface BreadcrumbsProps extends ComponentProps<typeof Breadcrumb> {\n /** Separator content. Falls back to shadcn's chevron when omitted. */\n separator?: ReactNode\n /** Segment-id → display-label map. Unmapped segments render raw. */\n labels?: Record<string, string>\n /**\n * Label a crumb from the path it links to, consulted before `labels`. Return\n * `undefined` to fall through.\n *\n * This exists so the nav can be the one place a route gets its name. Every app\n * already holds an `href → label` table to build the sidebar; the alternative\n * is a second table keyed by segment, and a second table drifts. Measured on\n * one app before this shipped: four routes the nav listed were missing from its\n * crumb table, one route had two names across the two, and a nested route was\n * swallowed by its parent's prefix so the crumb NAMED THE WRONG SCREEN. None of\n * that is visible on the screen that caused it.\n *\n * `labels` stays for an app small enough that its segments are already words.\n */\n resolve?: (href: string, segment: string) => ReactNode | undefined\n /**\n * Crumb rendered before the path segments — typically the app name linking\n * home. With it set the trail also renders on the root route (as the current\n * page); without it the root route renders nothing.\n */\n leading?: LeadingCrumb\n}\n\n/** Pathname-derived breadcrumbs. Renders nothing on the root route unless `leading` is set. */\nexport function Breadcrumbs({ separator, labels, resolve, leading, ...rest }: BreadcrumbsProps) {\n const { pathname } = useActiveRoute()\n const segments = pathname.split(\"/\").filter(Boolean)\n if (segments.length === 0 && !leading) return null\n const last = segments.length - 1\n const hideLeading = leading?.hideOnMobile ?? true\n return (\n <Breadcrumb {...rest}>\n <BreadcrumbList>\n {leading ? (\n <BreadcrumbItem className={hideLeading ? \"hidden md:block\" : undefined}>\n {/* On the root route the leading crumb IS the current page. */}\n {segments.length === 0 ? (\n <BreadcrumbPage>{leading.label}</BreadcrumbPage>\n ) : leading.href ? (\n <BreadcrumbLink asChild>\n <Link href={leading.href}>{leading.label}</Link>\n </BreadcrumbLink>\n ) : (\n leading.label\n )}\n </BreadcrumbItem>\n ) : null}\n {segments.map((seg, i) => {\n const href = \"/\" + segments.slice(0, i + 1).join(\"/\")\n const label = resolve?.(href, seg) ?? labels?.[seg] ?? seg\n return (\n <Fragment key={href}>\n {i > 0 || leading ? (\n <BreadcrumbSeparator className={i === 0 && hideLeading && leading ? \"hidden md:block\" : undefined}>\n {separator}\n </BreadcrumbSeparator>\n ) : null}\n <BreadcrumbItem>\n {i === last ? (\n <BreadcrumbPage>{label}</BreadcrumbPage>\n ) : (\n <BreadcrumbLink asChild>\n <Link href={href}>{label}</Link>\n </BreadcrumbLink>\n )}\n </BreadcrumbItem>\n </Fragment>\n )\n })}\n </BreadcrumbList>\n </Breadcrumb>\n )\n}\n","import type { ReactNode } from \"react\"\nimport { Search } from \"lucide-react\"\n\nimport { cn } from \"~/utils/cn.js\"\nimport {\n Card,\n CardDescription,\n CardTitle,\n} from \"~/ui/primitives/card.js\"\nimport {\n InputGroup,\n InputGroupAddon,\n InputGroupInput,\n} from \"~/ui/primitives/input-group.js\"\n\n/** One application in the {@link AppGrid} — rendered as an {@link AppTile}. */\nexport interface AppGridItem {\n /** Stable React list key. */\n key: string\n /** Display name. */\n name: string\n /** Optional tagline shown under the name. */\n description?: string\n /** Navigation target (usually an absolute cross-app URL). */\n href: string\n /**\n * Pre-rendered icon element (RSC-serializable), e.g. `<ToolCase />`. Sized by\n * the tile (`[&_svg]:size-7`) — same convention as {@link NavBrandApp}'s icon.\n */\n icon: ReactNode\n /**\n * Tailwind class string tinting the icon tile — `text-…` (colors the icon via\n * currentColor) + `bg-…` (tile background). Pass full class strings, not\n * interpolated fragments, so the JIT keeps them.\n */\n accent?: string\n /** Opens on a different origin (adds `target=\"_blank\"` + `rel`). */\n external?: boolean\n}\n\n/** A titled section of apps for {@link AppGrid}'s grouped mode. */\nexport interface AppGridGroup {\n /** Stable React list key. */\n key: string\n /** Section heading. */\n label: string\n /** Optional sub-heading under the label. */\n description?: string\n /** Apps in this group — already filtered by the caller. */\n apps: AppGridItem[]\n}\n\nexport interface AppGridProps {\n /** Flat mode: apps to render (already filtered). Ignored when `groups` is set. */\n apps?: AppGridItem[]\n /**\n * Grouped mode: titled sections (already filtered). When provided, renders a\n * heading + tile grid per group; empty groups are dropped.\n */\n groups?: AppGridGroup[]\n /** Controlled search value. Pass together with `onQueryChange` to show the search box. */\n query?: string\n onQueryChange?: (query: string) => void\n /** Search input placeholder. */\n searchPlaceholder?: string\n /** Autofocus the search box on mount. Default `false`. */\n autoFocusSearch?: boolean\n /** Message shown when there are no apps. */\n emptyLabel?: string\n className?: string\n}\n\n/** Default English labels — override for i18n. */\nexport const defaultAppGridLabels = {\n searchPlaceholder: \"Search apps…\",\n empty: \"No apps found.\",\n}\n\n/**\n * A responsive grid of application tiles with an optional search box — the \"app\n * grid\" / launcher surface (a portal home / \"all apps\" page). Presentational:\n * the app owns the registry, i18n, icons, and the filter — pass the already-\n * filtered `apps` plus a controlled `query`. Same app/lyra split as\n * {@link NavBrand} and {@link TabBar}.\n *\n * @example\n * const [q, setQ] = useState(\"\")\n * const filtered = APPS.filter(a => match(a, q))\n * <AppGrid\n * apps={filtered.map(a => ({\n * key: a.slug, name: t(a.slug), description: t(`${a.slug}.desc`),\n * href: a.href, icon: <a.Icon />, accent: a.accent, external: true,\n * }))}\n * query={q} onQueryChange={setQ}\n * searchPlaceholder={t(\"search\")} emptyLabel={t(\"empty\")}\n * />\n */\nexport function AppGrid({\n apps,\n groups,\n query,\n onQueryChange,\n searchPlaceholder = defaultAppGridLabels.searchPlaceholder,\n autoFocusSearch = false,\n emptyLabel = defaultAppGridLabels.empty,\n className,\n}: AppGridProps) {\n const grouped = groups != null\n const sections = grouped ? groups.filter((g) => g.apps.length > 0) : []\n const flat = apps ?? []\n const isEmpty = grouped ? sections.length === 0 : flat.length === 0\n\n return (\n <div className={cn(\"space-y-8\", className)}>\n {onQueryChange ? (\n <InputGroup className=\"mx-auto h-10 max-w-md\">\n <InputGroupAddon>\n <Search />\n </InputGroupAddon>\n <InputGroupInput\n type=\"search\"\n value={query ?? \"\"}\n onChange={(e) => onQueryChange(e.target.value)}\n placeholder={searchPlaceholder}\n aria-label={searchPlaceholder}\n autoFocus={autoFocusSearch}\n />\n </InputGroup>\n ) : null}\n\n {isEmpty ? (\n <p className=\"text-muted-foreground py-16 text-center text-sm\">\n {emptyLabel}\n </p>\n ) : grouped ? (\n <div className=\"space-y-10\">\n {sections.map((g) => (\n <section key={g.key} className=\"space-y-4\">\n <div className=\"space-y-1\">\n <h2 className=\"text-sm font-semibold tracking-tight\">{g.label}</h2>\n {g.description ? (\n <p className=\"text-muted-foreground text-xs\">{g.description}</p>\n ) : null}\n </div>\n <AppTileGrid apps={g.apps} />\n </section>\n ))}\n </div>\n ) : (\n <AppTileGrid apps={flat} />\n )}\n </div>\n )\n}\n\n/** The responsive tile grid shared by {@link AppGrid}'s flat and grouped modes. */\nfunction AppTileGrid({ apps }: { apps: AppGridItem[] }) {\n return (\n <div className=\"grid grid-cols-2 gap-4 sm:grid-cols-3 lg:grid-cols-4\">\n {apps.map((app) => (\n <AppTile key={app.key} app={app} />\n ))}\n </div>\n )\n}\n\n/**\n * A single application tile: the icon in a tinted rounded chip above the name +\n * description. Exported for callers that render their own grid.\n */\nexport function AppTile({ app }: { app: AppGridItem }) {\n return (\n <a\n href={app.href}\n target={app.external ? \"_blank\" : undefined}\n rel={app.external ? \"noreferrer\" : undefined}\n className=\"focus-visible:ring-ring group rounded-none outline-none focus-visible:ring-2\"\n >\n <Card className=\"h-full items-center gap-4 px-6 py-8 text-center transition-all duration-200 group-hover:-translate-y-0.5 group-hover:shadow-md group-hover:ring-foreground/30\">\n {/* Tinted icon tile (sharp — matches lyra's rounded-none aesthetic);\n `accent` carries the text-… (icon, via currentColor) + bg-… tint. */}\n {/* An app image arrives with its own plate, so the hairline ring would frame it twice. */}\n <div\n className={cn(\n \"flex size-14 items-center justify-center rounded-none ring-1 ring-foreground/5 ring-inset transition-transform duration-200 group-hover:scale-105 has-[img]:ring-0 [&_svg]:size-7\",\n app.accent,\n )}\n >\n {app.icon}\n </div>\n <div className=\"space-y-1\">\n <CardTitle>{app.name}</CardTitle>\n {app.description ? (\n <CardDescription className=\"line-clamp-2\">\n {app.description}\n </CardDescription>\n ) : null}\n </div>\n </Card>\n </a>\n )\n}\n"]}
1
+ {"version":3,"sources":["../../src/shell/app-shell.tsx","../../src/shell/stacked-sidebar.tsx","../../src/shell/page-shell.tsx","../../src/shell/page-header.tsx","../../src/shell/nav-group.tsx","../../src/shell/use-active-route.ts","../../src/shell/nav-item.tsx","../../src/shell/nav-section.tsx","../../src/shell/app-glyph.tsx","../../src/shell/nav-brand.tsx","../../src/shell/app-switcher.tsx","../../src/shell/nav-user.tsx","../../src/shell/tab-bar.tsx","../../src/shell/breadcrumbs.tsx","../../src/shell/app-grid.tsx"],"names":["jsx","jsxs","Link","Fragment","ChevronsUpDown","useRef","useEffect"],"mappings":"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","file":"index.js","sourcesContent":["\"use client\"\n\nimport type { ComponentProps, ReactNode } from \"react\"\nimport {\n SidebarInset,\n SidebarProvider,\n} from \"~/ui/primitives/sidebar.js\"\nimport { cn } from \"~/utils/cn.js\"\n\nexport interface AppShellProps\n extends Omit<ComponentProps<typeof SidebarProvider>, \"children\"> {\n children: ReactNode\n}\n\n/**\n * App shell composition root. Wires `SidebarProvider`. Pair with\n * `<Sidebar>` from `@tradalab/lyra/ui` and `<AppShellMain>` for the\n * right-hand pane.\n *\n * @example\n * import { AppShell, AppShellMain, AppShellHeader, AppShellBody, NavGroup, NavItem, Breadcrumbs } from \"@tradalab/lyra/shell\"\n * import { Sidebar, SidebarHeader, SidebarContent, SidebarFooter, SidebarTrigger } from \"@tradalab/lyra/ui\"\n *\n * <AppShell>\n * <Sidebar collapsible=\"icon\">\n * <SidebarHeader>{logo}</SidebarHeader>\n * <SidebarContent>\n * <NavGroup label=\"Primitives\">{...}</NavGroup>\n * </SidebarContent>\n * <SidebarFooter>{user}</SidebarFooter>\n * </Sidebar>\n * <AppShellMain>\n * <AppShellHeader><SidebarTrigger /><Breadcrumbs /></AppShellHeader>\n * <AppShellBody>{children}</AppShellBody>\n * </AppShellMain>\n * </AppShell>\n */\nexport function AppShell({ children, ...rest }: AppShellProps) {\n return <SidebarProvider {...rest}>{children}</SidebarProvider>\n}\n\n/** Right-hand pane container. Alias for `SidebarInset`. */\nexport const AppShellMain = SidebarInset\n\n/** Top bar inside `<AppShellMain>`. Standardizes h-12 + border-b + flex row. */\nexport function AppShellHeader({\n className,\n children,\n ...rest\n}: ComponentProps<\"header\">) {\n return (\n <header\n className={cn(\n \"flex h-12 shrink-0 items-center gap-2 border-b px-3\",\n className,\n )}\n {...rest}\n >\n {children}\n </header>\n )\n}\n\n/** Scroll body inside `<AppShellMain>`. Sibling of `<AppShellHeader>`. */\nexport function AppShellBody({\n className,\n children,\n ...rest\n}: ComponentProps<\"div\">) {\n return (\n <div className={cn(\"flex-1 overflow-auto\", className)} {...rest}>\n {children}\n </div>\n )\n}\n","\"use client\"\n\nimport type { ComponentProps } from \"react\"\nimport { Sidebar } from \"~/ui/primitives/sidebar.js\"\nimport { cn } from \"~/utils/cn.js\"\n\n/**\n * Dual / stacked sidebar wrapper — an icon rail plus one or more content\n * panels rendered side-by-side inside a single `<Sidebar>`. This is the\n * shadcn \"sidebar-07\" pattern.\n *\n * The `*:data-[sidebar=sidebar]:flex-row` trick lays the inner\n * `[data-sidebar=sidebar]` wrappers of the nested {@link SidebarDock} /\n * {@link SidebarPanel} columns out horizontally. `variant`/`collapsible`\n * default to `\"sidebar\"`/`\"icon\"` but can be overridden.\n *\n * @example\n * import { AppShell, AppShellMain, StackedSidebar, SidebarDock, SidebarPanel } from \"@tradalab/lyra/shell\"\n * import { SidebarHeader, SidebarContent, SidebarMenu, SidebarMenuItem, SidebarMenuButton } from \"@tradalab/lyra/ui\"\n *\n * <AppShell>\n * <StackedSidebar>\n * <SidebarDock>\n * <SidebarHeader>{logo}</SidebarHeader>\n * <SidebarContent>{iconButtons}</SidebarContent>\n * </SidebarDock>\n * {tab === \"files\" ? <SidebarPanel>{tree}</SidebarPanel> : null}\n * </StackedSidebar>\n * <AppShellMain>{children}</AppShellMain>\n * </AppShell>\n */\nexport function StackedSidebar({\n className,\n ...rest\n}: ComponentProps<typeof Sidebar>) {\n return (\n <Sidebar\n variant=\"sidebar\"\n collapsible=\"icon\"\n {...rest}\n className={cn(\n \"overflow-hidden *:data-[sidebar=sidebar]:flex-row\",\n className,\n )}\n />\n )\n}\n\n/**\n * Narrow icon-rail column inside a {@link StackedSidebar}. Fixed to the icon\n * width, never collapses on its own (the parent `StackedSidebar` owns the\n * icon-mode toggle).\n *\n * Named `SidebarDock` — not `SidebarRail` — because `SidebarRail` already\n * exists in `@tradalab/lyra/ui` as shadcn's drag-to-resize edge handle.\n */\nexport function SidebarDock({\n className,\n ...rest\n}: ComponentProps<typeof Sidebar>) {\n return (\n <Sidebar\n {...rest}\n collapsible=\"none\"\n className={cn(\n \"w-[calc(var(--sidebar-width-icon)+1px)]! overflow-hidden border-r\",\n className,\n )}\n />\n )\n}\n\n/**\n * Wider content column inside a {@link StackedSidebar} (tree, list, …).\n * Fills remaining width; render conditionally to show/hide per app state.\n */\nexport function SidebarPanel({\n className,\n ...rest\n}: ComponentProps<typeof Sidebar>) {\n return (\n <Sidebar\n {...rest}\n collapsible=\"none\"\n className={cn(\"flex-1 overflow-hidden\", className)}\n />\n )\n}\n","import type { ReactNode } from \"react\"\nimport { cn } from \"~/utils/cn.js\"\n\n/**\n * Width is decided by what is inside the page, not one page at a time.\n *\n * Left-aligned, not centred. `mx-auto` floats a form in the middle of a wide monitor with the nav\n * stranded on the far left, and the two stop reading as one page. Centring is what `Container`\n * offers, and it is right there: a standalone page owns the whole viewport. This one is the body of\n * a page inside `AppShellBody`, which already holds the scroll region and the gutters, so adding\n * either here doubles it.\n */\nconst MEASURE = {\n /**\n * Tables, trees, calendars and charts. A column nobody can read costs more than a long line: a\n * self-imposed cap is what makes a report collapse five facts into one cell while a third of the\n * monitor goes to margin.\n */\n table: \"max-w-none\",\n /** Prose, detail panes, dashboards. */\n read: \"max-w-5xl\",\n /** Stops where a line stops being comfortable to read, which is what a form field is. */\n form: \"max-w-3xl\",\n} as const\n\nexport type PageMeasure = keyof typeof MEASURE\n\nexport interface PageShellProps {\n /** Default `\"read\"`. */\n measure?: PageMeasure\n className?: string\n children?: ReactNode\n}\n\nexport function PageShell({ measure = \"read\", className, children }: PageShellProps) {\n return (\n <div className={cn(\"flex w-full flex-col gap-6\", MEASURE[measure], className)}>{children}</div>\n )\n}\n","import type { ReactNode } from \"react\"\nimport { cn } from \"~/utils/cn.js\"\n\n/**\n * Title, one line of context, and the actions that belong to the whole screen.\n *\n * Five apps carried this byte for byte and two more carried a variant, which is how the heading\n * level drifted: the same product rendered its page titles at three sizes depending on which app\n * you were standing in.\n *\n * The description is not decoration on a screen that names something a reader cannot infer (a pin,\n * a derived day, a rule pack); it is the sentence that stops them guessing. Leave it out rather\n * than restate the title.\n */\nexport interface PageHeaderProps {\n title: ReactNode\n description?: ReactNode\n actions?: ReactNode\n className?: string\n}\n\nexport function PageHeader({ title, description, actions, className }: PageHeaderProps) {\n return (\n <div\n className={cn(\n \"flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between\",\n className,\n )}\n >\n <div className=\"space-y-1\">\n <h1 className=\"text-2xl font-semibold tracking-tight\">{title}</h1>\n {description ? <p className=\"text-muted-foreground text-sm\">{description}</p> : null}\n </div>\n {actions ? <div className=\"flex shrink-0 items-center gap-2\">{actions}</div> : null}\n </div>\n )\n}\n","\"use client\"\n\nimport type { ReactNode } from \"react\"\nimport {\n SidebarGroup,\n SidebarGroupContent,\n SidebarGroupLabel,\n SidebarMenu,\n} from \"~/ui/primitives/sidebar.js\"\n\nexport interface NavGroupProps {\n label?: ReactNode\n children: ReactNode\n}\n\nexport function NavGroup({ label, children }: NavGroupProps) {\n return (\n <SidebarGroup>\n {label != null ? <SidebarGroupLabel>{label}</SidebarGroupLabel> : null}\n <SidebarGroupContent>\n <SidebarMenu>{children}</SidebarMenu>\n </SidebarGroupContent>\n </SidebarGroup>\n )\n}\n","\"use client\"\n\nimport { usePathname } from \"next/navigation\"\n\nexport type MatchMode = \"exact\" | \"prefix\"\n\nexport interface UseActiveRoute {\n pathname: string\n isActive: (href: string, mode?: MatchMode) => boolean\n}\n\n/**\n * App-Router pathname helper. Default `mode` is `\"prefix\"`.\n *\n * Root (`/`) is always exact-matched regardless of `mode` — otherwise the\n * root nav item would light up on every nested page.\n */\nexport function useActiveRoute(): UseActiveRoute {\n const pathname = usePathname() ?? \"/\"\n return {\n pathname,\n isActive: (href, mode = \"prefix\") => {\n if (mode === \"exact\" || href === \"/\") return pathname === href\n return pathname === href || pathname.startsWith(`${href}/`)\n },\n }\n}\n","\"use client\"\n\nimport Link from \"next/link\"\nimport { ArrowUpRight } from \"lucide-react\"\nimport type { ReactNode } from \"react\"\nimport {\n SidebarMenuButton,\n SidebarMenuItem,\n} from \"~/ui/primitives/sidebar.js\"\nimport { useActiveRoute, type MatchMode } from \"./use-active-route.js\"\n\nexport type NavIcon = ReactNode\n\n/** Absolute URLs and mail/tel links leave the app — they can't go through next/link. */\nconst looksExternal = (href: string) =>\n /^([a-z][a-z0-9+.-]*:)?\\/\\//i.test(href) || /^(mailto|tel):/i.test(href)\n\nexport interface NavItemProps {\n href: string\n label: string\n /** Pre-rendered icon element, e.g. `<Globe />`. Element form so the prop is serializable across the RSC boundary. */\n icon?: NavIcon\n /** Sidebar tooltip; visible in collapsed icon mode. */\n tooltip?: string\n /** Default `\"prefix\"` — matches `href` and any nested path. */\n matchMode?: MatchMode\n /**\n * Render a new-tab anchor instead of a `next/link`, and never mark the row\n * active. Defaults to auto-detection: absolute (`https://…`), protocol-\n * relative, `mailto:` and `tel:` hrefs count as external.\n */\n external?: boolean\n}\n\n/** Sidebar nav row. Must be rendered inside a {@link NavGroup}. */\nexport function NavItem({\n href,\n label,\n icon,\n tooltip,\n matchMode = \"prefix\",\n external,\n}: NavItemProps) {\n const { isActive } = useActiveRoute()\n const isExternal = external ?? looksExternal(href)\n return (\n <SidebarMenuItem>\n <SidebarMenuButton\n asChild\n isActive={!isExternal && isActive(href, matchMode)}\n tooltip={tooltip}\n >\n {isExternal ? (\n <a href={href} target=\"_blank\" rel=\"noopener noreferrer\">\n {icon}\n <span>{label}</span>\n <ArrowUpRight\n aria-hidden\n className=\"ms-auto size-3.5! shrink-0 opacity-50 group-data-[collapsible=icon]:hidden\"\n />\n </a>\n ) : (\n <Link href={href}>\n {icon}\n <span>{label}</span>\n </Link>\n )}\n </SidebarMenuButton>\n </SidebarMenuItem>\n )\n}\n","\"use client\"\n\nimport type { ReactNode } from \"react\"\nimport { NavGroup } from \"./nav-group.js\"\nimport { NavItem, type NavItemProps } from \"./nav-item.js\"\n\nexport interface NavSectionProps {\n /** Optional group heading. */\n label?: ReactNode\n /** Nav rows — each rendered as a {@link NavItem}. */\n items: NavItemProps[]\n}\n\n/**\n * Config-driven convenience over {@link NavGroup} + {@link NavItem}: pass a\n * data array instead of hand-mapping JSX. For dynamic or conditional rows,\n * compose `NavGroup`/`NavItem` directly.\n *\n * @example\n * const NAV = [\n * { href: \"/files\", label: \"Files\", icon: <Folder /> },\n * { href: \"/trash\", label: \"Trash\", icon: <Trash2 /> },\n * ]\n * <NavSection label=\"Workspace\" items={NAV} />\n */\nexport function NavSection({ label, items }: NavSectionProps) {\n return (\n <NavGroup label={label}>\n {items.map((item) => (\n <NavItem key={item.href} {...item} />\n ))}\n </NavGroup>\n )\n}\n","\"use client\"\n\n// The single renderer for a registered app's mark, shared by the app switcher, the launcher and\n// the Apps admin so one app looks identical everywhere. `icon` is the URL of the image the app\n// ships; anything else, and an image that fails to load, falls back to a name monogram.\n//\n// Lucide icon NAMES are deliberately no longer accepted: resolving any name at runtime meant\n// importing `lucide-react/dynamic`, one `import()` per icon, and Turbopack turned that into ~2000\n// chunk groups on every page of every app (doc/platform/PITFALLS.md).\n\nimport { useEffect, useRef, useState } from \"react\"\n\nimport { cn } from \"~/utils/cn.js\"\n\n// Absolute only: a relative path would resolve against whichever app is showing the launcher.\nexport function isIconUrl(s: string): boolean {\n return /^https?:\\/\\//i.test(s)\n}\n\n// One leading slash, never two. `//cdn.test/x.png` also starts with \"/\" and is a PROTOCOL-RELATIVE\n// URL pointing at another host - the one thing `ownOrigin` exists to exclude. A test that asserted\n// this was passing for the wrong reason until it stubbed the image state, and a mutation run is what\n// said so.\nfunction isOwnPath(s: string): boolean {\n return /^\\/(?!\\/)/.test(s)\n}\n\nexport interface AppGlyphProps {\n /** Absolute http(s) image URL; any other value draws the monogram. */\n icon?: string\n /** App name, drawn as the monogram when there is no image. */\n name?: string\n className?: string\n /**\n * Also accept a ROOT-RELATIVE `icon` (`/icon.svg`).\n *\n * Off by default, and that default is the point: a registry mark belongs to some OTHER app, so a\n * relative path would resolve against whichever app is drawing the launcher and quietly show the\n * wrong mark. app-glyph.test.tsx pins that.\n *\n * On for an app drawing its OWN mark - the sidebar header. There same-origin is the whole\n * intention: the file sits in this app's `public/`, so an absolute URL would add a cross-origin\n * round trip to fetch something already here, and one blip would swap the header for a letter.\n */\n ownOrigin?: boolean\n}\n\n/** An app's mark: its image when `icon` is a URL that loads, else a name monogram. */\nexport function AppGlyph({ icon, name, className, ownOrigin }: AppGlyphProps) {\n const box = \"flex size-9 shrink-0 items-center justify-center rounded-none\"\n const src = icon && (isIconUrl(icon) || (ownOrigin === true && isOwnPath(icon))) ? icon : \"\"\n const [failedSrc, setFailedSrc] = useState(\"\")\n const seen = useRef(src)\n const ref = useRef<HTMLImageElement>(null)\n\n // A new URL deserves its own attempt, including one that failed earlier: the admin form edits\n // this field a keystroke at a time, and a URL typed past a broken prefix would stay dead.\n if (seen.current !== src) {\n seen.current = src\n if (failedSrc) setFailedSrc(\"\")\n }\n\n // An image that fails before hydration never fires onError, so the mount check is what\n // catches a server-rendered tile whose host is down. It reads getAttribute, not `img.src`:\n // that property normalizes the URL (an upper-case scheme comes back lower-cased), and the\n // comparison below would then never match, leaving a broken image on screen.\n useEffect(() => {\n const img = ref.current\n if (img?.complete && img.naturalWidth === 0) setFailedSrc(img.getAttribute(\"src\") ?? \"\")\n }, [src])\n\n if (src && failedSrc !== src) {\n return (\n <img\n ref={ref}\n src={src}\n alt=\"\"\n aria-hidden\n className={cn(box, \"object-contain\", className)}\n onError={() => setFailedSrc(src)}\n />\n )\n }\n\n return (\n <span\n aria-hidden\n className={cn(box, \"bg-primary/10 text-sm font-semibold text-primary\", className)}\n >\n {(name || \"?\").charAt(0).toUpperCase()}\n </span>\n )\n}\n","\"use client\"\n\nimport { Fragment, type ReactNode } from \"react\"\nimport { ChevronsUpDown } from \"lucide-react\"\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from \"~/ui/primitives/dropdown-menu.js\"\nimport { AppGlyph } from \"./app-glyph.js\"\nimport {\n SidebarMenu,\n SidebarMenuButton,\n SidebarMenuItem,\n useSidebar,\n} from \"~/ui/primitives/sidebar.js\"\nimport { cn } from \"~/utils/cn.js\"\n\n/** An application shown in the {@link NavBrand} header / switcher. */\nexport interface NavBrandApp {\n /** Display name. */\n name: string\n /**\n * The app's mark, in one of three forms:\n *\n * - a ROOT-RELATIVE path (`\"/icon.svg\"`) - THIS app's own mark. Same origin by construction,\n * which is why the header uses it: the file is already in this app's `public/`, so an absolute\n * URL would add a cross-origin round trip to fetch something that is right here.\n * - an ABSOLUTE http(s) URL - another app's mark, as the registry stores it. Drawn by\n * {@link AppGlyph}, so a host that is down degrades to a name monogram rather than a broken\n * image.\n * - a pre-rendered element, e.g. `<Boxes />`. Element form (not a component reference) so the\n * prop stays serializable across the RSC boundary - same convention as {@link NavItem}'s\n * `icon`. Drawn on the brand-colour plate, inheriting its foreground.\n *\n * Either string form gets NO plate: the generated marks are opaque squircles, and a rounded mark\n * on a square colour tile shows four corners of the wrong colour behind it. Nor does the monogram\n * such a mark degrades to, which brings its own tint.\n */\n icon: ReactNode\n /** Navigation target. Usually an absolute cross-app URL (full navigation). */\n href: string\n /** Optional secondary line under the name (tagline / domain). */\n description?: string\n}\n\n/** A labeled section of apps in the {@link NavBrand} switcher. */\nexport interface NavBrandGroup {\n /** Stable React key. */\n key: string\n /** Section heading. Omit for an unlabeled section (e.g. ungrouped apps). */\n label?: string\n apps: NavBrandApp[]\n}\n\nexport interface NavBrandProps {\n /** The current app, rendered in the sidebar header. */\n app: NavBrandApp\n /**\n * Other apps to switch to (flat, single list). When omitted or empty - and no\n * `groups` - the header renders as a plain link to `app.href`.\n */\n apps?: NavBrandApp[]\n /**\n * Grouped apps - rendered as labeled sections separated by dividers. Takes\n * precedence over `apps`. The caller owns the grouping + ordering.\n */\n groups?: NavBrandGroup[]\n /** Heading above a flat `apps` list. Default `\"Applications\"`. Ignored for `groups`. */\n appsLabel?: string\n /** Extra menu content appended below the app list (e.g. an \"Add app\" row). */\n children?: ReactNode\n}\n\n/** Default English heading for the switcher's app list - override for i18n. */\nexport const defaultNavBrandAppsLabel = \"Applications\"\n\n/**\n * One renderer for every mark this component draws, so the header and the switcher rows cannot\n * drift apart. They did: the rows went through {@link AppGlyph} (image, monogram on failure) while\n * the header rendered its element raw, so opening the switcher showed the current app as a line\n * icon among everyone else's brand images.\n *\n * `own` says whose mark it is, and only that decides whether a root-relative path is honoured.\n */\nfunction Mark({ app, own, className }: { app: NavBrandApp; own?: boolean; className?: string }) {\n if (typeof app.icon !== \"string\") return <>{app.icon}</>\n return <AppGlyph icon={app.icon} name={app.name} className={className} ownOrigin={own} />\n}\n\n/**\n * Whether this mark needs chrome around it - the header's colour plate, a row's hairline box.\n *\n * Decided from the DATA, not from a `:has(img)` test. A string mark arrives with its own surface\n * either way: {@link AppGlyph} puts nothing behind an image, and `bg-primary/10` behind the monogram\n * it degrades to. Only the image branch contains an `<img>`, so a CSS test got that one right and\n * left the chrome around the fallback - in the header that meant the brand plate standing under a\n * letter painted in `text-primary`, a token for another surface, over two stacked backgrounds.\n * `typeof` answers before either branch renders.\n */\nfunction needsChrome(app: NavBrandApp): boolean {\n return typeof app.icon !== \"string\"\n}\n\n/** Icon tile + name/description shown inside the header button. */\nfunction BrandContent({ app }: { app: NavBrandApp }) {\n return (\n <>\n <div\n className={cn(\n \"flex aspect-square size-8 items-center justify-center rounded-none\",\n needsChrome(app) && \"bg-sidebar-primary text-sidebar-primary-foreground [&_svg]:size-4\",\n )}\n >\n <Mark app={app} own className=\"size-full text-xs\" />\n </div>\n <div className=\"grid flex-1 text-left text-sm leading-tight\">\n <span className=\"truncate font-semibold\">{app.name}</span>\n {app.description ? (\n <span className=\"text-muted-foreground truncate text-xs\">\n {app.description}\n </span>\n ) : null}\n </div>\n </>\n )\n}\n\n/** A single switch-to-app row (full-navigation link). */\nfunction AppRow({ app }: { app: NavBrandApp }) {\n return (\n <DropdownMenuItem asChild className=\"gap-2 p-2\">\n <a href={app.href}>\n <div\n className={cn(\n \"flex size-6 items-center justify-center rounded-none\",\n needsChrome(app) && \"border [&_svg]:size-3.5\",\n )}\n >\n <Mark app={app} className=\"size-full text-[10px]\" />\n </div>\n <span className=\"truncate\">{app.name}</span>\n </a>\n </DropdownMenuItem>\n )\n}\n\n/**\n * Sidebar-header brand block with an optional app switcher - the standardized\n * `SidebarHeader` content for app shells (pairs with {@link NavUser} in the\n * footer). Presentational: the app owns the config, icons, and labels.\n *\n * With `groups` (or `apps`), the header is a dropdown trigger listing the other\n * apps as links; `groups` renders labeled sections, `apps` a single flat list.\n * Without either, the header is a plain link to `app.href`. Cross-app links use\n * a plain `<a>` so switching does a full navigation.\n *\n * Must render inside an `AppShell`/`SidebarProvider`, and below a\n * `TooltipProvider` (the collapsed-mode tooltip needs one).\n *\n * @example\n * <NavBrand\n * app={{ name: \"Admin\", description: \"Platform console\", icon: <ShieldCheck />, href: \"/\" }}\n * groups={[\n * { key: \"workspace\", label: \"Workspace\", apps: [{ name: \"Drive\", icon: <HardDrive />, href: \"https://drive.tradalab.com\" }] },\n * ]}\n * />\n */\nexport function NavBrand({\n app,\n apps,\n groups,\n appsLabel = defaultNavBrandAppsLabel,\n children,\n}: NavBrandProps) {\n const { isMobile } = useSidebar()\n const groupTotal = groups?.reduce((n, g) => n + g.apps.length, 0) ?? 0\n const hasSwitcher = (apps?.length ?? 0) > 0 || groupTotal > 0 || children != null\n\n return (\n <SidebarMenu>\n <SidebarMenuItem>\n {hasSwitcher ? (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <SidebarMenuButton\n size=\"lg\"\n tooltip={app.name}\n className=\"data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground\"\n >\n <BrandContent app={app} />\n <ChevronsUpDown className=\"ml-auto size-4\" />\n </SidebarMenuButton>\n </DropdownMenuTrigger>\n <DropdownMenuContent\n className=\"w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-none\"\n align=\"start\"\n side={isMobile ? \"bottom\" : \"right\"}\n sideOffset={4}\n >\n {groupTotal > 0 ? (\n groups!.map((g, i) => (\n <Fragment key={g.key}>\n {i > 0 ? <DropdownMenuSeparator /> : null}\n {g.label ? (\n <DropdownMenuLabel className=\"text-muted-foreground text-xs\">\n {g.label}\n </DropdownMenuLabel>\n ) : null}\n {g.apps.map((item) => (\n <AppRow key={item.href} app={item} />\n ))}\n </Fragment>\n ))\n ) : (\n <>\n <DropdownMenuLabel className=\"text-muted-foreground text-xs\">\n {appsLabel}\n </DropdownMenuLabel>\n {apps?.map((item) => (\n <AppRow key={item.href} app={item} />\n ))}\n </>\n )}\n {children ? (\n <>\n <DropdownMenuSeparator />\n {children}\n </>\n ) : null}\n </DropdownMenuContent>\n </DropdownMenu>\n ) : (\n <SidebarMenuButton size=\"lg\" asChild tooltip={app.name}>\n <a href={app.href}>\n <BrandContent app={app} />\n </a>\n </SidebarMenuButton>\n )}\n </SidebarMenuItem>\n </SidebarMenu>\n )\n}\n","\"use client\"\n\nimport {\n NavBrand,\n type NavBrandApp,\n type NavBrandGroup,\n} from \"./nav-brand.js\"\n\n/**\n * A registry app in the {@link AppSwitcher} - the shape a launcher endpoint\n * returns (icon as a URL string; the glyph is rendered internally). Structurally\n * matches nekit's `LauncherApp`, so `groupApps(...)` output feeds straight in.\n */\nexport interface AppSwitcherApp {\n name: string\n description?: string\n /** Navigation target (usually an absolute cross-app URL). */\n url: string\n /** Image URL; anything else renders a monogram. */\n icon: string\n}\n\n/** A grouped, ordered section of apps (label omitted for the ungrouped bucket). */\nexport interface AppSwitcherSection {\n key: string\n label?: string\n apps: AppSwitcherApp[]\n}\n\nexport interface AppSwitcherProps {\n /** The current app, shown in the sidebar header. */\n app: NavBrandApp\n /** Grouped registry apps - e.g. nekit `groupApps(data.apps, data.groups)`. */\n sections: AppSwitcherSection[]\n /** Heading for the flat fallback list. */\n appsLabel?: string\n /** Shown when `sections` is empty (registry loading / unavailable) so the\n * switcher never collapses to a plain link. */\n fallback?: NavBrandApp[]\n}\n\n/**\n * The cross-app switcher: a batteries-included wrapper over {@link NavBrand} that\n * takes the launcher registry (grouped `sections` of `{ icon, url, … }`)\n * and renders it - labeled group sections, each app's mark drawn by `NavBrand`.\n * Consumers just fetch + group (nekit) and pass config. Empty registry → the `fallback` flat list.\n *\n * @example\n * const sections = groupApps(data.apps, data.groups) // @tradalab/nekit/data/launcher\n * <AppSwitcher app={{ name: \"Admin\", icon: <ShieldCheck/>, href: \"/\" }} sections={sections} />\n */\nexport function AppSwitcher({\n app,\n sections,\n appsLabel,\n fallback,\n}: AppSwitcherProps) {\n const groups: NavBrandGroup[] = sections\n .filter((s) => s.apps.length > 0)\n .map((s) => ({\n key: s.key,\n label: s.label,\n apps: s.apps.map((a) => ({\n name: a.name,\n description: a.description,\n href: a.url,\n // The registry URL, passed through. NavBrand draws every mark it shows - header and rows\n // alike - so wrapping it in AppGlyph here as well is the second renderer that let the two\n // drift apart in the first place.\n icon: a.icon,\n })),\n }))\n\n return (\n <NavBrand\n app={app}\n groups={groups.length ? groups : undefined}\n apps={groups.length ? undefined : fallback}\n appsLabel={appsLabel}\n />\n )\n}\n","\"use client\"\n\nimport type { ReactNode } from \"react\"\nimport {\n ChevronsUpDown,\n CircleUser,\n LogIn,\n LogOut,\n Monitor,\n Moon,\n Palette,\n Sun,\n UserRoundCog,\n} from \"lucide-react\"\nimport { useTheme } from \"next-themes\"\nimport { Avatar, AvatarFallback, AvatarImage } from \"~/ui/primitives/avatar.js\"\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuRadioGroup,\n DropdownMenuRadioItem,\n DropdownMenuSeparator,\n DropdownMenuSub,\n DropdownMenuSubContent,\n DropdownMenuSubTrigger,\n DropdownMenuTrigger,\n} from \"~/ui/primitives/dropdown-menu.js\"\nimport {\n SidebarMenu,\n SidebarMenuButton,\n SidebarMenuItem,\n useSidebar,\n} from \"~/ui/primitives/sidebar.js\"\nimport {\n defaultAccountMenuLabels,\n initialsOf,\n type AccountMenuLabels,\n type AccountMenuUser,\n} from \"~/ui/account-menu.js\"\nimport { PlatePicker, type PlatePickerProps } from \"~/ui/plate-picker.js\"\nimport { Switch } from \"~/ui/primitives/switch.js\"\nimport { useCompact, usePlate } from \"~/hooks/index.js\"\n\nexport interface NavUserThemeLabels {\n theme: string\n light: string\n dark: string\n system: string\n /** Submenu trigger. It covers theme, accent and density, so it is not called \"Theme\". */\n appearance: string\n accent: string\n /** The neutral option in the accent row. */\n accentDefault: string\n compact: string\n}\n\n/** Default English appearance-submenu labels - spread + override for i18n. */\nexport const defaultNavUserThemeLabels: NavUserThemeLabels = {\n theme: \"Theme\",\n light: \"Light\",\n dark: \"Dark\",\n system: \"System\",\n appearance: \"Appearance\",\n accent: \"Accent\",\n accentDefault: \"Default\",\n compact: \"Compact\",\n}\n\nexport interface NavUserProps {\n /**\n * Signed-in user. When `null`/`undefined`, renders a guest menu (icon +\n * theme + Sign in) if `onSignIn` is set, otherwise renders nothing.\n */\n user?: AccountMenuUser | null\n /** Account-action labels — reuses lyra's {@link AccountMenuLabels}. */\n labels?: Partial<AccountMenuLabels>\n /** Switch-account handler (signed-in only). Item hidden if omitted. */\n onSwitchAccount?: () => void\n /** Sign-out handler (signed-in only). Item hidden if omitted. */\n onSignOut?: () => void\n /**\n * Sign-in handler (guest only). When set, a null `user` renders the guest\n * menu with a Sign in action instead of rendering nothing.\n */\n onSignIn?: () => void\n /** Render the light/dark/system theme submenu. Default `true`. */\n showTheme?: boolean\n /** Appearance-submenu labels. */\n themeLabels?: Partial<NavUserThemeLabels>\n /**\n * Translated name per plate, passed through to the accent row. Falls back to the plate's own key,\n * which is English: fine while the swatch is the thing a reader picks by, wrong if an app wants the\n * names read out, and the reason this is a prop rather than 19 strings baked in here.\n */\n plateLabels?: PlatePickerProps[\"plateLabels\"]\n /** Extra menu content inserted below the appearance submenu. */\n children?: ReactNode\n}\n\nconst THEMES = [\n { value: \"light\", key: \"light\", icon: Sun },\n { value: \"dark\", key: \"dark\", icon: Moon },\n { value: \"system\", key: \"system\", icon: Monitor },\n] as const\n\n/**\n * Sidebar-footer account menu — the standardized NavUser for app shells.\n * Avatar trigger (size `lg`) + name/email opening a dropdown with an optional\n * theme switcher and switch-account / sign-out actions. Auth flows stay in the\n * app (callbacks); labels are props for i18n.\n *\n * Differs from {@link AccountMenu} in `@tradalab/lyra/ui`: that one is a\n * generic header dropdown; this is styled for the collapsible sidebar footer\n * and follows `useSidebar()` for mobile placement.\n *\n * Must render inside an `AppShell`/SidebarProvider, and below a\n * `TooltipProvider` (the avatar button's collapsed-mode tooltip requires one —\n * app root providers normally mount it). The theme submenu needs a\n * `next-themes` provider mounted (it no-ops without one); pass\n * `showTheme={false}` for apps with a fixed theme. It drops the three theme rows and nothing else:\n * the Appearance submenu stays, because accent and density are per-device choices that a fixed\n * theme has no opinion about.\n *\n * @example\n * import { NavUser } from \"@tradalab/lyra/shell\"\n * import { SidebarFooter } from \"@tradalab/lyra/ui\"\n *\n * <SidebarFooter>\n * <NavUser\n * user={session.user}\n * onSwitchAccount={() => signIn(\"zitadel\", { redirectTo: \"/\" }, { prompt: \"select_account\" })}\n * onSignOut={() => signOut({ redirectTo: \"/\" })}\n * />\n * </SidebarFooter>\n */\nexport function NavUser({\n user,\n labels,\n onSwitchAccount,\n onSignOut,\n onSignIn,\n showTheme = true,\n themeLabels,\n plateLabels,\n children,\n}: NavUserProps) {\n const { isMobile } = useSidebar()\n const { theme, setTheme } = useTheme()\n // Both read localStorage and write an attribute on `<html>`: device-local, like the theme beside\n // them, and nothing here needs a session.\n const [plate, setPlate] = usePlate()\n const [compact, setCompact] = useCompact()\n const l = { ...defaultAccountMenuLabels, ...labels }\n const t = { ...defaultNavUserThemeLabels, ...themeLabels }\n\n // Guests render nothing unless a sign-in handler opts them into a guest menu.\n if (!user && !onSignIn) return null\n\n const name = user ? (user.name ?? user.email ?? l.account) : l.guest\n const email = user?.email ?? \"\"\n\n const avatar = (\n <Avatar className=\"size-8 rounded-none after:rounded-none\">\n {user?.image ? (\n <AvatarImage className=\"rounded-none\" src={user.image} alt={name} />\n ) : null}\n <AvatarFallback className=\"rounded-none\">\n {user ? initialsOf(name) : <CircleUser className=\"size-4\" />}\n </AvatarFallback>\n </Avatar>\n )\n\n // Theme, accent and density in one submenu, because they are one question - what this looks like\n // on THIS device - and because three separate rows would push the account actions out of reach.\n //\n // The accent row and the density switch are deliberately NOT `DropdownMenuItem`s. A menu item\n // closes the menu when it is selected, and picking a colour is something a reader does two or\n // three times before settling; a swatch grid that shut the menu on the first click would be worse\n // than no grid. Raw controls inside `DropdownMenuSubContent` keep it open.\n // `showTheme` gates the THEME ROWS ONLY, not the submenu. It has always meant \"this app has a\n // fixed theme\" - `trade` sets it for exactly that reason and says so - so letting it also strip\n // the accent and the density switch would widen a flag's meaning under the one app that uses it,\n // and nothing would say so: the controls would simply not be there.\n const appearanceSubmenu = (\n <>\n <DropdownMenuSeparator />\n <DropdownMenuSub>\n <DropdownMenuSubTrigger>\n <Palette className=\"mr-2 size-4\" />\n {t.appearance}\n </DropdownMenuSubTrigger>\n <DropdownMenuSubContent className=\"w-56\">\n {showTheme ? (\n <>\n <DropdownMenuRadioGroup value={theme ?? \"system\"} onValueChange={setTheme}>\n {THEMES.map(({ value, key, icon: Icon }) => (\n <DropdownMenuRadioItem key={value} value={value}>\n <Icon className=\"mr-2 size-4\" />\n {t[key]}\n </DropdownMenuRadioItem>\n ))}\n </DropdownMenuRadioGroup>\n <DropdownMenuSeparator />\n </>\n ) : null}\n\n <DropdownMenuLabel className=\"text-muted-foreground text-xs font-normal\">\n {t.accent}\n </DropdownMenuLabel>\n <div className=\"px-2 pb-1.5\">\n <PlatePicker\n value={plate}\n onChange={setPlate}\n label={t.accent}\n resetLabel={t.accentDefault}\n plateLabels={plateLabels}\n className=\"gap-1.5\"\n />\n </div>\n\n <DropdownMenuSeparator />\n <label className=\"flex cursor-pointer items-center justify-between gap-2 px-2 py-1.5 text-sm\">\n {t.compact}\n <Switch checked={compact} onCheckedChange={setCompact} />\n </label>\n </DropdownMenuSubContent>\n </DropdownMenuSub>\n </>\n )\n\n return (\n <SidebarMenu>\n <SidebarMenuItem>\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <SidebarMenuButton\n size=\"lg\"\n tooltip={name}\n className=\"data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground\"\n >\n {avatar}\n <div className=\"grid flex-1 text-left text-sm leading-tight\">\n <span className=\"truncate font-medium\">{name}</span>\n {email ? (\n <span className=\"text-muted-foreground truncate text-xs\">\n {email}\n </span>\n ) : null}\n </div>\n <ChevronsUpDown className=\"ml-auto size-4\" />\n </SidebarMenuButton>\n </DropdownMenuTrigger>\n <DropdownMenuContent\n className=\"w-(--radix-dropdown-menu-trigger-width) min-w-56\"\n side={isMobile ? \"bottom\" : \"right\"}\n align=\"end\"\n sideOffset={4}\n >\n <DropdownMenuLabel className=\"flex items-center gap-2\">\n {avatar}\n <div className=\"grid flex-1 text-left leading-tight\">\n <span className=\"truncate text-sm font-medium\">{name}</span>\n {email ? (\n <span className=\"text-muted-foreground truncate text-xs font-normal\">\n {email}\n </span>\n ) : null}\n </div>\n </DropdownMenuLabel>\n\n {appearanceSubmenu}\n\n {children ? (\n <>\n <DropdownMenuSeparator />\n {children}\n </>\n ) : null}\n\n {user ? (\n <>\n {onSwitchAccount || onSignOut ? <DropdownMenuSeparator /> : null}\n {onSwitchAccount ? (\n <DropdownMenuItem onSelect={onSwitchAccount}>\n <UserRoundCog className=\"mr-2 size-4\" />\n {l.switchAccount}\n </DropdownMenuItem>\n ) : null}\n {onSignOut ? (\n <DropdownMenuItem variant=\"destructive\" onSelect={onSignOut}>\n <LogOut className=\"mr-2 size-4\" />\n {l.signOut}\n </DropdownMenuItem>\n ) : null}\n </>\n ) : (\n <>\n <DropdownMenuSeparator />\n <DropdownMenuItem onSelect={onSignIn}>\n <LogIn className=\"mr-2 size-4\" />\n {l.signIn}\n </DropdownMenuItem>\n </>\n )}\n </DropdownMenuContent>\n </DropdownMenu>\n </SidebarMenuItem>\n </SidebarMenu>\n )\n}\n","\"use client\"\n\nimport { useEffect, useRef, type ReactNode, type WheelEvent } from \"react\"\nimport { ChevronDown, ListX, Pin, PinOff, Trash2, X } from \"lucide-react\"\nimport { Button } from \"~/ui/primitives/button.js\"\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from \"~/ui/primitives/dropdown-menu.js\"\nimport { ScrollArea, ScrollBar } from \"~/ui/primitives/scroll-area.js\"\nimport {\n ContextMenu,\n ContextMenuContent,\n ContextMenuItem,\n ContextMenuSeparator,\n ContextMenuTrigger,\n} from \"~/ui/primitives/context-menu.js\"\nimport { cn } from \"~/utils/cn.js\"\n\nexport interface TabItem {\n id: string\n title: string\n /** Secondary line under the title (e.g. connection/db, file path). */\n subtitle?: ReactNode\n /** Leading icon element — the app maps its own tab type → icon. */\n icon?: ReactNode\n pinned?: boolean\n}\n\nexport interface TabBarLabels {\n close: string\n pinTab: string\n unpinTab: string\n closeOthers: string\n closeAll: string\n}\n\n/** Default English labels — spread + override for i18n. */\nexport const defaultTabBarLabels: TabBarLabels = {\n close: \"Close\",\n pinTab: \"Pin tab\",\n unpinTab: \"Unpin tab\",\n closeOthers: \"Close others\",\n closeAll: \"Close all\",\n}\n\nexport interface TabBarProps {\n tabs: TabItem[]\n activeId: string | null\n onSelect: (id: string) => void\n onClose: (id: string) => void\n /** Pin row in the context menu is hidden when omitted. */\n onTogglePin?: (id: string) => void\n /** \"Close others\" row is hidden when omitted. */\n onCloseOthers?: (id: string) => void\n /** \"Close all\" row is hidden when omitted. */\n onCloseAll?: () => void\n labels?: Partial<TabBarLabels>\n}\n\n/**\n * Horizontal tab strip for the content area of stacked-sidebar apps\n * (redishub, loom). Presentational + store-agnostic: the app owns tab state\n * and passes `tabs` + callbacks; `icon`/`subtitle` are per-tab so apps map\n * their own tab types, and labels are props for i18n. Renders nothing when\n * `tabs` is empty.\n *\n * Sizes rows from the `--tab-height` CSS var — set it in your globals.\n *\n * @example\n * const s = useTabStore()\n * <TabBar\n * tabs={s.tabs.map(t => ({ id: t.id, title: t.title, pinned: t.pinned, icon: <Icon t={t.type}/>, subtitle: fmt(t) }))}\n * activeId={s.activeTabId}\n * onSelect={s.setActiveTabId}\n * onClose={s.removeTab}\n * onTogglePin={s.togglePin}\n * onCloseOthers={s.closeOthers}\n * onCloseAll={s.closeAll}\n * />\n */\nexport function TabBar({\n tabs,\n activeId,\n onSelect,\n onClose,\n onTogglePin,\n onCloseOthers,\n onCloseAll,\n labels,\n}: TabBarProps) {\n const l = { ...defaultTabBarLabels, ...labels }\n const activeTabRef = useRef<HTMLDivElement>(null)\n\n // Scroll the active tab into view when it changes.\n useEffect(() => {\n if (activeId && activeTabRef.current) {\n activeTabRef.current.scrollIntoView({\n behavior: \"smooth\",\n block: \"nearest\",\n inline: \"nearest\",\n })\n }\n }, [activeId])\n\n // Map vertical wheel scroll to horizontal scroll over the tab strip.\n const handleWheel = (e: WheelEvent<HTMLDivElement>) => {\n if (Math.abs(e.deltaY) > Math.abs(e.deltaX)) {\n const viewport = e.currentTarget.querySelector(\n '[data-slot=\"scroll-area-viewport\"]',\n )\n if (viewport) viewport.scrollLeft += e.deltaY * 0.5\n }\n }\n\n if (tabs.length === 0) return null\n\n const hasContextActions = Boolean(onTogglePin || onCloseOthers || onCloseAll)\n\n return (\n <div\n className=\"group/tabbar flex w-full shrink-0 items-center overflow-hidden border-b bg-muted/30\"\n style={{ height: \"var(--tab-height)\" }}\n >\n <ScrollArea\n type=\"hover\"\n className=\"min-w-0 flex-1 overflow-hidden [&_[data-orientation=vertical]]:hidden\"\n onWheel={handleWheel}\n >\n <div\n className=\"flex w-max items-center gap-0\"\n style={{ height: \"var(--tab-height)\" }}\n >\n {tabs.map((tab) => {\n const isActive = activeId === tab.id\n return (\n <ContextMenu key={tab.id}>\n <ContextMenuTrigger asChild>\n <div\n ref={isActive ? activeTabRef : null}\n onClick={() => onSelect(tab.id)}\n title={tab.title}\n className={cn(\n \"group relative flex min-w-[140px] max-w-[240px] shrink-0 cursor-pointer select-none items-center gap-2 border-r px-3 transition-colors\",\n isActive\n ? \"border-t-2 border-t-primary bg-background\"\n : \"border-t-2 border-t-transparent bg-transparent text-muted-foreground hover:bg-muted/50\",\n tab.pinned && \"min-w-[100px] bg-primary/5\",\n )}\n style={{ height: \"var(--tab-height)\" }}\n >\n {tab.icon ? (\n <div className=\"relative shrink-0\">\n <span\n className={cn(\n \"flex [&_svg]:size-3.5\",\n isActive && \"text-primary\",\n )}\n >\n {tab.icon}\n </span>\n {tab.pinned ? (\n <div className=\"absolute -right-1.5 -top-1.5 rounded-full bg-background p-0.5 shadow-sm\">\n <Pin className=\"size-2 fill-primary text-primary\" />\n </div>\n ) : null}\n </div>\n ) : null}\n <div className=\"flex min-w-0 flex-1 flex-col items-start justify-center overflow-hidden py-1\">\n <span\n className={cn(\n \"w-full truncate text-xs leading-tight\",\n isActive && \"font-medium text-foreground\",\n )}\n >\n {tab.title}\n </span>\n {tab.subtitle ? (\n <span className=\"mt-0.5 w-full truncate text-[10px] leading-none text-muted-foreground/70\">\n {tab.subtitle}\n </span>\n ) : null}\n </div>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n title={l.close}\n className={cn(\n \"size-5 shrink-0 rounded-md opacity-0 transition-opacity hover:bg-muted-foreground/20 group-hover:opacity-100\",\n isActive && \"opacity-100\",\n )}\n onClick={(e) => {\n e.stopPropagation()\n onClose(tab.id)\n }}\n >\n <X className=\"size-3\" />\n </Button>\n {isActive ? (\n <div className=\"absolute bottom-0 left-0 right-0 z-10 h-[2px] bg-background\" />\n ) : null}\n </div>\n </ContextMenuTrigger>\n {hasContextActions ? (\n <ContextMenuContent className=\"w-48\">\n {onTogglePin ? (\n <>\n <ContextMenuItem onClick={() => onTogglePin(tab.id)}>\n {tab.pinned ? (\n <>\n <PinOff className=\"mr-2 size-4\" />\n <span>{l.unpinTab}</span>\n </>\n ) : (\n <>\n <Pin className=\"mr-2 size-4\" />\n <span>{l.pinTab}</span>\n </>\n )}\n </ContextMenuItem>\n <ContextMenuSeparator />\n </>\n ) : null}\n <ContextMenuItem onClick={() => onClose(tab.id)}>\n <X className=\"mr-2 size-4\" />\n <span>{l.close}</span>\n </ContextMenuItem>\n {onCloseOthers ? (\n <ContextMenuItem onClick={() => onCloseOthers(tab.id)}>\n <ListX className=\"mr-2 size-4\" />\n <span>{l.closeOthers}</span>\n </ContextMenuItem>\n ) : null}\n {onCloseAll ? (\n <ContextMenuItem onClick={() => onCloseAll()}>\n <Trash2 className=\"mr-2 size-4\" />\n <span>{l.closeAll}</span>\n </ContextMenuItem>\n ) : null}\n </ContextMenuContent>\n ) : null}\n </ContextMenu>\n )\n })}\n </div>\n <ScrollBar orientation=\"horizontal\" className=\"z-20 h-1.5 hover:h-2\" />\n </ScrollArea>\n\n <div\n className=\"flex shrink-0 items-center border-l bg-muted/30 px-1\"\n style={{ height: \"var(--tab-height)\" }}\n >\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button variant=\"ghost\" size=\"icon\" className=\"size-8 shrink-0 rounded-sm\">\n <ChevronDown className=\"size-4\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent\n align=\"end\"\n className=\"max-h-[400px] w-[260px] overflow-y-auto\"\n >\n {tabs.map((tab) => (\n <DropdownMenuItem\n key={tab.id}\n onClick={() => onSelect(tab.id)}\n title={tab.title}\n className={cn(\n \"cursor-pointer gap-2 py-2\",\n activeId === tab.id && \"bg-accent\",\n )}\n >\n {tab.icon ? (\n <div className=\"relative shrink-0\">\n <span className=\"flex text-muted-foreground [&_svg]:size-4\">\n {tab.icon}\n </span>\n {tab.pinned ? (\n <Pin className=\"absolute -right-1 -top-1 size-2 fill-primary text-primary\" />\n ) : null}\n </div>\n ) : null}\n <div className=\"flex flex-1 flex-col items-start overflow-hidden leading-tight\">\n <span className=\"w-full flex-1 truncate text-sm\">{tab.title}</span>\n {tab.subtitle ? (\n <span className=\"w-full truncate text-[10px] text-muted-foreground\">\n {tab.subtitle}\n </span>\n ) : null}\n </div>\n <X\n className=\"size-4 shrink-0 rounded-sm p-0.5 hover:bg-red-50 hover:text-red-500\"\n onClick={(e) => {\n e.stopPropagation()\n onClose(tab.id)\n }}\n />\n </DropdownMenuItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n </div>\n )\n}\n","\"use client\"\n\nimport { Fragment, type ComponentProps, type ReactNode } from \"react\"\nimport Link from \"next/link\"\nimport {\n Breadcrumb,\n BreadcrumbItem,\n BreadcrumbLink,\n BreadcrumbList,\n BreadcrumbPage,\n BreadcrumbSeparator,\n} from \"~/ui/primitives/breadcrumb.js\"\nimport { useActiveRoute } from \"./use-active-route.js\"\n\nexport interface LeadingCrumb {\n /** Where the crumb links. Omit to render it as plain text. */\n href?: string\n label: ReactNode\n /** Hide on small screens, as app-name crumbs usually should. Default `true`. */\n hideOnMobile?: boolean\n}\n\nexport interface BreadcrumbsProps extends ComponentProps<typeof Breadcrumb> {\n /** Separator content. Falls back to shadcn's chevron when omitted. */\n separator?: ReactNode\n /** Segment-id → display-label map. Unmapped segments render raw. */\n labels?: Record<string, string>\n /**\n * Label a crumb from the path it links to, consulted before `labels`. Return\n * `undefined` to fall through.\n *\n * This exists so the nav can be the one place a route gets its name. Every app\n * already holds an `href → label` table to build the sidebar; the alternative\n * is a second table keyed by segment, and a second table drifts. Measured on\n * one app before this shipped: four routes the nav listed were missing from its\n * crumb table, one route had two names across the two, and a nested route was\n * swallowed by its parent's prefix so the crumb NAMED THE WRONG SCREEN. None of\n * that is visible on the screen that caused it.\n *\n * `labels` stays for an app small enough that its segments are already words.\n */\n resolve?: (href: string, segment: string) => ReactNode | undefined\n /**\n * Crumb rendered before the path segments — typically the app name linking\n * home. With it set the trail also renders on the root route (as the current\n * page); without it the root route renders nothing.\n */\n leading?: LeadingCrumb\n}\n\n/** Pathname-derived breadcrumbs. Renders nothing on the root route unless `leading` is set. */\nexport function Breadcrumbs({ separator, labels, resolve, leading, ...rest }: BreadcrumbsProps) {\n const { pathname } = useActiveRoute()\n const segments = pathname.split(\"/\").filter(Boolean)\n if (segments.length === 0 && !leading) return null\n const last = segments.length - 1\n const hideLeading = leading?.hideOnMobile ?? true\n return (\n <Breadcrumb {...rest}>\n <BreadcrumbList>\n {leading ? (\n <BreadcrumbItem className={hideLeading ? \"hidden md:block\" : undefined}>\n {/* On the root route the leading crumb IS the current page. */}\n {segments.length === 0 ? (\n <BreadcrumbPage>{leading.label}</BreadcrumbPage>\n ) : leading.href ? (\n <BreadcrumbLink asChild>\n <Link href={leading.href}>{leading.label}</Link>\n </BreadcrumbLink>\n ) : (\n leading.label\n )}\n </BreadcrumbItem>\n ) : null}\n {segments.map((seg, i) => {\n const href = \"/\" + segments.slice(0, i + 1).join(\"/\")\n const label = resolve?.(href, seg) ?? labels?.[seg] ?? seg\n return (\n <Fragment key={href}>\n {i > 0 || leading ? (\n <BreadcrumbSeparator className={i === 0 && hideLeading && leading ? \"hidden md:block\" : undefined}>\n {separator}\n </BreadcrumbSeparator>\n ) : null}\n <BreadcrumbItem>\n {i === last ? (\n <BreadcrumbPage>{label}</BreadcrumbPage>\n ) : (\n <BreadcrumbLink asChild>\n <Link href={href}>{label}</Link>\n </BreadcrumbLink>\n )}\n </BreadcrumbItem>\n </Fragment>\n )\n })}\n </BreadcrumbList>\n </Breadcrumb>\n )\n}\n","import type { ReactNode } from \"react\"\nimport { Search } from \"lucide-react\"\n\nimport { cn } from \"~/utils/cn.js\"\nimport {\n Card,\n CardDescription,\n CardTitle,\n} from \"~/ui/primitives/card.js\"\nimport {\n InputGroup,\n InputGroupAddon,\n InputGroupInput,\n} from \"~/ui/primitives/input-group.js\"\n\n/** One application in the {@link AppGrid} — rendered as an {@link AppTile}. */\nexport interface AppGridItem {\n /** Stable React list key. */\n key: string\n /** Display name. */\n name: string\n /** Optional tagline shown under the name. */\n description?: string\n /** Navigation target (usually an absolute cross-app URL). */\n href: string\n /**\n * Pre-rendered icon element (RSC-serializable), e.g. `<ToolCase />`. Sized by\n * the tile (`[&_svg]:size-7`) — same convention as {@link NavBrandApp}'s icon.\n */\n icon: ReactNode\n /**\n * Tailwind class string tinting the icon tile — `text-…` (colors the icon via\n * currentColor) + `bg-…` (tile background). Pass full class strings, not\n * interpolated fragments, so the JIT keeps them.\n */\n accent?: string\n /** Opens on a different origin (adds `target=\"_blank\"` + `rel`). */\n external?: boolean\n}\n\n/** A titled section of apps for {@link AppGrid}'s grouped mode. */\nexport interface AppGridGroup {\n /** Stable React list key. */\n key: string\n /** Section heading. */\n label: string\n /** Optional sub-heading under the label. */\n description?: string\n /** Apps in this group — already filtered by the caller. */\n apps: AppGridItem[]\n}\n\nexport interface AppGridProps {\n /** Flat mode: apps to render (already filtered). Ignored when `groups` is set. */\n apps?: AppGridItem[]\n /**\n * Grouped mode: titled sections (already filtered). When provided, renders a\n * heading + tile grid per group; empty groups are dropped.\n */\n groups?: AppGridGroup[]\n /** Controlled search value. Pass together with `onQueryChange` to show the search box. */\n query?: string\n onQueryChange?: (query: string) => void\n /** Search input placeholder. */\n searchPlaceholder?: string\n /** Autofocus the search box on mount. Default `false`. */\n autoFocusSearch?: boolean\n /** Message shown when there are no apps. */\n emptyLabel?: string\n className?: string\n}\n\n/** Default English labels — override for i18n. */\nexport const defaultAppGridLabels = {\n searchPlaceholder: \"Search apps…\",\n empty: \"No apps found.\",\n}\n\n/**\n * A responsive grid of application tiles with an optional search box — the \"app\n * grid\" / launcher surface (a portal home / \"all apps\" page). Presentational:\n * the app owns the registry, i18n, icons, and the filter — pass the already-\n * filtered `apps` plus a controlled `query`. Same app/lyra split as\n * {@link NavBrand} and {@link TabBar}.\n *\n * @example\n * const [q, setQ] = useState(\"\")\n * const filtered = APPS.filter(a => match(a, q))\n * <AppGrid\n * apps={filtered.map(a => ({\n * key: a.slug, name: t(a.slug), description: t(`${a.slug}.desc`),\n * href: a.href, icon: <a.Icon />, accent: a.accent, external: true,\n * }))}\n * query={q} onQueryChange={setQ}\n * searchPlaceholder={t(\"search\")} emptyLabel={t(\"empty\")}\n * />\n */\nexport function AppGrid({\n apps,\n groups,\n query,\n onQueryChange,\n searchPlaceholder = defaultAppGridLabels.searchPlaceholder,\n autoFocusSearch = false,\n emptyLabel = defaultAppGridLabels.empty,\n className,\n}: AppGridProps) {\n const grouped = groups != null\n const sections = grouped ? groups.filter((g) => g.apps.length > 0) : []\n const flat = apps ?? []\n const isEmpty = grouped ? sections.length === 0 : flat.length === 0\n\n return (\n <div className={cn(\"space-y-8\", className)}>\n {onQueryChange ? (\n <InputGroup className=\"mx-auto h-10 max-w-md\">\n <InputGroupAddon>\n <Search />\n </InputGroupAddon>\n <InputGroupInput\n type=\"search\"\n value={query ?? \"\"}\n onChange={(e) => onQueryChange(e.target.value)}\n placeholder={searchPlaceholder}\n aria-label={searchPlaceholder}\n autoFocus={autoFocusSearch}\n />\n </InputGroup>\n ) : null}\n\n {isEmpty ? (\n <p className=\"text-muted-foreground py-16 text-center text-sm\">\n {emptyLabel}\n </p>\n ) : grouped ? (\n <div className=\"space-y-10\">\n {sections.map((g) => (\n <section key={g.key} className=\"space-y-4\">\n <div className=\"space-y-1\">\n <h2 className=\"text-sm font-semibold tracking-tight\">{g.label}</h2>\n {g.description ? (\n <p className=\"text-muted-foreground text-xs\">{g.description}</p>\n ) : null}\n </div>\n <AppTileGrid apps={g.apps} />\n </section>\n ))}\n </div>\n ) : (\n <AppTileGrid apps={flat} />\n )}\n </div>\n )\n}\n\n/** The responsive tile grid shared by {@link AppGrid}'s flat and grouped modes. */\nfunction AppTileGrid({ apps }: { apps: AppGridItem[] }) {\n return (\n <div className=\"grid grid-cols-2 gap-4 sm:grid-cols-3 lg:grid-cols-4\">\n {apps.map((app) => (\n <AppTile key={app.key} app={app} />\n ))}\n </div>\n )\n}\n\n/**\n * A single application tile: the icon in a tinted rounded chip above the name +\n * description. Exported for callers that render their own grid.\n */\nexport function AppTile({ app }: { app: AppGridItem }) {\n return (\n <a\n href={app.href}\n target={app.external ? \"_blank\" : undefined}\n rel={app.external ? \"noreferrer\" : undefined}\n className=\"focus-visible:ring-ring group rounded-none outline-none focus-visible:ring-2\"\n >\n <Card className=\"h-full items-center gap-4 px-6 py-8 text-center transition-all duration-200 group-hover:-translate-y-0.5 group-hover:shadow-md group-hover:ring-foreground/30\">\n {/* Tinted icon tile (sharp — matches lyra's rounded-none aesthetic);\n `accent` carries the text-… (icon, via currentColor) + bg-… tint. */}\n {/* An app image arrives with its own plate, so the hairline ring would frame it twice. */}\n <div\n className={cn(\n \"flex size-14 items-center justify-center rounded-none ring-1 ring-foreground/5 ring-inset transition-transform duration-200 group-hover:scale-105 has-[img]:ring-0 [&_svg]:size-7\",\n app.accent,\n )}\n >\n {app.icon}\n </div>\n <div className=\"space-y-1\">\n <CardTitle>{app.name}</CardTitle>\n {app.description ? (\n <CardDescription className=\"line-clamp-2\">\n {app.description}\n </CardDescription>\n ) : null}\n </div>\n </Card>\n </a>\n )\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tradalab/lyra",
3
- "version": "3.6.0",
3
+ "version": "3.6.1",
4
4
  "description": "Tradalab UI kit — shadcn (radix-lyra) primitives, composed components, and app shell for React 19 / Next 16 / Tailwind v4.",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,