{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "breadcrumb",
  "title": "Breadcrumb",
  "description": "A trail from the root to the current page, with a nav landmark, a current marker and a middle that collapses into a menu.",
  "dependencies": [
    "lucide-react",
    "motion"
  ],
  "registryDependencies": [
    "utils",
    "https://miseui.junsu4621.workers.dev/r/motion.json",
    "https://miseui.junsu4621.workers.dev/r/dropdown-menu.json"
  ],
  "files": [
    {
      "path": "registry/miseui/ui/breadcrumb.tsx",
      "content": "import type * as React from \"react\"\nimport { useId, useMemo, useState } from \"react\"\nimport { LayoutGroup, useReducedMotion } from \"motion/react\"\nimport { ChevronLeft, Ellipsis } from \"lucide-react\"\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuTrigger,\n} from \"@/registry/miseui/ui/dropdown-menu\"\nimport {\n  BreadcrumbContext,\n  CrumbLink,\n  CrumbSeparator,\n  CurrentCrumb,\n  LINK_BASE_CLASS,\n  LINK_CLASS,\n  SpringTarget,\n  collapseItems,\n  useBreadcrumb,\n  type BreadcrumbEffect,\n  type BreadcrumbEntry,\n  type BreadcrumbLinkProps,\n  type BreadcrumbVariant,\n} from \"@/registry/miseui/ui/breadcrumb-parts\"\nimport { cn } from \"@/registry/miseui/lib/utils\"\n\nconst DEFAULT_MAX_ITEMS = 4\n\ntype BreadcrumbProps = Omit<React.ComponentProps<\"nav\">, \"children\"> & {\n  /** From the root to the current page. The last one is the page you are on and is never a link. */\n  items: readonly BreadcrumbEntry[]\n  /** `chevron` plain text and arrows, `slash` a monospace path, `pill` chips without separators, `back` only the parent. */\n  variant?: BreadcrumbVariant\n  /** `spring` slides one hover highlight between the crumbs. */\n  effect?: BreadcrumbEffect\n  /** How many pages stay visible before the middle collapses into a menu. */\n  maxItems?: number\n  /** Name of the nav landmark. */\n  label?: string\n  /** Name of the button that opens the collapsed pages. */\n  moreLabel?: string\n  /** Read before the parent's name in `back`. */\n  backLabel?: string\n  /** Renders each link with your router: `(entry, props) => <Link to={entry.href} {...props} />`. */\n  renderLink?: (entry: BreadcrumbEntry, props: BreadcrumbLinkProps) => React.ReactNode\n}\n\nfunction MoreMenu({ hidden, label }: { hidden: readonly BreadcrumbEntry[]; label: string }) {\n  const { variant, renderLink } = useBreadcrumb()\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger\n        aria-label={label}\n        data-slot=\"breadcrumb-more\"\n        className={cn(\n          LINK_BASE_CLASS,\n          LINK_CLASS[variant],\n          \"min-w-8 justify-center data-[state=open]:text-foreground pointer-coarse:min-w-10\",\n        )}\n      >\n        <Ellipsis aria-hidden=\"true\" />\n      </DropdownMenuTrigger>\n      <DropdownMenuContent align=\"start\">\n        {hidden.map((entry, index) => (\n          <DropdownMenuItem key={index} asChild={entry.href !== undefined} onClick={entry.onClick}>\n            {entry.href === undefined ? (\n              entry.label\n            ) : renderLink ? (\n              renderLink(entry, { href: entry.href, children: entry.label })\n            ) : (\n              <a href={entry.href}>{entry.label}</a>\n            )}\n          </DropdownMenuItem>\n        ))}\n      </DropdownMenuContent>\n    </DropdownMenu>\n  )\n}\n\nfunction Trail({ items, maxItems, moreLabel }: { items: readonly BreadcrumbEntry[]; maxItems: number; moreLabel: string }) {\n  const { setHovered, spring } = useBreadcrumb()\n  const { before, hidden, after } = collapseItems(items, maxItems)\n  const last = items[items.length - 1]\n  const crumb = (entry: BreadcrumbEntry, id: string) => (\n    <li key={id} className=\"inline-flex min-w-0 items-center gap-1\">\n      {entry === last ? (\n        <CurrentCrumb entry={entry} />\n      ) : (\n        <>\n          <SpringTarget id={id}>\n            <CrumbLink entry={entry} />\n          </SpringTarget>\n          <CrumbSeparator />\n        </>\n      )}\n    </li>\n  )\n  return (\n    <ol\n      className=\"flex min-w-0 flex-wrap items-center gap-x-1\"\n      onPointerLeave={spring ? () => setHovered(null) : undefined}\n    >\n      {before.map((entry, index) => crumb(entry, `before-${index}`))}\n      {hidden.length > 0 ? (\n        <li className=\"inline-flex items-center gap-1\">\n          <SpringTarget id=\"more\">\n            <MoreMenu hidden={hidden} label={moreLabel} />\n          </SpringTarget>\n          <CrumbSeparator />\n        </li>\n      ) : null}\n      {after.map((entry, index) => crumb(entry, `after-${index}`))}\n    </ol>\n  )\n}\n\nfunction BackLink({ parent, backLabel }: { parent: BreadcrumbEntry; backLabel: string }) {\n  return (\n    <SpringTarget id=\"back\">\n      <CrumbLink entry={parent}>\n        <ChevronLeft\n          aria-hidden=\"true\"\n          className=\"transition-transform duration-(--duration-fast) ease-(--ease-mise) group-hover/back:-translate-x-1 motion-reduce:transition-none\"\n        />\n        <span className=\"sr-only\">{backLabel}</span> {parent.label}\n      </CrumbLink>\n    </SpringTarget>\n  )\n}\n\nfunction Breadcrumb({\n  items,\n  variant = \"chevron\",\n  effect,\n  maxItems = DEFAULT_MAX_ITEMS,\n  label = \"Breadcrumb\",\n  moreLabel = \"Show hidden pages\",\n  backLabel = \"Back to\",\n  renderLink,\n  className,\n  ...props\n}: BreadcrumbProps) {\n  const groupId = useId()\n  const reduceMotion = useReducedMotion() ?? false\n  const [hovered, setHovered] = useState<string | null>(null)\n  const spring = effect === \"spring\"\n  const context = useMemo(\n    () => ({ variant, spring, reduceMotion, hovered, setHovered, renderLink }),\n    [variant, spring, reduceMotion, hovered, renderLink],\n  )\n  const parent = items[items.length - 2]\n  if (items.length === 0 || (variant === \"back\" && !parent)) return null\n  return (\n    <BreadcrumbContext.Provider value={context}>\n      {/* layoutId is global to the page; without a per-instance group two trails would share one highlight. */}\n      <LayoutGroup id={groupId}>\n        <nav\n          aria-label={label}\n          data-slot=\"breadcrumb\"\n          data-variant={variant}\n          data-effect={effect}\n          className={cn(\"min-w-0\", className)}\n          {...props}\n        >\n          {variant === \"back\" ? (\n            <BackLink parent={parent} backLabel={backLabel} />\n          ) : (\n            <Trail items={items} maxItems={maxItems} moreLabel={moreLabel} />\n          )}\n        </nav>\n      </LayoutGroup>\n    </BreadcrumbContext.Provider>\n  )\n}\n\nexport {\n  Breadcrumb,\n  collapseItems,\n  type BreadcrumbProps,\n  type BreadcrumbEntry,\n  type BreadcrumbLinkProps,\n  type BreadcrumbVariant,\n  type BreadcrumbEffect,\n}\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/miseui/ui/breadcrumb-parts.tsx",
      "content": "import type * as React from \"react\"\nimport { createContext, useContext } from \"react\"\nimport { motion } from \"motion/react\"\nimport { ChevronRight } from \"lucide-react\"\nimport { SPRING_DEFAULT } from \"@/registry/miseui/lib/motion\"\nimport { cn } from \"@/registry/miseui/lib/utils\"\n\ntype BreadcrumbVariant = \"chevron\" | \"slash\" | \"pill\" | \"back\"\ntype BreadcrumbEffect = \"spring\"\n\ninterface BreadcrumbEntry {\n  label: React.ReactNode\n  href?: string\n  onClick?: React.MouseEventHandler<HTMLElement>\n  icon?: React.ReactNode\n}\n\ninterface BreadcrumbLinkProps {\n  href?: string\n  onClick?: React.MouseEventHandler<HTMLElement>\n  className?: string\n  children: React.ReactNode\n  \"data-slot\"?: string\n}\n\ninterface CollapsedTrail {\n  before: readonly BreadcrumbEntry[]\n  hidden: readonly BreadcrumbEntry[]\n  after: readonly BreadcrumbEntry[]\n}\n\n/** Keeps the root and the nearest pages. A menu holding one page is worse than showing that page, so it needs two. */\nfunction collapseItems(items: readonly BreadcrumbEntry[], maxItems: number): CollapsedTrail {\n  const kept = Math.max(1, maxItems - 1)\n  const hiddenCount = items.length - 1 - kept\n  if (hiddenCount < 2) return { before: items, hidden: [], after: [] }\n  return { before: items.slice(0, 1), hidden: items.slice(1, -kept), after: items.slice(-kept) }\n}\n\nconst LINK_CLASS: Record<BreadcrumbVariant, string> = {\n  chevron: \"h-8 gap-1 rounded-sm px-1 text-sm text-muted-foreground hover:text-foreground pointer-coarse:h-10\",\n  slash: \"h-8 gap-1 rounded-sm px-1 font-mono text-[13px] text-muted-foreground hover:text-foreground pointer-coarse:h-10\",\n  pill: \"h-8 gap-1 rounded-full bg-muted px-3 text-[13px] text-foreground hover:bg-accent pointer-coarse:h-10\",\n  back: \"group/back h-8 gap-1 rounded-sm pr-2 text-sm font-medium text-foreground pointer-coarse:h-10\",\n}\n\nconst CURRENT_CLASS: Record<Exclude<BreadcrumbVariant, \"back\">, string> = {\n  chevron: \"px-1 text-sm font-medium text-foreground\",\n  slash: \"px-1 font-mono text-[13px] font-medium text-foreground\",\n  pill: \"inline-flex h-8 items-center rounded-full bg-foreground px-3 text-[13px] font-medium text-background\",\n}\n\nconst LINK_BASE_CLASS = [\n  \"relative inline-flex min-w-0 items-center outline-none\",\n  \"transition-colors duration-(--duration-fast) ease-(--ease-mise)\",\n  \"focus-visible:ring-[3px] focus-visible:ring-ring/50\",\n  \"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n]\n\nconst INSTANT = { duration: 0 } as const\n\ninterface BreadcrumbContextValue {\n  variant: BreadcrumbVariant\n  spring: boolean\n  reduceMotion: boolean\n  hovered: string | null\n  setHovered: (key: string | null) => void\n  renderLink?: (entry: BreadcrumbEntry, props: BreadcrumbLinkProps) => React.ReactNode\n}\n\nconst BreadcrumbContext = createContext<BreadcrumbContextValue>({\n  variant: \"chevron\",\n  spring: false,\n  reduceMotion: false,\n  hovered: null,\n  setHovered: () => undefined,\n})\n\nfunction useBreadcrumb() {\n  return useContext(BreadcrumbContext)\n}\n\n/** The one highlight that slides between crumbs. Without the effect this is only a wrapper. */\nfunction SpringTarget({ id, children }: { id: string; children: React.ReactNode }) {\n  const { spring, reduceMotion, hovered, setHovered, variant } = useBreadcrumb()\n  if (!spring) return <>{children}</>\n  return (\n    <span\n      className=\"relative inline-flex min-w-0\"\n      onPointerEnter={() => setHovered(id)}\n      onFocus={() => setHovered(id)}\n      onBlur={() => setHovered(null)}\n    >\n      {hovered === id ? (\n        <motion.span\n          aria-hidden=\"true\"\n          data-slot=\"breadcrumb-highlight\"\n          layoutId=\"breadcrumb-highlight\"\n          // A pill's own fill would hide a highlight underneath it, so that one sits on top and lets clicks through.\n          className={cn(\n            \"pointer-events-none absolute inset-0 bg-foreground/5\",\n            variant === \"pill\" ? \"z-10 rounded-full\" : \"rounded-sm\",\n          )}\n          transition={reduceMotion ? INSTANT : SPRING_DEFAULT}\n        />\n      ) : null}\n      {children}\n    </span>\n  )\n}\n\nfunction CrumbLink({\n  entry,\n  children,\n  slot = \"breadcrumb-link\",\n}: {\n  entry: BreadcrumbEntry\n  children?: React.ReactNode\n  slot?: string\n}) {\n  const { variant, renderLink } = useBreadcrumb()\n  const linkProps: BreadcrumbLinkProps = {\n    href: entry.href,\n    onClick: entry.onClick,\n    className: cn(LINK_BASE_CLASS, LINK_CLASS[variant]),\n    \"data-slot\": slot,\n    children: children ?? (\n      <>\n        {entry.icon}\n        {entry.label}\n      </>\n    ),\n  }\n  if (renderLink) return renderLink(entry, linkProps)\n  if (entry.href === undefined && entry.onClick) {\n    return <button type=\"button\" {...linkProps} className={cn(linkProps.className, \"cursor-pointer\")} />\n  }\n  return <a {...linkProps} />\n}\n\nfunction CurrentCrumb({ entry }: { entry: BreadcrumbEntry }) {\n  const { variant } = useBreadcrumb()\n  return (\n    <span\n      aria-current=\"page\"\n      data-slot=\"breadcrumb-current\"\n      className={cn(\"inline-flex min-w-0 items-center gap-1\", variant !== \"back\" && CURRENT_CLASS[variant])}\n    >\n      {entry.icon}\n      {entry.label}\n    </span>\n  )\n}\n\nfunction CrumbSeparator() {\n  const { variant } = useBreadcrumb()\n  if (variant === \"pill\" || variant === \"back\") return null\n  return (\n    <span aria-hidden=\"true\" data-slot=\"breadcrumb-separator\" className=\"flex shrink-0 items-center text-muted-foreground/60\">\n      {variant === \"slash\" ? <span className=\"px-1 font-mono text-[13px]\">/</span> : <ChevronRight className=\"size-4\" />}\n    </span>\n  )\n}\n\nexport {\n  BreadcrumbContext,\n  CrumbLink,\n  CrumbSeparator,\n  CurrentCrumb,\n  SpringTarget,\n  collapseItems,\n  useBreadcrumb,\n  LINK_BASE_CLASS,\n  LINK_CLASS,\n  type BreadcrumbEffect,\n  type BreadcrumbEntry,\n  type BreadcrumbLinkProps,\n  type BreadcrumbVariant,\n}\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "navigation"
  ],
  "type": "registry:ui"
}