{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "page-layout",
  "type": "registry:ui",
  "title": "Page layout",
  "description": "Page shell that owns the scroll container, the shared measure, and the title and actions row.",
  "dependencies": [],
  "registryDependencies": [
    "https://control-ui.dev/r/core.json",
    "https://control-ui.dev/r/scroll-area.json"
  ],
  "files": [
    {
      "path": "src/registry/knob-contracts/page-layout-knobs.ts",
      "target": "@components/control-ui/knob-contracts/page-layout-knobs.ts",
      "type": "registry:component",
      "content": "// Generated from src/registry/sources/control-ui/recipes/page-layout.css by scripts/gen-knob-contracts.ts — run `bun run sync:knobs`.\nexport const pageLayoutKnobs = [\n  \"--cui-page-layout-article-size\",\n  \"--cui-page-layout-aside-size\",\n  \"--cui-page-layout-padding-inline\",\n  \"--cui-page-layout-padding-block\",\n  \"--cui-page-layout-column-gap\",\n  \"--cui-page-layout-header-gap\",\n  \"--cui-page-layout-header-column-gap\",\n  \"--cui-page-layout-header-row-gap\",\n  \"--cui-page-layout-size\",\n  \"--cui-page-layout-sticky-gap\",\n] as const;\nexport type PageLayoutKnobStyle = Partial<Record<(typeof pageLayoutKnobs)[number], string>>;\n"
    },
    {
      "path": "src/registry/sources/control-ui/recipes/page-layout.css",
      "target": "@components/control-ui/styles/recipes/page-layout.css",
      "type": "registry:file",
      "content": "@layer components {\n  :where([data-control-family=\"page-layout\"][data-slot=\"root\"]) {\n    --cui-page-layout-article-size: 40rem;\n    --cui-page-layout-aside-size: 11.25rem;\n    --cui-page-layout-padding-inline: calc(var(--spacing) * 5);\n    --cui-page-layout-padding-block: calc(var(--spacing) * 10) calc(var(--spacing) * 24);\n    --cui-page-layout-column-gap: calc(var(--spacing) * 5);\n    --cui-page-layout-header-gap: calc(var(--spacing) * 8);\n    --cui-page-layout-header-column-gap: calc(var(--spacing) * 4);\n    --cui-page-layout-header-row-gap: calc(var(--spacing) * 2);\n    --cui-page-layout-sticky-gap: 0px;\n\n    /* Header and body both read this, so they stop at the same right edge. */\n    --cui-page-layout-size: calc(var(--cui-page-layout-article-size) + var(--cui-page-layout-padding-inline) * 2);\n  }\n\n  :where([data-control-family=\"page-layout\"][data-slot=\"root\"][data-width=\"content\"]) {\n    --cui-page-layout-article-size: 48rem;\n  }\n\n  :where([data-control-family=\"page-layout\"][data-slot=\"root\"][data-width=\"wide\"]) {\n    --cui-page-layout-article-size: 80rem;\n  }\n\n  :where([data-control-family=\"page-layout\"][data-slot=\"root\"][data-width=\"full\"]) {\n    --cui-page-layout-size: none;\n    --cui-page-layout-padding-inline: calc(var(--spacing) * 6);\n  }\n\n  :where([data-control-family=\"page-layout\"][data-slot=\"header\"]) {\n    display: grid;\n    grid-template-columns: minmax(0, 1fr);\n    grid-template-areas: \"title\" \"description\" \"actions\";\n    align-items: center;\n    width: 100%;\n    max-width: var(--cui-page-layout-size);\n    margin-inline: auto;\n    column-gap: var(--cui-page-layout-header-column-gap);\n    row-gap: var(--cui-page-layout-header-row-gap);\n    padding-inline: var(--cui-page-layout-padding-inline);\n    padding-block-end: var(--cui-page-layout-header-gap);\n  }\n\n  :where([data-control-family=\"page-layout\"][data-slot=\"body\"]) {\n    display: grid;\n    grid-template-columns: minmax(0, 1fr);\n    width: 100%;\n    max-width: var(--cui-page-layout-size);\n    margin-inline: auto;\n    gap: var(--cui-page-layout-column-gap);\n    padding-inline: var(--cui-page-layout-padding-inline);\n    padding-block: var(--cui-page-layout-padding-block);\n  }\n\n  :where([data-control-family=\"page-layout\"][data-slot=\"title\"]) {\n    grid-area: title;\n    font-size: var(--text-heading-1);\n    line-height: var(--text-heading-1--line-height);\n    font-weight: var(--text-heading-1--font-weight);\n    letter-spacing: var(--text-heading-1--letter-spacing);\n    color: var(--foreground);\n  }\n\n  :where([data-control-family=\"page-layout\"][data-slot=\"description\"]) {\n    grid-area: description;\n    font-size: var(--text-body);\n    color: var(--muted-foreground);\n  }\n\n  :where([data-control-family=\"page-layout\"][data-slot=\"actions\"]) {\n    grid-area: actions;\n    justify-self: start;\n  }\n\n  @media (width >= 40rem) {\n    :where([data-control-family=\"page-layout\"][data-slot=\"header\"]) {\n      grid-template-columns: minmax(0, 1fr) auto;\n      grid-template-areas: \"title actions\" \"description actions\";\n    }\n\n    :where([data-control-family=\"page-layout\"][data-slot=\"actions\"]) {\n      justify-self: end;\n    }\n  }\n\n  :where([data-control-family=\"page-layout\"][data-slot=\"header\"][data-variant=\"sticky\"]) {\n    position: sticky;\n    top: var(--cui-page-layout-sticky-gap);\n    z-index: 20;\n  }\n\n  /* Zero height: the band hangs over the body instead of displacing it. */\n  :where([data-control-family=\"page-layout\"][data-slot=\"header\"][data-variant=\"floating\"]) {\n    position: sticky;\n    top: var(--cui-page-layout-sticky-gap);\n    z-index: 20;\n    align-items: start;\n    height: 0;\n    padding-block-end: 0;\n    pointer-events: none;\n  }\n\n  :where([data-control-family=\"page-layout\"][data-slot=\"header\"][data-variant=\"floating\"] > *) {\n    pointer-events: auto;\n  }\n\n  :where(\n    [data-control-family=\"page-layout\"][data-slot=\"header\"][data-variant=\"floating\"]\n      [data-control-family=\"page-layout\"][data-slot=\"actions\"]\n  ) {\n    justify-self: end;\n  }\n\n  :where([data-control-family=\"page-layout\"][data-slot=\"root\"][data-width=\"full\"] [data-control-family=\"page-layout\"][data-slot=\"body\"]) {\n    padding-block: 0;\n    padding-inline: 0;\n  }\n\n  :where([data-control-family=\"page-layout\"][data-slot=\"aside\"]) {\n    display: none;\n  }\n\n  @media (width >= 80rem) {\n    :where([data-control-family=\"page-layout\"][data-slot=\"body\"][data-aside]),\n    :where(\n        [data-control-family=\"page-layout\"][data-slot=\"header\"]:has(+ [data-control-family=\"page-layout\"][data-slot=\"body\"][data-aside])\n      ) {\n      --cui-page-layout-size: calc(\n        var(--cui-page-layout-article-size) +\n        var(--cui-page-layout-column-gap) +\n        var(--cui-page-layout-aside-size) +\n        var(--cui-page-layout-padding-inline) *\n        2\n      );\n    }\n\n    :where([data-control-family=\"page-layout\"][data-slot=\"body\"][data-aside]) {\n      grid-template-columns: minmax(0, var(--cui-page-layout-article-size)) var(--cui-page-layout-aside-size);\n    }\n\n    :where([data-control-family=\"page-layout\"][data-slot=\"aside\"]) {\n      display: block;\n    }\n  }\n}\n\n@property --cui-page-layout-article-size {\n  syntax: \"<length>\";\n  inherits: true;\n  initial-value: 0px;\n}\n\n@property --cui-page-layout-aside-size {\n  syntax: \"<length>\";\n  inherits: true;\n  initial-value: 0px;\n}\n\n@property --cui-page-layout-padding-inline {\n  syntax: \"<length>\";\n  inherits: true;\n  initial-value: 0px;\n}\n\n@property --cui-page-layout-padding-block {\n  syntax: \"*\";\n  inherits: true;\n}\n\n@property --cui-page-layout-column-gap {\n  syntax: \"<length>\";\n  inherits: true;\n  initial-value: 0px;\n}\n\n@property --cui-page-layout-header-gap {\n  syntax: \"<length>\";\n  inherits: true;\n  initial-value: 0px;\n}\n\n@property --cui-page-layout-header-column-gap {\n  syntax: \"<length>\";\n  inherits: true;\n  initial-value: 0px;\n}\n\n@property --cui-page-layout-header-row-gap {\n  syntax: \"<length>\";\n  inherits: true;\n  initial-value: 0px;\n}\n\n@property --cui-page-layout-size {\n  syntax: \"*\";\n  inherits: true;\n}\n\n@property --cui-page-layout-sticky-gap {\n  syntax: \"<length>\";\n  inherits: true;\n  initial-value: 0px;\n}\n"
    },
    {
      "path": "src/registry/sources/control-ui/ui/page-layout.tsx",
      "target": "@components/control-ui/ui/page-layout.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\n\nimport type { ComponentProps, CSSProperties, ReactNode } from \"react\";\nimport type { PageLayoutKnobStyle } from \"@/components/control-ui/knob-contracts/page-layout-knobs\";\nimport { cn } from \"@/components/control-ui/lib/cn\";\nimport { useSkin } from \"@/components/control-ui/skin-provider\";\nimport { ScrollArea, type ScrollAreaProps } from \"@/components/control-ui/ui/scroll-area\";\n\nexport const pageWidths = [\"prose\", \"content\", \"wide\", \"full\"] as const;\nexport type PageWidth = (typeof pageWidths)[number];\n\nexport const pageScrollModes = [\"page\", \"inset\", \"none\"] as const;\nexport type PageScrollMode = (typeof pageScrollModes)[number];\nexport type PageScroll = PageScrollMode | \"auto\";\n\nexport const pageHeaderVariants = [\"flow\", \"sticky\", \"floating\"] as const;\nexport type PageHeaderVariant = (typeof pageHeaderVariants)[number];\n\ntype PageStyle = CSSProperties & PageLayoutKnobStyle;\n\nexport function usePageScroll(scroll: PageScroll = \"auto\"): PageScrollMode {\n  const skin = useSkin();\n  if (scroll !== \"auto\") return scroll;\n  return skin.sidebarLayout === \"page\" ? \"page\" : \"inset\";\n}\n\nexport type PageLayoutProps = Omit<ComponentProps<\"div\">, \"style\"> & {\n  scroll?: PageScroll;\n  width?: PageWidth;\n  scrollViewportRef?: ScrollAreaProps[\"viewportRef\"];\n  style?: PageStyle;\n};\n\nexport function PageLayout({ scroll = \"auto\", width = \"prose\", scrollViewportRef, children, className, ...props }: PageLayoutProps) {\n  const resolvedScroll = usePageScroll(scroll);\n  const scrollsInset = resolvedScroll === \"inset\";\n\n  return (\n    <div\n      data-control-ui=\"page-layout\"\n      data-control-family=\"page-layout\"\n      data-slot=\"root\"\n      data-scroll={resolvedScroll}\n      data-width={width}\n      className={cn(\"flex w-full flex-col\", resolvedScroll !== \"page\" && \"min-h-0 flex-1 overflow-hidden\", className)}\n      {...props}\n    >\n      {scrollsInset ? (\n        <ScrollArea\n          className=\"min-h-0 flex-1\"\n          viewportClassName=\"scroll-smooth motion-reduce:scroll-auto\"\n          viewportRef={scrollViewportRef}\n          lockAxis=\"x\"\n        >\n          {children}\n        </ScrollArea>\n      ) : (\n        children\n      )}\n    </div>\n  );\n}\n\nexport type PageHeaderProps = Omit<ComponentProps<\"header\">, \"style\"> & {\n  variant?: PageHeaderVariant;\n  style?: PageStyle;\n};\n\nexport function PageHeader({ variant = \"flow\", className, ...props }: PageHeaderProps) {\n  return (\n    <header\n      data-control-ui=\"page-layout\"\n      data-control-family=\"page-layout\"\n      data-slot=\"header\"\n      data-variant={variant}\n      className={className}\n      {...props}\n    />\n  );\n}\n\nexport type PageTitleProps = Omit<ComponentProps<\"h1\">, \"style\"> & { style?: PageStyle };\n\nexport function PageTitle({ className, ...props }: PageTitleProps) {\n  return (\n    <h1\n      data-control-ui=\"page-layout\"\n      data-control-family=\"page-layout\"\n      data-slot=\"title\"\n      className={cn(\"text-balance\", className)}\n      {...props}\n    />\n  );\n}\n\nexport type PageDescriptionProps = Omit<ComponentProps<\"p\">, \"style\"> & { style?: PageStyle };\n\nexport function PageDescription({ className, ...props }: PageDescriptionProps) {\n  return (\n    <p\n      data-control-ui=\"page-layout\"\n      data-control-family=\"page-layout\"\n      data-slot=\"description\"\n      className={cn(\"text-pretty\", className)}\n      {...props}\n    />\n  );\n}\n\nexport type PageActionsProps = Omit<ComponentProps<\"div\">, \"style\"> & { style?: PageStyle };\n\nexport function PageActions({ className, ...props }: PageActionsProps) {\n  return (\n    <div\n      data-control-ui=\"page-layout\"\n      data-control-family=\"page-layout\"\n      data-slot=\"actions\"\n      className={cn(\"flex items-center gap-2\", className)}\n      {...props}\n    />\n  );\n}\n\nexport type PageBodyProps = Omit<ComponentProps<\"div\">, \"style\"> & {\n  aside?: ReactNode;\n  contentClassName?: string;\n  style?: PageStyle;\n};\n\nexport function PageBody({ aside, contentClassName, children, className, ...props }: PageBodyProps) {\n  return (\n    <div\n      data-control-ui=\"page-layout\"\n      data-control-family=\"page-layout\"\n      data-slot=\"body\"\n      data-aside={aside ? \"\" : undefined}\n      className={className}\n      {...props}\n    >\n      <div data-control-ui=\"page-layout\" data-control-family=\"page-layout\" data-slot=\"content\" className={cn(\"min-w-0\", contentClassName)}>\n        {children}\n      </div>\n      {aside ? (\n        <aside data-control-ui=\"page-layout\" data-control-family=\"page-layout\" data-slot=\"aside\">\n          {aside}\n        </aside>\n      ) : null}\n    </div>\n  );\n}\n"
    }
  ],
  "css": {
    "@import \"../components/control-ui/styles/recipes/page-layout.css\"": {}
  }
}
