{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "app-shell",
  "title": "AppShell",
  "description": "The frame of an app: sidebar, header, content and an optional aside.",
  "registryDependencies": [
    "https://creance.pages.dev/r/creance-base.json",
    "https://creance.pages.dev/r/icon-button.json",
    "https://creance.pages.dev/r/sheet.json"
  ],
  "files": [
    {
      "path": "app-shell/AppShell.css",
      "content": "/* The shell is the container, so it responds to the space it is given, not the window. */\n.cr-app-shell {\n  container-type: inline-size;\n  height: var(--cr-app-shell-height, 100dvh);\n  color: var(--cr-color-text-default);\n  font-family: var(--cr-font-family-sans);\n  font-size: var(--cr-font-size-md);\n}\n\n.cr-app-shell__frame {\n  position: relative;\n  display: flex;\n  height: 100%;\n}\n\n.cr-app-shell__skip {\n  position: absolute;\n  top: var(--cr-space-2);\n  left: var(--cr-space-2);\n  z-index: 20;\n  padding: var(--cr-space-2) var(--cr-space-3);\n  border-radius: var(--cr-radius-md);\n  background: var(--cr-color-ink-bg);\n  color: var(--cr-color-ink-fg);\n  font-weight: var(--cr-font-weight-semibold);\n  text-decoration: none;\n}\n\n/* Hidden until it has focus. Clipped, not moved, so it cannot show above a shell that is not at the top of the page. */\n.cr-app-shell__skip:not(:focus) {\n  width: 1px;\n  height: 1px;\n  padding: 0;\n  overflow: hidden;\n  clip-path: inset(50%);\n  white-space: nowrap;\n}\n\n.cr-app-shell__sidebar {\n  display: flex;\n  flex: none;\n  overflow-y: auto;\n  border-inline-end: 1px solid var(--cr-color-border-default);\n  background: color-mix(in srgb, var(--cr-color-glass-tint) 22%, transparent);\n}\n\n.cr-app-shell__column {\n  display: flex;\n  flex: 1;\n  flex-direction: column;\n  min-width: 0;\n}\n\n.cr-app-shell__header {\n  display: flex;\n  align-items: center;\n  gap: var(--cr-space-3);\n  box-sizing: border-box;\n  min-height: 60px;\n  padding: var(--cr-space-3) var(--cr-space-5);\n  border-bottom: 1px solid var(--cr-color-border-default);\n  background: var(--cr-color-glass-tile);\n}\n\n/* With no header content, the bar is only needed for the menu button on a narrow shell. */\n.cr-app-shell__header[data-empty] {\n  display: none;\n}\n\n.cr-app-shell__header-slot {\n  display: flex;\n  flex: 1;\n  align-items: center;\n  gap: var(--cr-space-3);\n  min-width: 0;\n}\n\n.cr-app-shell__menu {\n  display: none;\n}\n\n.cr-app-shell__content {\n  display: flex;\n  flex: 1;\n  min-height: 0;\n}\n\n.cr-app-shell__main {\n  flex: 1;\n  box-sizing: border-box;\n  min-width: 0;\n  padding: var(--cr-space-6) var(--cr-space-8);\n  overflow-y: auto;\n}\n\n.cr-app-shell__main:focus {\n  outline: none;\n}\n\n.cr-app-shell__aside {\n  flex: none;\n  box-sizing: border-box;\n  width: var(--cr-app-shell-aside-width, 300px);\n  padding: var(--cr-space-6) var(--cr-space-5);\n  overflow-y: auto;\n  border-inline-start: 1px solid var(--cr-color-border-default);\n  background: color-mix(in srgb, var(--cr-color-glass-tint) 22%, transparent);\n}\n\n.cr-app-shell__sheet .cr-sidebar {\n  width: 100%;\n  padding: 0;\n}\n\n/* Narrow: the sidebar leaves for a sheet, and the aside drops below the content. */\n@container (max-width: 900px) {\n  .cr-app-shell__sidebar {\n    display: none;\n  }\n\n  .cr-app-shell__menu,\n  .cr-app-shell__header[data-empty] {\n    display: flex;\n  }\n\n  .cr-app-shell__content {\n    flex-direction: column;\n    overflow-y: auto;\n  }\n\n  .cr-app-shell__main {\n    flex: none;\n    padding: var(--cr-space-5);\n    overflow: visible;\n  }\n\n  .cr-app-shell__aside {\n    width: auto;\n    overflow: visible;\n    border-inline-start: 0;\n    border-top: 1px solid var(--cr-color-border-default);\n  }\n}\n",
      "type": "registry:file",
      "target": "@components/creance/app-shell/AppShell.css"
    },
    {
      "path": "app-shell/AppShell.tsx",
      "content": "'use client';\n\nimport { useId, useState, type CSSProperties, type ReactNode } from 'react';\nimport { IconButton } from '@/components/creance/icon-button/IconButton';\nimport { Sheet, SheetContent } from '@/components/creance/sheet/Sheet';\nimport { cx } from '@/lib/creance/cx';\nimport { MenuIcon } from '@/lib/creance/icons';\nimport './AppShell.css';\n\nexport interface AppShellProps {\n  /** Down the left, usually a Sidebar. When the shell is narrow it moves into a sheet opened from the header. */\n  sidebar?: ReactNode;\n  /** Across the top of the content: breadcrumbs, search, actions. */\n  header?: ReactNode;\n  /** A second column beside the content. It moves below the content when the shell is narrow. */\n  aside?: ReactNode;\n  /** The page. Rendered in `<main>`. */\n  children: ReactNode;\n  /** Height of the shell. Default is the full viewport. */\n  height?: number | string;\n  /** Title of the sheet that holds the sidebar on a narrow shell. Default \"Navigation\". */\n  sidebarLabel?: string;\n  /** Accessible name of the button that opens it. Default \"Open navigation\". */\n  menuLabel?: string;\n  /** Accessible name of the aside. */\n  asideLabel?: string;\n  /** Text of the link that jumps past the sidebar and header. Default \"Skip to content\". */\n  skipLabel?: string;\n  className?: string;\n}\n\n/**\n * The frame of an app: sidebar, header, content and an optional aside. Layout only: it renders the slots it is given.\n * The first thing in it is a skip link to the content.\n */\nexport function AppShell({\n  sidebar,\n  header,\n  aside,\n  children,\n  height,\n  sidebarLabel = 'Navigation',\n  menuLabel = 'Open navigation',\n  asideLabel,\n  skipLabel = 'Skip to content',\n  className,\n}: AppShellProps) {\n  const [open, setOpen] = useState(false);\n  const mainId = useId();\n  const style = height === undefined ? undefined : ({ '--cr-app-shell-height': typeof height === 'number' ? `${height}px` : height } as CSSProperties);\n\n  return (\n    <div className={cx('cr-app-shell', className)} style={style}>\n      <div className=\"cr-app-shell__frame\">\n        <a className=\"cr-app-shell__skip\" href={`#${mainId}`}>\n          {skipLabel}\n        </a>\n        {sidebar && <div className=\"cr-app-shell__sidebar\">{sidebar}</div>}\n        <div className=\"cr-app-shell__column\">\n          {(header || sidebar) && (\n            <header className=\"cr-app-shell__header\" data-empty={header ? undefined : ''}>\n              {sidebar && (\n                <span className=\"cr-app-shell__menu\">\n                  <IconButton size=\"sm\" icon={<MenuIcon />} label={menuLabel} tooltip={false} onClick={() => setOpen(true)} />\n                </span>\n              )}\n              {header && <div className=\"cr-app-shell__header-slot\">{header}</div>}\n            </header>\n          )}\n          <div className=\"cr-app-shell__content\">\n            <main id={mainId} tabIndex={-1} className=\"cr-app-shell__main\">\n              {children}\n            </main>\n            {aside && (\n              <aside className=\"cr-app-shell__aside\" aria-label={asideLabel}>\n                {aside}\n              </aside>\n            )}\n          </div>\n        </div>\n      </div>\n      {sidebar && (\n        <Sheet open={open} onOpenChange={setOpen}>\n          <SheetContent\n            side=\"left\"\n            title={sidebarLabel}\n            className=\"cr-app-shell__sheet\"\n            // Following a link in the sidebar closes the sheet.\n            onClick={(e) => (e.target as Element).closest('a') && setOpen(false)}\n          >\n            {open && sidebar}\n          </SheetContent>\n        </Sheet>\n      )}\n    </div>\n  );\n}\n",
      "type": "registry:ui",
      "target": "@components/creance/app-shell/AppShell.tsx"
    }
  ],
  "categories": [
    "template",
    "navigation"
  ],
  "type": "registry:ui"
}