{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "app-header",
  "title": "App Header",
  "description": "A compound application header with navigation, search, account actions, and responsive overflow.",
  "dependencies": [
    "class-variance-authority",
    "lucide-react"
  ],
  "registryDependencies": [
    "button",
    "input",
    "https://uaiblocks.dev/r/uai-theme.json",
    "https://uaiblocks.dev/r/uai-utils.json"
  ],
  "files": [
    {
      "path": "src/registry/uai/components/app-header.tsx",
      "content": "\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport { Menu, Search, X } from \"lucide-react\";\nimport {\n  type ComponentProps,\n  createContext,\n  type ReactNode,\n  useContext,\n  useId,\n  useState,\n} from \"react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport { cn } from \"@/lib/uai-utils\";\n\nexport const APP_HEADER_VARIANTS = [\"bar\", \"floating\", \"compact\"] as const;\n\nexport type AppHeaderVariant = (typeof APP_HEADER_VARIANTS)[number];\n\nexport type AppHeaderProps = ComponentProps<\"header\"> & {\n  variant?: AppHeaderVariant;\n  open?: boolean;\n  defaultOpen?: boolean;\n  onOpenChange?: (open: boolean) => void;\n};\n\nfunction appHeaderChrome(variant: AppHeaderVariant) {\n  const compact = variant === \"compact\";\n\n  return {\n    gapClass: compact ? \"gap-1.5\" : \"gap-2.5\",\n    brandClass: compact ? \"h-7 gap-1.5 px-1 text-[12.5px]\" : \"h-8 gap-2 px-1 text-[13px]\",\n    overflowClass: compact ? \"gap-2 md:gap-3\" : \"gap-3 md:gap-5\",\n    navClass: compact ? \"gap-0.5\" : \"gap-1\",\n    navItemClass: compact ? \"h-7 px-2.5 text-[12px]\" : \"h-8 px-3 text-[12.5px]\",\n    searchClass: compact ? \"h-7 md:w-36\" : \"h-8 md:w-48 lg:w-56\",\n    actionClass: compact ? \"size-7 text-[11px]\" : \"size-8 text-[11.5px]\",\n  };\n}\n\nconst appHeaderVariants = cva(\"flex w-full flex-wrap items-center bg-card text-foreground\", {\n  variants: {\n    variant: {\n      bar: \"min-h-14 gap-2.5 rounded-none border-b px-4 py-0\",\n      floating:\n        \"min-h-14 gap-2.5 rounded-[14px] p-2 shadow-[0_0_0_1px_var(--border),0_1px_2px_oklch(0_0_0/0.06),0_12px_28px_-20px_oklch(0_0_0/0.4)]\",\n      compact: \"min-h-11 gap-1.5 rounded-xl border px-1.5 py-1\",\n    },\n  },\n});\n\ntype AppHeaderContextValue = {\n  chrome: ReturnType<typeof appHeaderChrome>;\n  open: boolean;\n  overflowId: string;\n  setOpen: (open: boolean) => void;\n};\n\nconst AppHeaderContext = createContext<AppHeaderContextValue | null>(null);\n\nfunction useAppHeader(name: string) {\n  const context = useContext(AppHeaderContext);\n  if (!context) throw new Error(`${name} must be used within AppHeader`);\n  return context;\n}\n\nexport function AppHeader({\n  variant = \"bar\",\n  open,\n  defaultOpen = false,\n  onOpenChange,\n  children,\n  className,\n  ...props\n}: AppHeaderProps) {\n  const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen);\n  const resolvedOpen = open ?? uncontrolledOpen;\n  const overflowId = useId();\n  const chrome = appHeaderChrome(variant);\n\n  function setOpen(nextOpen: boolean) {\n    if (open === undefined) setUncontrolledOpen(nextOpen);\n    onOpenChange?.(nextOpen);\n  }\n\n  return (\n    <AppHeaderContext.Provider value={{ chrome, open: resolvedOpen, overflowId, setOpen }}>\n      <header\n        data-slot=\"app-header\"\n        data-variant={variant}\n        data-open={resolvedOpen || undefined}\n        className={cn(appHeaderVariants({ variant }), className)}\n        {...props}\n      >\n        {children}\n      </header>\n    </AppHeaderContext.Provider>\n  );\n}\n\nexport type AppHeaderBrandProps = ComponentProps<\"a\">;\n\nexport function AppHeaderBrand({ children, className, ...props }: AppHeaderBrandProps) {\n  const context = useAppHeader(\"AppHeaderBrand\");\n\n  return (\n    <a\n      data-slot=\"app-header-brand\"\n      className={cn(\n        \"inline-flex min-w-0 shrink-0 items-center rounded-lg font-medium tracking-[-0.01em] outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-card\",\n        context.chrome.brandClass,\n        className,\n      )}\n      {...props}\n    >\n      {children}\n    </a>\n  );\n}\n\nexport type AppHeaderOverflowProps = Omit<ComponentProps<\"div\">, \"id\">;\n\nexport function AppHeaderOverflow({ children, className, ...props }: AppHeaderOverflowProps) {\n  const context = useAppHeader(\"AppHeaderOverflow\");\n\n  return (\n    <div\n      data-slot=\"app-header-overflow\"\n      data-state={context.open ? \"open\" : \"closed\"}\n      className={cn(\n        \"order-4 min-w-0 basis-full flex-col border-t pt-2 md:order-none md:flex md:basis-auto md:flex-row md:items-center md:border-0 md:pt-0\",\n        context.open ? \"flex\" : \"hidden\",\n        // The mobile panel eases in each time it opens; desktop keeps it static.\n        \"max-md:animate-in max-md:fade-in-0 max-md:slide-in-from-top-1 max-md:duration-200 max-md:ease-out-quint motion-reduce:animate-none\",\n        context.chrome.overflowClass,\n        className,\n      )}\n      {...props}\n      id={context.overflowId}\n    >\n      {children}\n    </div>\n  );\n}\n\nexport type AppHeaderNavProps = ComponentProps<\"nav\">;\n\nexport function AppHeaderNav({\n  children,\n  className,\n  \"aria-label\": ariaLabel = \"Primary navigation\",\n  ...props\n}: AppHeaderNavProps) {\n  const context = useAppHeader(\"AppHeaderNav\");\n\n  return (\n    <nav\n      data-slot=\"app-header-nav\"\n      aria-label={ariaLabel}\n      className={cn(\"min-w-0\", className)}\n      {...props}\n    >\n      <ul className={cn(\"flex list-none flex-col p-0 md:flex-row\", context.chrome.navClass)}>\n        {children}\n      </ul>\n    </nav>\n  );\n}\n\nexport type AppHeaderNavItemProps = ComponentProps<\"a\"> & {\n  active?: boolean;\n};\n\nexport function AppHeaderNavItem({\n  active = false,\n  children,\n  className,\n  \"aria-current\": ariaCurrent,\n  ...props\n}: AppHeaderNavItemProps) {\n  const context = useAppHeader(\"AppHeaderNavItem\");\n\n  return (\n    <li className=\"min-w-0\">\n      <a\n        data-slot=\"app-header-nav-item\"\n        data-active={active || undefined}\n        aria-current={ariaCurrent ?? (active ? \"page\" : undefined)}\n        className={cn(\n          \"flex min-w-0 items-center rounded-full font-medium whitespace-nowrap outline-none transition-[background-color,color,scale] duration-[120ms] ease-out focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-card active:scale-[0.97] motion-reduce:transition-none motion-reduce:active:scale-100\",\n          active\n            ? \"bg-accent text-foreground\"\n            : \"text-muted-foreground hover:bg-accent/60 hover:text-foreground\",\n          context.chrome.navItemClass,\n          className,\n        )}\n        {...props}\n      >\n        {children}\n      </a>\n    </li>\n  );\n}\n\nexport type AppHeaderSearchProps = Omit<ComponentProps<\"input\">, \"type\"> & {\n  label?: ReactNode;\n};\n\nexport function AppHeaderSearch({\n  label = \"Search\",\n  className,\n  placeholder = \"Search\",\n  id,\n  ...props\n}: AppHeaderSearchProps) {\n  const context = useAppHeader(\"AppHeaderSearch\");\n  const generatedId = useId();\n  const inputId = id ?? generatedId;\n\n  return (\n    <label\n      htmlFor={inputId}\n      data-slot=\"app-header-search\"\n      className={cn(\n        \"flex w-full min-w-0 items-center gap-2 rounded-full border border-transparent bg-muted px-3 text-subtle-foreground transition-[border-color,background-color,color] duration-[120ms] ease-out hover:text-muted-foreground focus-within:border-border-strong focus-within:bg-card focus-within:text-muted-foreground md:ml-auto motion-reduce:transition-none\",\n        context.chrome.searchClass,\n      )}\n    >\n      <Search className=\"size-3.5 shrink-0\" strokeWidth={1.8} aria-hidden=\"true\" />\n      <span className=\"sr-only\">{label}</span>\n      <Input\n        type=\"search\"\n        data-slot=\"app-header-search-input\"\n        className={cn(\n          \"h-auto min-w-0 flex-1 rounded-none border-0 bg-transparent p-0 text-[12.5px] text-foreground shadow-none outline-none placeholder:text-subtle-foreground focus-visible:border-0 focus-visible:ring-0 md:text-[12.5px] dark:bg-transparent [&::-webkit-search-cancel-button]:hidden\",\n          className,\n        )}\n        placeholder={placeholder}\n        {...props}\n        id={inputId}\n      />\n    </label>\n  );\n}\n\nexport type AppHeaderActionsProps = ComponentProps<\"div\">;\n\nexport function AppHeaderActions({ children, className, ...props }: AppHeaderActionsProps) {\n  const context = useAppHeader(\"AppHeaderActions\");\n\n  return (\n    <div\n      data-slot=\"app-header-actions\"\n      className={cn(\"ml-auto flex shrink-0 items-center\", context.chrome.gapClass, className)}\n      {...props}\n    >\n      {children}\n    </div>\n  );\n}\n\nexport type AppHeaderActionProps = ComponentProps<\"button\"> & {\n  emphasis?: \"primary\" | \"secondary\";\n};\n\nexport function AppHeaderAction({\n  emphasis = \"secondary\",\n  type = \"button\",\n  children,\n  className,\n  ...props\n}: AppHeaderActionProps) {\n  const context = useAppHeader(\"AppHeaderAction\");\n\n  return (\n    <Button\n      type={type}\n      data-slot=\"app-header-action\"\n      data-emphasis={emphasis}\n      variant={emphasis === \"primary\" ? \"default\" : \"ghost\"}\n      size=\"icon\"\n      className={cn(\n        \"gap-0 font-medium tabular-nums transition-[background-color,color,filter,scale] duration-[120ms] ease-out focus-visible:border-transparent focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-card active:scale-[0.97] motion-reduce:transition-none motion-reduce:active:scale-100\",\n        emphasis === \"primary\"\n          ? \"rounded-full bg-primary text-primary-foreground shadow-[0_0_0_1px_oklch(1_0_0_/_0.08)] hover:bg-primary hover:brightness-[1.08]\"\n          : \"rounded-lg bg-transparent text-muted-foreground hover:bg-accent hover:text-foreground dark:hover:bg-accent\",\n        context.chrome.actionClass,\n        className,\n      )}\n      {...props}\n    >\n      {children}\n    </Button>\n  );\n}\n\nexport type AppHeaderMenuButtonProps = Omit<\n  ComponentProps<\"button\">,\n  \"aria-controls\" | \"aria-expanded\"\n>;\n\nexport function AppHeaderMenuButton({\n  type = \"button\",\n  className,\n  onClick,\n  \"aria-label\": ariaLabel,\n  ...props\n}: AppHeaderMenuButtonProps) {\n  const context = useAppHeader(\"AppHeaderMenuButton\");\n  const Icon = context.open ? X : Menu;\n\n  return (\n    <Button\n      data-slot=\"app-header-menu-button\"\n      variant=\"ghost\"\n      size=\"icon\"\n      className={cn(\n        \"rounded-lg text-muted-foreground transition-[background-color,color,scale] duration-[120ms] ease-out hover:bg-accent hover:text-foreground focus-visible:border-transparent focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-card active:scale-[0.97] md:hidden motion-reduce:transition-none motion-reduce:active:scale-100 dark:hover:bg-accent\",\n        context.chrome.actionClass,\n        className,\n      )}\n      {...props}\n      type={type}\n      aria-label={ariaLabel ?? (context.open ? \"Close navigation\" : \"Open navigation\")}\n      aria-controls={context.overflowId}\n      aria-expanded={context.open}\n      onClick={(event) => {\n        context.setOpen(!context.open);\n        onClick?.(event);\n      }}\n    >\n      <Icon\n        key={context.open ? \"close\" : \"open\"}\n        className=\"size-4 animate-in fade-in-0 zoom-in-75 duration-150 ease-out-quint motion-reduce:animate-none\"\n        strokeWidth={1.8}\n        aria-hidden=\"true\"\n      />\n    </Button>\n  );\n}\n",
      "type": "registry:ui",
      "target": "@ui/uai/app-header.tsx"
    }
  ],
  "categories": [
    "navigation",
    "layout"
  ],
  "type": "registry:ui"
}