{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "page-header",
  "type": "registry:component",
  "title": "Page header",
  "description": "Breadcrumb trail, title row with an optional avatar or icon, description and actions.",
  "registryDependencies": [
    "@wibo/utils",
    "@wibo/breadcrumb"
  ],
  "files": [
    {
      "path": "src/patterns/page-header.tsx",
      "type": "registry:component",
      "content": "import { Fragment, type AnchorHTMLAttributes, type ComponentProps, type ComponentType, type ReactNode } from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport {\n  Breadcrumb,\n  BreadcrumbEllipsis,\n  BreadcrumbItem,\n  BreadcrumbLink,\n  BreadcrumbList,\n  BreadcrumbPage,\n  BreadcrumbSeparator,\n} from \"@/components/ui/breadcrumb\";\n\n/** A router link component (Next's `Link` behind a plain `href: string`). */\ntype PageLinkComponent = ComponentType<AnchorHTMLAttributes<HTMLAnchorElement> & { href: string }>;\n\ninterface Crumb {\n  /** The crumb's words: a section (\"Tenants\") or a thing's own name (\"Olon\"). */\n  label: ReactNode;\n  /** Where the crumb leads. Ignored on the last crumb, which is the page in view. */\n  href?: string;\n}\n\ninterface PageHeaderProps extends Omit<ComponentProps<\"header\">, \"title\"> {\n  title: ReactNode;\n  description?: ReactNode;\n  /**\n   * The trail from the section down to this page, the page itself last\n   * (\"Tenants\", \"Olon\", \"Agents\", \"PMP Copilot\"). Drawn above the title when\n   * it has two crumbs or more: a section's own page is named by its title.\n   */\n  breadcrumbs?: Crumb[];\n  /** Renders the crumb links; plain anchors otherwise. */\n  linkComponent?: PageLinkComponent;\n  /** Accessible name of the trail. */\n  breadcrumbLabel?: string;\n  /** Accessible name of the collapsed crumbs on phones. */\n  moreLabel?: string;\n  /** The object's mark before the title: an `Avatar` (32 px, rounded square). */\n  avatar?: ReactNode;\n  /** Small Lucide icon before the title, when the page has no avatar. */\n  icon?: ReactNode;\n  /** Right-aligned controls: filters, time range, primary action. */\n  actions?: ReactNode;\n  /** Heading level; pages use 1. */\n  headingLevel?: 1 | 2;\n}\n\n/**\n * The head of every page: an optional breadcrumb trail (13 px, muted,\n * chevrons, the current page in ink), then the title row (an optional 32 px\n * avatar or icon, the title in 20 px semibold), the description under it and\n * the actions on the right. On phones a long trail keeps its first crumb and\n * its last two, the ones between fold into an ellipsis, and every crumb\n * truncates on one line. Renders on the server: pass the app's link\n * component for the crumbs.\n */\nfunction PageHeader({\n  title,\n  description,\n  breadcrumbs,\n  linkComponent: Link,\n  breadcrumbLabel = \"Breadcrumb\",\n  moreLabel = \"More\",\n  avatar,\n  icon,\n  actions,\n  headingLevel = 1,\n  className,\n  ...props\n}: PageHeaderProps) {\n  const Heading = `h${headingLevel}` as const;\n  const crumbs = breadcrumbs && breadcrumbs.length > 1 ? breadcrumbs : null;\n  // On phones, crumbs between the first and the last two fold into an ellipsis.\n  const folded = crumbs ? Math.max(0, crumbs.length - 3) : 0;\n\n  return (\n    <header data-slot=\"page-header\" className={cn(\"flex min-w-0 flex-col gap-5\", className)} {...props}>\n      {crumbs ? (\n        <Breadcrumb aria-label={breadcrumbLabel} className=\"min-w-0\">\n          <BreadcrumbList>\n            {crumbs.map((crumb, index) => {\n              const last = index === crumbs.length - 1;\n              const hiddenOnPhone = index >= 1 && index <= folded;\n              const title = typeof crumb.label === \"string\" ? crumb.label : undefined;\n              return (\n                <Fragment key={index}>\n                  <BreadcrumbItem\n                    className={cn(\n                      last ? \"min-w-0 shrink\" : \"max-w-40 shrink-0 sm:max-w-64\",\n                      hiddenOnPhone && \"hidden sm:inline-flex\",\n                    )}\n                  >\n                    {last || !crumb.href ? (\n                      <BreadcrumbPage title={title} aria-current={last ? \"page\" : undefined}>\n                        {crumb.label}\n                      </BreadcrumbPage>\n                    ) : Link ? (\n                      <BreadcrumbLink asChild title={title}>\n                        <Link href={crumb.href}>{crumb.label}</Link>\n                      </BreadcrumbLink>\n                    ) : (\n                      <BreadcrumbLink href={crumb.href} title={title}>\n                        {crumb.label}\n                      </BreadcrumbLink>\n                    )}\n                  </BreadcrumbItem>\n                  {last ? null : <BreadcrumbSeparator className={cn(hiddenOnPhone && \"hidden sm:flex\")} />}\n                  {index === 0 && folded > 0 ? (\n                    <>\n                      <BreadcrumbItem className=\"sm:hidden\">\n                        <BreadcrumbEllipsis label={moreLabel} />\n                      </BreadcrumbItem>\n                      <BreadcrumbSeparator className=\"sm:hidden\" />\n                    </>\n                  ) : null}\n                </Fragment>\n              );\n            })}\n          </BreadcrumbList>\n        </Breadcrumb>\n      ) : null}\n      <div className=\"flex flex-wrap items-end justify-between gap-x-6 gap-y-3\">\n        <div className=\"grid min-w-0 flex-1 basis-64 gap-1.5\">\n          <div className=\"flex min-w-0 items-center gap-3\">\n            {avatar ? (\n              <span aria-hidden=\"true\" className=\"flex shrink-0\">\n                {avatar}\n              </span>\n            ) : icon ? (\n              <span aria-hidden=\"true\" className=\"flex shrink-0 text-muted-foreground [&_svg]:size-5\">\n                {icon}\n              </span>\n            ) : null}\n            <Heading className=\"min-w-0 truncate text-xl leading-7 font-semibold tracking-tight text-foreground\">\n              {title}\n            </Heading>\n          </div>\n          {description ? <div className=\"max-w-[72ch] text-sm text-muted-foreground\">{description}</div> : null}\n        </div>\n        {actions ? <div className=\"flex flex-wrap items-center gap-2\">{actions}</div> : null}\n      </div>\n    </header>\n  );\n}\n\nexport { PageHeader };\nexport type { Crumb, PageHeaderProps, PageLinkComponent };\n"
    }
  ]
}
