{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "breadcrumb-trail",
  "title": "Breadcrumb Trail",
  "description": "Hierarchy with label truncation, collapsed levels, and a compact mobile fallback.",
  "dependencies": [
    "class-variance-authority",
    "lucide-react"
  ],
  "registryDependencies": [
    "breadcrumb",
    "button",
    "https://uaiblocks.dev/r/uai-theme.json",
    "https://uaiblocks.dev/r/uai-utils.json"
  ],
  "files": [
    {
      "path": "src/registry/uai/components/breadcrumb-trail.tsx",
      "content": "\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport { ChevronLeft, ChevronRight, Ellipsis } from \"lucide-react\";\nimport {\n  Children,\n  type ComponentProps,\n  createContext,\n  type ReactNode,\n  useContext,\n  useEffect,\n  useRef,\n  useState,\n} from \"react\";\nimport {\n  Breadcrumb,\n  BreadcrumbItem,\n  BreadcrumbLink,\n  BreadcrumbList,\n  BreadcrumbPage,\n  BreadcrumbSeparator,\n} from \"@/components/ui/breadcrumb\";\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/uai-utils\";\n\nexport const BREADCRUMB_TRAIL_VARIANTS = [\"chevron\", \"slash\", \"contained\"] as const;\nexport type BreadcrumbTrailVariant = (typeof BREADCRUMB_TRAIL_VARIANTS)[number];\nexport type BreadcrumbTrailProps = ComponentProps<\"nav\"> & {\n  variant?: BreadcrumbTrailVariant;\n  /** Media query that switches to the compact back-link fallback. */\n  compactQuery?: string;\n  /** Force the compact fallback on or off instead of using compactQuery. */\n  compact?: boolean;\n};\ntype TrailContext = { variant: BreadcrumbTrailVariant; compact: boolean };\nconst Context = createContext<TrailContext | null>(null);\nfunction useTrail(part: string) {\n  const context = useContext(Context);\n  if (!context) throw new Error(`${part} must be used within BreadcrumbTrail`);\n  return context;\n}\n\nfunction useMediaQuery(query: string) {\n  const [matches, setMatches] = useState(false);\n  useEffect(() => {\n    if (typeof window.matchMedia !== \"function\") return;\n    const media = window.matchMedia(query);\n    const update = () => setMatches(media.matches);\n    update();\n    media.addEventListener?.(\"change\", update);\n    return () => media.removeEventListener?.(\"change\", update);\n  }, [query]);\n  return matches;\n}\n\nconst breadcrumbTrailListVariants = cva(\n  \"m-0 inline-flex max-w-full list-none flex-wrap items-center text-[13px]/[18px] font-medium break-normal text-muted-foreground\",\n  {\n    variants: {\n      variant: {\n        chevron: \"gap-0.5 p-0 sm:gap-0.5\",\n        slash: \"gap-0 p-0 sm:gap-0\",\n        contained:\n          \"gap-0.5 rounded-full sm:gap-0.5 bg-muted p-0.75 shadow-[inset_0_0_0_1px_color-mix(in_oklab,var(--border)_70%,transparent)]\",\n      },\n    },\n  },\n);\n\nexport function BreadcrumbTrail({\n  variant = \"chevron\",\n  compactQuery = \"(max-width: 479px)\",\n  compact,\n  children,\n  className,\n  ...props\n}: BreadcrumbTrailProps) {\n  const narrow = useMediaQuery(compactQuery);\n  const isCompact = compact ?? narrow;\n  return (\n    <Context.Provider value={{ variant, compact: isCompact }}>\n      <Breadcrumb\n        aria-label=\"Breadcrumb\"\n        data-slot=\"breadcrumb-trail\"\n        data-variant={variant}\n        data-compact={isCompact || undefined}\n        className={cn(\"min-w-0 text-[13px]/[18px] font-medium text-muted-foreground\", className)}\n        {...props}\n      >\n        <BreadcrumbList className={breadcrumbTrailListVariants({ variant })}>\n          {children}\n        </BreadcrumbList>\n      </Breadcrumb>\n    </Context.Provider>\n  );\n}\n\nfunction Separator({ variant }: { variant: BreadcrumbTrailVariant }) {\n  return (\n    <BreadcrumbSeparator\n      className={cn(\n        \"inline-grid place-items-center font-normal text-subtle-foreground opacity-70\",\n        variant === \"slash\" ? \"w-3.5\" : \"w-4\",\n      )}\n    >\n      {variant === \"slash\" ? (\n        \"/\"\n      ) : (\n        <ChevronRight size={14} className=\"size-3.5\" strokeWidth={1.75} />\n      )}\n    </BreadcrumbSeparator>\n  );\n}\n\nconst link = \"inline-flex h-7 min-w-0 max-w-full items-center gap-1 rounded-full px-2 no-underline\";\n// Hover and press feedback.\nconst interactive =\n  \"bg-transparent text-inherit transition-[background-color,color,scale] duration-[120ms,120ms,140ms] ease-[ease-out,ease-out,cubic-bezier(0.23,1,0.32,1)] hover:bg-accent/70 hover:text-foreground active:scale-[0.97] focus-visible:outline-2 focus-visible:outline-solid focus-visible:outline-offset-1 focus-visible:outline-ring motion-reduce:transition-none motion-reduce:active:scale-100\";\n\nexport type BreadcrumbTrailItemProps = Omit<ComponentProps<\"li\">, \"children\"> & {\n  href?: string;\n  /** Marks the current page. It renders as text with aria-current=\"page\". */\n  current?: boolean;\n  /** Marks the direct parent. It becomes the back link in the compact fallback. */\n  parent?: boolean;\n  /** Truncate the label beyond this width. The full label stays in the title. */\n  maxWidth?: number;\n  title?: string;\n  children: ReactNode;\n};\n\nexport function BreadcrumbTrailItem({\n  href,\n  current = false,\n  parent = false,\n  maxWidth = 160,\n  title,\n  children,\n  className,\n  ...props\n}: BreadcrumbTrailItemProps) {\n  const context = useTrail(\"BreadcrumbTrailItem\");\n  const fullTitle = title ?? (typeof children === \"string\" ? children : undefined);\n  if (context.compact && !parent) return null;\n  if (context.compact) {\n    return (\n      <BreadcrumbItem\n        data-slot=\"breadcrumb-trail-item\"\n        className={cn(\"inline-flex min-w-0 gap-0\", className)}\n        {...props}\n      >\n        <BreadcrumbLink href={href} className={cn(link, interactive, \"pl-1 text-foreground\")}>\n          <ChevronLeft size={16} strokeWidth={1.75} aria-hidden=\"true\" />\n          <span className=\"max-w-60 truncate\" title={fullTitle}>\n            <span className=\"sr-only\">Back to </span>\n            {children}\n          </span>\n        </BreadcrumbLink>\n      </BreadcrumbItem>\n    );\n  }\n  return (\n    <>\n      <BreadcrumbItem\n        data-slot=\"breadcrumb-trail-item\"\n        className={cn(\"inline-flex min-w-0 items-center gap-0\", className)}\n        {...props}\n      >\n        {current ? (\n          <BreadcrumbPage\n            title={fullTitle}\n            className={cn(\n              link,\n              \"block truncate leading-7 font-medium text-foreground\",\n              context.variant === \"contained\" &&\n                \"bg-card shadow-[0_0_0_1px_var(--border),0_1px_2px_oklch(0_0_0/0.06)]\",\n            )}\n            style={{ maxWidth }}\n          >\n            {children}\n          </BreadcrumbPage>\n        ) : (\n          <BreadcrumbLink href={href} title={fullTitle} className={cn(link, interactive)}>\n            <span className=\"truncate\" style={{ maxWidth }}>\n              {children}\n            </span>\n          </BreadcrumbLink>\n        )}\n      </BreadcrumbItem>\n      {current ? null : <Separator variant={context.variant} />}\n    </>\n  );\n}\n\nexport type BreadcrumbTrailCollapsedProps = Omit<ComponentProps<\"li\">, \"children\"> & {\n  /** Accessible label for the reveal button. */\n  label?: string;\n  defaultExpanded?: boolean;\n  children: ReactNode;\n};\n\n/** Hides intermediate levels behind an ellipsis button until the reader asks for them. */\nexport function BreadcrumbTrailCollapsed({\n  label = \"Show hidden levels\",\n  defaultExpanded = false,\n  children,\n  className,\n  ...props\n}: BreadcrumbTrailCollapsedProps) {\n  const context = useTrail(\"BreadcrumbTrailCollapsed\");\n  const [expanded, setExpanded] = useState(defaultExpanded);\n  const reveal = useRef<{ list: Element; index: number } | null>(null);\n  const count = Children.count(children);\n  useEffect(() => {\n    if (!expanded || !reveal.current) return;\n    const { list, index } = reveal.current;\n    reveal.current = null;\n    list.children[index]?.querySelector<HTMLElement>(\"a, [aria-current]\")?.focus();\n    if (window.matchMedia?.(\"(prefers-reduced-motion: reduce)\").matches) return;\n    // Each revealed level renders an item and its separator.\n    for (let offset = 0; offset < count * 2; offset++) {\n      list.children[index + offset]?.animate?.(\n        [\n          { opacity: 0, transform: \"translateX(-4px)\" },\n          { opacity: 1, transform: \"none\" },\n        ],\n        {\n          duration: 220,\n          delay: Math.floor(offset / 2) * 40,\n          easing: \"cubic-bezier(0.23, 1, 0.32, 1)\",\n          fill: \"backwards\",\n        },\n      );\n    }\n  }, [expanded, count]);\n  if (context.compact) return null;\n  if (expanded) return children;\n  return (\n    <>\n      <BreadcrumbItem\n        data-slot=\"breadcrumb-trail-collapsed\"\n        className={cn(\"inline-flex items-center gap-0\", className)}\n        {...props}\n      >\n        <Button\n          type=\"button\"\n          variant=\"ghost\"\n          size=\"icon\"\n          aria-label={label}\n          aria-expanded={false}\n          onClick={(event) => {\n            const item = event.currentTarget.closest(\"li\");\n            const list = item?.parentElement;\n            if (item && list)\n              reveal.current = { list, index: Array.from(list.children).indexOf(item) };\n            setExpanded(true);\n          }}\n          className={cn(\n            interactive,\n            \"grid size-7 cursor-pointer place-items-center rounded-full border-0 p-0 focus-visible:ring-0 dark:hover:bg-accent/70\",\n          )}\n        >\n          <Ellipsis size={16} strokeWidth={1.75} aria-hidden=\"true\" />\n        </Button>\n      </BreadcrumbItem>\n      <Separator variant={context.variant} />\n    </>\n  );\n}\n",
      "type": "registry:ui",
      "target": "@ui/uai/breadcrumb-trail.tsx"
    }
  ],
  "categories": [
    "navigation"
  ],
  "type": "registry:ui"
}