{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "app-shell",
  "type": "registry:component",
  "title": "App shell",
  "description": "Sidebar column, content frame with a slim banner slot, mobile bar and navigation sheet; no top bar.",
  "registryDependencies": [
    "@wibo/utils",
    "@wibo/sheet",
    "@wibo/tooltip"
  ],
  "files": [
    {
      "path": "src/shell/app-shell.tsx",
      "type": "registry:component",
      "content": "import {\n  createContext,\n  useCallback,\n  useContext,\n  useMemo,\n  useState,\n  type AnchorHTMLAttributes,\n  type ComponentProps,\n  type ComponentType,\n  type ReactNode,\n} from \"react\";\nimport { Menu } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\nimport { Sheet, SheetContent, SheetDescription, SheetTitle } from \"@/components/ui/sheet\";\nimport { TooltipProvider } from \"@/components/ui/tooltip\";\n\n/**\n * A router link component (for example Next's `Link`). Packages never import\n * a router: the app passes its link component to `AppShell` once.\n */\ntype LinkComponent = ComponentType<AnchorHTMLAttributes<HTMLAnchorElement> & { href: string }>;\n\n/**\n * The router's pending state of the link it is called under (Next's\n * `useLinkStatus` from `next/link`). Called inside the rendered link only.\n */\ntype LinkStatusHook = () => { pending: boolean };\n\ninterface AppShellContextValue {\n  /** Desktop sidebar reduced to an icon rail. */\n  collapsed: boolean;\n  setCollapsed: (collapsed: boolean) => void;\n  toggleCollapsed: () => void;\n  /** Mobile navigation sheet. */\n  mobileOpen: boolean;\n  setMobileOpen: (open: boolean) => void;\n  linkComponent: LinkComponent | \"a\";\n  useLinkStatus: LinkStatusHook;\n}\n\nconst AppShellContext = createContext<AppShellContextValue | null>(null);\n\n/** Where a sidebar is rendered: the desktop column or the mobile sheet. */\nconst SidebarPlacementContext = createContext<\"desktop\" | \"sheet\">(\"desktop\");\n\nconst noLinkStatus: LinkStatusHook = () => ({ pending: false });\n\nconst fallbackContext: AppShellContextValue = {\n  collapsed: false,\n  setCollapsed: () => {},\n  toggleCollapsed: () => {},\n  mobileOpen: false,\n  setMobileOpen: () => {},\n  linkComponent: \"a\",\n  useLinkStatus: noLinkStatus,\n};\n\n/** Shell state for sidebar, topbar and nav items. Works outside `AppShell` with inert defaults. */\nfunction useAppShell(): AppShellContextValue {\n  return useContext(AppShellContext) ?? fallbackContext;\n}\n\nfunction useSidebarPlacement(): \"desktop\" | \"sheet\" {\n  return useContext(SidebarPlacementContext);\n}\n\ninterface AppShellProps extends Omit<ComponentProps<\"div\">, \"children\"> {\n  /** A `Sidebar` element. Rendered in the left column on desktop and in a sheet on mobile. */\n  sidebar: ReactNode;\n  /**\n   * Optional bar across the top of the content frame (`Topbar`). The\n   * dashboards pass none: the page header carries the breadcrumb, and below\n   * `md` the shell draws its own mobile bar with the menu button.\n   */\n  topbar?: ReactNode;\n  /** Slim banner at the top of the content frame, above the page: `StaffBanner`. */\n  banner?: ReactNode;\n  /** Name shown next to the menu button in the mobile bar (the product or workspace). */\n  mobileTitle?: ReactNode;\n  children: ReactNode;\n  collapsed?: boolean;\n  defaultCollapsed?: boolean;\n  onCollapsedChange?: (collapsed: boolean) => void;\n  linkComponent?: LinkComponent;\n  /** The router's link status hook (Next: `useLinkStatus`), for instant feedback on clicked nav rows. */\n  useLinkStatus?: LinkStatusHook;\n  /** Accessible name of the mobile navigation sheet. */\n  navigationLabel?: string;\n  /** Accessible name of the mobile menu button. */\n  openNavigationLabel?: string;\n  /** Accessible name of the close button of the phone navigation sheet. */\n  closeNavigationLabel?: string;\n  skipLinkLabel?: string;\n}\n\n/**\n * Product layout in the Mastra Studio arrangement: the sidebar sits on the\n * darkest surface, the page lives in a raised frame inset by 8 px on the\n * other three sides. No top bar: the signed-in user sits at the foot of the\n * sidebar and each page carries its own breadcrumb (`PageHeader`). Below\n * `md` a slim bar holds the menu button that opens the sidebar in a sheet.\n * Sidebar 240 px wide, 64 px as an icon rail.\n */\nfunction AppShell({\n  sidebar,\n  topbar,\n  banner,\n  mobileTitle,\n  children,\n  collapsed: collapsedProp,\n  defaultCollapsed = false,\n  onCollapsedChange,\n  linkComponent,\n  useLinkStatus,\n  navigationLabel = \"Navigation\",\n  openNavigationLabel = \"Open navigation\",\n  closeNavigationLabel = \"Close\",\n  skipLinkLabel = \"Skip to content\",\n  className,\n  ...props\n}: AppShellProps) {\n  const [internalCollapsed, setInternalCollapsed] = useState(defaultCollapsed);\n  const [mobileOpen, setMobileOpen] = useState(false);\n  const collapsed = collapsedProp ?? internalCollapsed;\n\n  const setCollapsed = useCallback(\n    (next: boolean) => {\n      if (collapsedProp === undefined) setInternalCollapsed(next);\n      onCollapsedChange?.(next);\n    },\n    [collapsedProp, onCollapsedChange],\n  );\n\n  const value = useMemo<AppShellContextValue>(\n    () => ({\n      collapsed,\n      setCollapsed,\n      toggleCollapsed: () => setCollapsed(!collapsed),\n      mobileOpen,\n      setMobileOpen,\n      linkComponent: linkComponent ?? \"a\",\n      useLinkStatus: useLinkStatus ?? noLinkStatus,\n    }),\n    [collapsed, setCollapsed, mobileOpen, linkComponent, useLinkStatus],\n  );\n\n  return (\n    <AppShellContext.Provider value={value}>\n      <TooltipProvider>\n        <div\n          data-slot=\"app-shell\"\n          data-collapsed={collapsed || undefined}\n          className={cn(\"flex h-dvh flex-col bg-sidebar text-foreground\", className)}\n          {...props}\n        >\n          <a\n            href=\"#main-content\"\n            className=\"sr-only z-50 rounded-control bg-primary px-3 py-1.5 text-label font-medium text-primary-foreground focus:not-sr-only focus:fixed focus:top-2 focus:left-2\"\n          >\n            {skipLinkLabel}\n          </a>\n          <div className=\"flex min-h-0 flex-1\">\n            <div\n              data-slot=\"app-shell-sidebar\"\n              className={cn(\n                \"hidden shrink-0 transition-[width] duration-300 ease-glide md:flex\",\n                collapsed ? \"w-16\" : \"w-60\",\n              )}\n            >\n              {sidebar}\n            </div>\n            <Sheet open={mobileOpen} onOpenChange={setMobileOpen}>\n              <SheetContent side=\"left\" closeLabel={closeNavigationLabel} className=\"w-72 max-w-[85vw] gap-0 border-0 bg-sidebar p-0\">\n                <SheetTitle className=\"sr-only\">{navigationLabel}</SheetTitle>\n                <SheetDescription className=\"sr-only\">{navigationLabel}</SheetDescription>\n                <SidebarPlacementContext.Provider value=\"sheet\">{sidebar}</SidebarPlacementContext.Provider>\n              </SheetContent>\n            </Sheet>\n            <div data-slot=\"app-shell-content\" className=\"flex min-w-0 flex-1 flex-col md:py-2 md:pr-2\">\n              <div\n                data-slot=\"app-shell-frame\"\n                className=\"flex min-h-0 flex-1 flex-col overflow-hidden bg-background md:rounded-xl md:border md:border-border md:shadow-card\"\n              >\n                {banner}\n                {topbar ?? (\n                  <div\n                    data-slot=\"app-shell-mobile-bar\"\n                    className=\"flex h-12 shrink-0 items-center gap-2 px-2 md:hidden\"\n                  >\n                    <button\n                      type=\"button\"\n                      onClick={() => setMobileOpen(true)}\n                      aria-label={openNavigationLabel}\n                      className=\"press-icon inline-flex size-8.5 shrink-0 cursor-pointer items-center justify-center rounded-md text-muted-foreground outline-none hover:bg-foreground/4 hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring/50 [&_svg]:size-4\"\n                    >\n                      <Menu aria-hidden=\"true\" />\n                    </button>\n                    {mobileTitle ? (\n                      <span className=\"min-w-0 truncate text-sm font-semibold tracking-tight\">{mobileTitle}</span>\n                    ) : null}\n                  </div>\n                )}\n                <main\n                  id=\"main-content\"\n                  data-slot=\"app-shell-main\"\n                  tabIndex={-1}\n                  className=\"min-h-0 flex-1 overflow-y-auto outline-none\"\n                >\n                  {children}\n                </main>\n              </div>\n            </div>\n          </div>\n        </div>\n      </TooltipProvider>\n    </AppShellContext.Provider>\n  );\n}\n\nexport { AppShell, useAppShell, useSidebarPlacement };\nexport type { AppShellContextValue, AppShellProps, LinkComponent, LinkStatusHook };\n"
    }
  ],
  "dependencies": [
    "lucide-react"
  ]
}
