{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "page-header",
  "title": "Page Header",
  "description": "Page Header from the Get IT Done design system.",
  "dependencies": [
    "class-variance-authority@^0.7.0",
    "lucide-react@^0.379.0"
  ],
  "registryDependencies": [
    "https://zero-design.itdone.de/r/button.json",
    "https://zero-design.itdone.de/r/dropdown-menu.json",
    "https://zero-design.itdone.de/r/labels-context.json",
    "https://zero-design.itdone.de/r/use-collapse-empty.json",
    "https://zero-design.itdone.de/r/utils.json",
    "https://zero-design.itdone.de/r/theme.json"
  ],
  "files": [
    {
      "path": "registry/ui/page-header.tsx",
      "content": "'use client';\n\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { MoreHorizontal } from 'lucide-react';\nimport * as React from 'react';\n\nimport { useEmptyWrapper } from '@/lib/use-collapse-empty';\nimport { cn } from '@/lib/utils';\nimport { Button } from '@/components/ui/button';\nimport { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger } from '@/components/ui/dropdown-menu';\nimport { useLabels } from '@/components/ui/labels-context';\n\n// Figma: Page header 1239:122640 (Style=zero|zero tabs|Record company|person|project|invoice, Divider, Attributes).\nconst pageHeaderTitleVariants = cva('min-w-0 text-text-default', {\n  variants: {\n    variant: {\n      default: 'typography-h3',\n      // Product page title (zero: 24/29 600).\n      zero: 'typography-page-title',\n      // Record head: a named object (company, person, project, invoice) with a lead tile and badges.\n      record: 'typography-page-title',\n      // Profile styles: large title next to a avatar, optionally under a cover banner.\n      avatar: 'typography-h3',\n      banner: 'typography-h3',\n      'banner-avatar': 'typography-h3',\n    },\n  },\n  defaultVariants: { variant: 'default' },\n});\n\nexport interface PageHeaderProps\n  extends Omit<React.HTMLAttributes<HTMLElement>, 'title'>,\n    VariantProps<typeof pageHeaderTitleVariants> {\n  title: React.ReactNode;\n  subtitle?: React.ReactNode;\n  // Heading level element for the title.\n  titleAs?: 'h1' | 'h2' | 'h3';\n  // Breadcrumb navigation rendered above the title row.\n  breadcrumbs?: React.ReactNode;\n  // Avatar, logo tile or object icon before the title.\n  leading?: React.ReactNode;\n  // Badges or chips inline after the title.\n  badges?: React.ReactNode;\n  // Row of scope chips under the subtitle (for example an organisation or a product scope). Chips can carry a leading icon.\n  scopeChips?: React.ReactNode;\n  // Buttons, bottom-aligned on desktop and full-width stacked on mobile.\n  actions?: React.ReactNode;\n  // Mobile More menu: `DropdownMenuItem` rows (also groups, separators and checkbox items). Below the sm breakpoint `actions` are hidden and a non-shrinking icon button (Button icon-sm, outline) opens this menu with 44px touch rows. From sm up the menu is hidden and `actions` show as before. Without `overflow` nothing changes.\n  overflow?: React.ReactNode;\n  // The one action that must stay reachable on a phone (for example \"New invoice\"). It renders in every viewport next to the title block, never moves into the `overflow` menu and never changes position after hydration, because only CSS switches the layout. `actions` can still collapse into `overflow` below sm.\n  primaryAction?: React.ReactNode;\n  // Accessible name of the More button. Defaults to the provider text (\"More actions\").\n  overflowLabel?: string;\n  // Tab list rendered under the title row.\n  tabs?: React.ReactNode;\n  // Attributes column to the right of the header (record layouts).\n  attributes?: React.ReactNode;\n  // Hairline under the header. Off for the zero style by default.\n  divider?: boolean;\n  // Cover image or gradient for the banner styles. Rendered full width, 240px high.\n  cover?: React.ReactNode;\n  // Centers the content (banner and banner-avatar styles).\n  centered?: boolean;\n}\n\n// The title block never gets narrower than this (the prototype uses 240px, capped at the row width on narrow screens). With the minimum in place the actions wrap onto their own row before the title is squeezed, because a flex item that cannot shrink below it no longer fits next to the actions. Reuses the 240 size token.\n// Token gap PH-TOK-01: a dedicated page-header-title-min size would replace this reuse.\nconst TITLE_BLOCK_MIN = 'sm:min-w-[min(100%,var(--size-column-240,240px))]';\n\n// Mobile More trigger. `flex-none shrink-0` keeps it a square next to a long title, and it sits outside the actions wrapper so the stacked-actions rule (`flex-1` children) never stretches it.\nconst PageHeaderOverflow = ({ label, className, children }: { label: string; className?: string; children: React.ReactNode }) => (\n  <div data-slot=\"overflow\" className={cn('flex flex-none shrink-0 items-center sm:hidden', className)}>\n    <DropdownMenu>\n      <DropdownMenuTrigger asChild>\n        <Button type=\"button\" variant=\"outline\" size=\"icon-sm\" aria-label={label} className=\"flex-none shrink-0\">\n          <MoreHorizontal aria-hidden=\"true\" className=\"h-icon-sm w-icon-sm\" />\n        </Button>\n      </DropdownMenuTrigger>\n      <DropdownMenuContent align=\"end\" density=\"touch\">\n        {children}\n      </DropdownMenuContent>\n    </DropdownMenu>\n  </div>\n);\n\n// Div that leaves the layout while none of its children is shown (hidden by class, attribute or style), so the gap of its parent does not leave a blank row.\nconst EmptyAwareDiv = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => {\n  const { ref, empty } = useEmptyWrapper<HTMLDivElement>();\n  return <div ref={ref} data-empty={empty || undefined} className={className} {...props} />;\n};\n\n// Actions of the header. From sm up `primaryAction` sits beside the actions in one group that does not wrap: the actions wrap among themselves, the primary action stays at the end of the group on the last actions row (items-end) and never drops alone onto its own row. The group may shrink (min-w-0) so wide tabs or actions never push the page sideways. Below sm the group dissolves and the primary action is an item of the header column in DOM order (title, primary action, tabs), so the visual order equals the tab order (WCAG 2.4.3, 1.3.2). No CSS order is used.\nconst HeaderActions = ({ gap, overflowing, actions, primaryAction }: { gap: string; overflowing: boolean; actions?: React.ReactNode; primaryAction?: React.ReactNode }) => {\n  if (!primaryAction) {\n    return (\n      <EmptyAwareDiv className={cn('flex flex-wrap items-center', gap, 'max-sm:[&>*]:flex-1 data-[empty=true]:hidden', overflowing && 'max-sm:hidden')}>{actions}</EmptyAwareDiv>\n    );\n  }\n  const primary = (\n    <EmptyAwareDiv data-slot=\"primary-action\" className=\"flex shrink-0 items-center max-sm:[&>*]:flex-1 data-[empty=true]:hidden\">\n      {primaryAction}\n    </EmptyAwareDiv>\n  );\n  if (!actions) return primary;\n  return (\n    <EmptyAwareDiv data-slot=\"header-actions\" className={cn('flex min-w-0 items-end max-sm:contents sm:flex-nowrap sm:data-[empty=true]:hidden', gap)}>\n      <EmptyAwareDiv data-slot=\"actions\" className={cn('flex min-w-0 flex-wrap items-center', gap, 'max-sm:[&>*]:flex-1 data-[empty=true]:hidden', overflowing && 'max-sm:hidden')}>\n        {actions}\n      </EmptyAwareDiv>\n      {primary}\n    </EmptyAwareDiv>\n  );\n};\n\nconst PageHeader = React.forwardRef<HTMLElement, PageHeaderProps>(\n  (\n    { className, variant = 'default', title, subtitle, titleAs: Title = 'h1', breadcrumbs, leading, badges, scopeChips, actions, primaryAction, overflow, overflowLabel, tabs, attributes, divider, cover, centered = false, ...props },\n    ref,\n  ) => {\n    const l = useLabels({ moreActions: overflowLabel });\n    const moreTrigger = (className: string) => (overflow ? <PageHeaderOverflow label={l.moreActions} className={className}>{overflow}</PageHeaderOverflow> : null);\n    // `actions` collapse into the More menu below sm when `overflow` is set. `primaryAction` is always visible. From sm up it stays beside the actions in one group, so it never drops alone onto its own row.\n    // Below sm the group dissolves (`contents`) and the primary action follows the title block, before the tabs slot. DOM order and visual order are the same, so keyboard focus never jumps backwards.\n    const actionsBlock = (gap: string) => {\n      if (!actions && !primaryAction) return null;\n      return (\n        <HeaderActions gap={gap} overflowing={Boolean(overflow)} actions={actions} primaryAction={primaryAction} />\n      );\n    };\n    const isBanner = variant === 'banner' || variant === 'banner-avatar';\n    const showDivider = divider ?? variant === 'default';\n    if (isBanner) {\n      const overlap = variant === 'banner-avatar' && cover;\n      return (\n        <header ref={ref} className={cn('flex w-full flex-col', className)} {...props}>\n          {cover ? <div className=\"relative h-page-header-cover w-full overflow-hidden\">{cover}</div> : null}\n          <div className={cn('flex w-full flex-col gap-6 px-4 md:px-8', overlap ? '-mt-10' : 'pt-6', centered && 'items-center text-center')}>\n            <div className={cn('flex w-full gap-6', centered ? 'flex-col items-center' : 'flex-col md:flex-row md:items-start')}>\n              {leading ? (\n                <div className=\"h-avatar-profile w-avatar-profile shrink-0 overflow-hidden rounded-full border-4 border-solid border-surface-canvas shadow-elevation-raised [&>*]:h-full [&>*]:w-full\">\n                  {leading}\n                </div>\n              ) : null}\n              <div className={cn('flex min-w-0 flex-1 flex-col gap-5', leading && !centered && 'md:pt-16', centered && 'items-center')}>\n                {breadcrumbs ? <div>{breadcrumbs}</div> : null}\n                <div className={cn('flex w-full flex-col gap-4 max-sm:contents', centered ? 'items-center' : 'md:flex-row md:items-start')}>\n                  <div className={cn('flex min-w-0 flex-1 flex-col gap-1', centered && 'items-center')}>\n                    <div className=\"flex flex-wrap items-center gap-2\">\n                      <Title className={pageHeaderTitleVariants({ variant })}>{title}</Title>\n                      {badges}\n                    </div>\n                    {subtitle ? <p className=\"typography-small text-text-subtle\">{subtitle}</p> : null}\n                    {scopeChips ? <div data-scope-chips=\"\" className=\"flex flex-wrap items-center gap-2 pt-3\">{scopeChips}</div> : null}\n                  </div>\n                  {moreTrigger('self-end')}\n                  {actionsBlock('gap-3')}\n                </div>\n                {tabs}\n              </div>\n            </div>\n            {divider ? <hr className=\"m-0 h-px w-full border-0 bg-border\" /> : null}\n          </div>\n        </header>\n      );\n    }\n    return (\n      <header\n        ref={ref}\n        className={cn('flex w-full flex-col lg:flex-row', variant === 'zero' ? 'gap-4' : 'gap-6', showDivider && 'border-b border-solid border-border pb-4', className)}\n        {...props}\n      >\n        <div className=\"flex min-w-0 flex-1 flex-col gap-4\">\n          {breadcrumbs ? <div>{breadcrumbs}</div> : null}\n          <div className=\"flex flex-col gap-4 max-sm:contents sm:flex-row sm:flex-wrap sm:items-end\">\n            <div className={cn('flex min-w-0 flex-1 items-center gap-3', TITLE_BLOCK_MIN)}>\n              {leading ? <div className=\"shrink-0\">{leading}</div> : null}\n              <div className=\"flex min-w-0 flex-col gap-1\">\n                <div className=\"flex flex-wrap items-center gap-2\">\n                  <Title className={pageHeaderTitleVariants({ variant })}>{title}</Title>\n                  {badges}\n                </div>\n                {subtitle ? <p className=\"typography-ui text-text-subtle\">{subtitle}</p> : null}\n                {scopeChips ? <div data-scope-chips=\"\" className=\"flex flex-wrap items-center gap-2 pt-3\">{scopeChips}</div> : null}\n              </div>\n              {moreTrigger('ml-auto')}\n            </div>\n            {actionsBlock('gap-2')}\n          </div>\n          {tabs}\n        </div>\n        {attributes ? <div className=\"w-full shrink-0 lg:w-aside\">{attributes}</div> : null}\n      </header>\n    );\n  },\n);\nPageHeader.displayName = 'PageHeader';\n\nexport { PageHeader, pageHeaderTitleVariants };\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}