{
  "name": "breadcrumbs",
  "layer": "application",
  "type": "component",
  "title": "Breadcrumb components",
  "description": "Powerful and customizable React breadcrumb components built for modern applications and websites. These breadcrumbs are built using React Aria and styled with Tailwind CSS.",
  "files": [
    {
      "path": "components/application/breadcrumbs/breadcrumbs.tsx",
      "target": "components/application/breadcrumbs/breadcrumbs.tsx",
      "type": "component",
      "content": "\"use client\";\n\nimport { type FC, type ReactNode, createContext, useContext } from \"react\";\nimport type { Key as AriaKey } from \"react-aria-components\";\nimport { Breadcrumb as AriaBreadcrumb, Breadcrumbs as AriaBreadcrumbs, Button as AriaButton, Link as AriaLink } from \"react-aria-components\";\nimport { ChevronRight, ChevronSelectorVertical, SlashDivider } from \"@properui/icons\";\nimport { cx, sortCx } from \"@/utils/cx\";\nimport { Avatar } from \"../../base/avatar/avatar\";\nimport { Dropdown } from \"../../base/dropdown/dropdown\";\n\nconst styles = sortCx({\n    list: \"relative flex\",\n    item: \"flex items-center current:overflow-hidden\",\n    link: [\n        \"group inline-flex items-center justify-center gap-1 rounded-md outline-focus-ring transition duration-100 ease-linear\",\n        \"focus-visible:outline-2 focus-visible:outline-offset-2\",\n        // Let the current item shrink and truncate instead of overflowing the list.\n        \"in-current:max-w-full\",\n    ].join(\" \"),\n    icon: \"size-5 shrink-0 text-fg-quaternary transition-inherit-all group-hover:text-fg-quaternary_hover\",\n    label: \"text-sm font-semibold whitespace-nowrap transition-inherit-all in-current:truncate\",\n    labelDefault: \"text-quaternary group-hover:text-tertiary_hover\",\n    // Directional dividers mirror in RTL so the hierarchy still reads outwards.\n    divider: \"size-4 shrink-0 stroke-[2.25px] text-utility-neutral-300 rtl:-scale-x-100\",\n\n    types: {\n        text: {\n            list: \"gap-1.5 md:gap-2\",\n            item: \"gap-1.5 md:gap-2\",\n            link: \"\",\n            linkCurrent: \"\",\n            label: \"\",\n            labelCurrent: \"text-brand-secondary group-hover:text-brand-secondary\",\n        },\n        \"text-line\": {\n            list: \"gap-1.5 py-2 ps-2 after:pointer-events-none after:absolute after:inset-0 after:border-t after:border-b after:border-secondary md:gap-2\",\n            item: \"gap-1.5 md:gap-2\",\n            link: \"\",\n            linkCurrent: \"\",\n            label: \"\",\n            labelCurrent: \"text-brand-secondary group-hover:text-brand-secondary\",\n        },\n        button: {\n            list: \"gap-0.5 md:gap-1\",\n            item: \"gap-0.5 md:gap-1\",\n            link: \"p-1 hover:bg-primary_hover\",\n            linkCurrent: \"bg-primary_hover\",\n            label: \"px-1\",\n            labelCurrent: \"text-fg-tertiary_hover group-hover:text-tertiary_hover\",\n        },\n    },\n\n    account: {\n        root: \"flex cursor-pointer items-center gap-1.5 rounded-lg outline-focus-ring outline-offset-2 focus-visible:outline-2\",\n        avatarFrame: \"flex rounded-lg bg-primary p-0.5 ring-[0.5px] ring-secondary ring-inset\",\n        name: \"text-sm font-semibold text-primary\",\n        chevron: \"size-3 shrink-0 stroke-3 text-fg-quaternary\",\n    },\n});\n\nconst dividerIcons = {\n    chevron: ChevronRight,\n    slash: SlashDivider,\n};\n\n/** The visual treatment shared by every item of a breadcrumb trail. */\nexport type BreadcrumbsType = keyof typeof styles.types;\n\n/**\n * The icon rendered between two breadcrumb items. Spelled out as a literal union (instead of\n * `keyof typeof dividerIcons`) so declaration emit doesn't need to name each icon component's\n * own unexported `Props` type (TS4023) — keep this in sync with `dividerIcons`'s keys.\n */\nexport type BreadcrumbsDivider = \"chevron\" | \"slash\";\n\n/** A single entry of a breadcrumb dropdown menu. */\nexport interface BreadcrumbsMenuItem {\n    /** A unique id, passed to `onAction` when the entry is selected. */\n    id: string;\n    /** The visible label of the entry. */\n    label: string;\n    /** Link target of the entry. */\n    href?: string;\n    /** Icon rendered before the label. */\n    icon?: FC<{ className?: string }>;\n}\n\nconst BreadcrumbsStyleContext = createContext<{ type: BreadcrumbsType; divider: BreadcrumbsDivider }>({ type: \"text\", divider: \"chevron\" });\n\nconst BreadcrumbDivider = ({ divider }: { divider: BreadcrumbsDivider }) => {\n    const Icon = dividerIcons[divider];\n\n    return <Icon aria-hidden=\"true\" className={styles.divider} />;\n};\n\nconst BreadcrumbsMenu = ({ items, onAction, width }: { items: BreadcrumbsMenuItem[]; onAction?: (key: AriaKey) => void; width: string }) => (\n    <Dropdown.Popover placement=\"bottom start\" className={width}>\n        <Dropdown.Menu onAction={onAction}>\n            {items.map((item) => (\n                <Dropdown.Item key={item.id} id={item.id} href={item.href} icon={item.icon} label={item.label} />\n            ))}\n        </Dropdown.Menu>\n    </Dropdown.Popover>\n);\n\nexport interface BreadcrumbsProps {\n    /**\n     * The visual treatment of the breadcrumb items.\n     *\n     * @default \"text\"\n     */\n    type?: BreadcrumbsType;\n    /**\n     * The icon rendered between two items.\n     *\n     * @default \"chevron\"\n     */\n    divider?: BreadcrumbsDivider;\n    /**\n     * The accessible label of the surrounding navigation landmark.\n     *\n     * @default \"Breadcrumb\"\n     */\n    \"aria-label\"?: string;\n    /** Whether every breadcrumb of the trail is disabled. */\n    isDisabled?: boolean;\n    /** Handler called with the item id when a breadcrumb is pressed. */\n    onAction?: (key: AriaKey) => void;\n    /** The class name applied to the breadcrumb list. */\n    className?: string;\n    /** The breadcrumb items. */\n    children?: ReactNode;\n}\n\nconst BreadcrumbsRoot = ({\n    type = \"text\",\n    divider = \"chevron\",\n    \"aria-label\": ariaLabel = \"Breadcrumb\",\n    isDisabled,\n    onAction,\n    className,\n    children,\n}: BreadcrumbsProps) => (\n    <BreadcrumbsStyleContext.Provider value={{ type, divider }}>\n        <nav aria-label={ariaLabel} className=\"min-w-0\">\n            <AriaBreadcrumbs isDisabled={isDisabled} onAction={onAction} className={cx(styles.list, styles.types[type].list, className)}>\n                {children}\n            </AriaBreadcrumbs>\n        </nav>\n    </BreadcrumbsStyleContext.Provider>\n);\n\nexport interface BreadcrumbsItemProps {\n    /** Link target of the item. The last item of a trail is always rendered as the current page. */\n    href?: string;\n    /** Icon rendered before the label. */\n    icon?: FC<{ className?: string }>;\n    /** The accessible label of the item — required when the item only renders an icon. */\n    \"aria-label\"?: string;\n    /** A unique id, passed to the `onAction` handler of the parent. */\n    id?: AriaKey;\n    /** The class name applied to the item link. */\n    className?: string;\n    /** The class name applied to the item label. */\n    labelClassName?: string;\n    /** The label of the item. */\n    children?: ReactNode;\n}\n\nconst BreadcrumbsItem = ({ href, icon: Icon, \"aria-label\": ariaLabel, id, className, labelClassName, children }: BreadcrumbsItemProps) => {\n    const { type, divider } = useContext(BreadcrumbsStyleContext);\n\n    return (\n        <AriaBreadcrumb id={id} className={cx(styles.item, styles.types[type].item)}>\n            {({ isCurrent }) => (\n                <>\n                    <AriaLink\n                        href={href}\n                        aria-label={ariaLabel}\n                        className={cx(styles.link, styles.types[type].link, isCurrent ? styles.types[type].linkCurrent : href && \"cursor-pointer\", className)}\n                    >\n                        {Icon && <Icon aria-hidden=\"true\" className={styles.icon} />}\n\n                        {children != null && (\n                            <span\n                                className={cx(\n                                    styles.label,\n                                    styles.types[type].label,\n                                    isCurrent ? styles.types[type].labelCurrent : styles.labelDefault,\n                                    labelClassName,\n                                )}\n                            >\n                                {children}\n                            </span>\n                        )}\n                    </AriaLink>\n\n                    {!isCurrent && <BreadcrumbDivider divider={divider} />}\n                </>\n            )}\n        </AriaBreadcrumb>\n    );\n};\n\nexport interface BreadcrumbsCollapsedProps {\n    /** The items hidden behind the ellipsis. */\n    items: BreadcrumbsMenuItem[];\n    /**\n     * The accessible label of the ellipsis trigger.\n     *\n     * @default \"See all breadcrumb items\"\n     */\n    \"aria-label\"?: string;\n    /** Handler called with the item id when a hidden item is selected. */\n    onAction?: (key: AriaKey) => void;\n    /** The class name applied to the ellipsis trigger. */\n    className?: string;\n}\n\nconst BreadcrumbsCollapsed = ({ items, \"aria-label\": ariaLabel = \"See all breadcrumb items\", onAction, className }: BreadcrumbsCollapsedProps) => {\n    const { type, divider } = useContext(BreadcrumbsStyleContext);\n\n    return (\n        <AriaBreadcrumb className={cx(styles.item, styles.types[type].item)}>\n            {({ isCurrent }) => (\n                <>\n                    <Dropdown.Root>\n                        <AriaButton aria-label={ariaLabel} className={cx(styles.link, styles.types[type].link, \"cursor-pointer\", className)}>\n                            <span aria-hidden=\"true\" className={cx(styles.label, styles.types[type].label, styles.labelDefault)}>\n                                ...\n                            </span>\n                        </AriaButton>\n\n                        <BreadcrumbsMenu items={items} onAction={onAction} width=\"w-50\" />\n                    </Dropdown.Root>\n\n                    {!isCurrent && <BreadcrumbDivider divider={divider} />}\n                </>\n            )}\n        </AriaBreadcrumb>\n    );\n};\n\ninterface AccountContentProps {\n    /** The avatar image of the account. */\n    src: string;\n    /** The alt text of the avatar image — leave empty when the name is already visible. */\n    alt?: string;\n    /** Whether to render the selector chevron of a menu trigger. */\n    hasMenu?: boolean;\n    /** The name of the account. */\n    children: ReactNode;\n}\n\nconst AccountContent = ({ src, alt = \"\", hasMenu, children }: AccountContentProps) => (\n    <>\n        <span className={styles.account.avatarFrame}>\n            <Avatar size=\"xs\" src={src} alt={alt} className=\"shadow-md\" contentClassName=\"rounded-md\" />\n        </span>\n\n        <span className={styles.account.name}>{children}</span>\n\n        {hasMenu && <ChevronSelectorVertical aria-hidden=\"true\" className={styles.account.chevron} />}\n    </>\n);\n\nexport interface BreadcrumbsAccountProps extends Omit<AccountContentProps, \"hasMenu\"> {\n    /** Link target of the account item. */\n    href?: string;\n    /** A unique id, passed to the `onAction` handler of the parent. */\n    id?: AriaKey;\n    /** The class name applied to the account link. */\n    className?: string;\n}\n\nconst BreadcrumbsAccount = ({ src, alt, href, id, className, children }: BreadcrumbsAccountProps) => {\n    const { type, divider } = useContext(BreadcrumbsStyleContext);\n\n    return (\n        <AriaBreadcrumb id={id} className={cx(styles.item, styles.types[type].item)}>\n            {({ isCurrent }) => (\n                <>\n                    <AriaLink href={href} className={cx(styles.account.root, className)}>\n                        <AccountContent src={src} alt={alt}>\n                            {children}\n                        </AccountContent>\n                    </AriaLink>\n\n                    {!isCurrent && <BreadcrumbDivider divider={divider} />}\n                </>\n            )}\n        </AriaBreadcrumb>\n    );\n};\n\nexport interface BreadcrumbsAccountMenuProps extends Omit<AccountContentProps, \"hasMenu\"> {\n    /** The entries of the account menu. */\n    items: BreadcrumbsMenuItem[];\n    /** Handler called with the entry id when a menu entry is selected. */\n    onAction?: (key: AriaKey) => void;\n    /** The class name applied to the account trigger. */\n    className?: string;\n}\n\nconst BreadcrumbsAccountMenu = ({ src, alt, items, onAction, className, children }: BreadcrumbsAccountMenuProps) => {\n    const { type, divider } = useContext(BreadcrumbsStyleContext);\n\n    return (\n        <AriaBreadcrumb className={cx(styles.item, styles.types[type].item)}>\n            {({ isCurrent }) => (\n                <>\n                    <Dropdown.Root>\n                        <AriaButton className={cx(styles.account.root, className)}>\n                            <AccountContent src={src} alt={alt} hasMenu>\n                                {children}\n                            </AccountContent>\n                        </AriaButton>\n\n                        <BreadcrumbsMenu items={items} onAction={onAction} width=\"w-56\" />\n                    </Dropdown.Root>\n\n                    {!isCurrent && <BreadcrumbDivider divider={divider} />}\n                </>\n            )}\n        </AriaBreadcrumb>\n    );\n};\n\nexport const Breadcrumbs = Object.assign(BreadcrumbsRoot, {\n    Item: BreadcrumbsItem,\n    Collapsed: BreadcrumbsCollapsed,\n    Account: BreadcrumbsAccount,\n    AccountMenu: BreadcrumbsAccountMenu,\n});\n",
      "dependencies": [
        "@properui/icons",
        "react",
        "react-aria-components"
      ]
    }
  ],
  "registryDependencies": [
    "avatar",
    "cx",
    "dropdown"
  ],
  "optionalRegistryDependencies": [],
  "dependencies": [
    "@properui/icons",
    "react",
    "react-aria-components"
  ],
  "cssVars": [],
  "examples": [
    "account-button-chevron",
    "account-button-slash",
    "breadcrumbs-button",
    "breadcrumbs-example",
    "breadcrumbs-text",
    "breadcrumbs-text-with-line"
  ],
  "docs": "/components/breadcrumbs",
  "intent": "Show the user's location in a hierarchy and let them navigate to an ancestor page.",
  "avoid_when": [
    "there is only one level of navigation (omit entirely rather than show a single crumb)"
  ],
  "composes_with": [
    "dropdown",
    "app-navigation"
  ],
  "a11y_contract": [
    "wraps items in `<nav aria-label=\"Breadcrumb\">` (overridable) via React Aria Breadcrumbs. The current page is marked programmatically, and a collapsed \"...\" menu item ships a default aria-label (\"See all breadcrumb items\")"
  ],
  "responsive_contract": [
    "the gap between crumbs narrows below the md breakpoint"
  ],
  "requires_data": [
    "an ordered list of crumb items with label and href; the last item represents the current page"
  ],
  "token_contract": [
    "bg-primary",
    "bg-primary_hover",
    "border-secondary",
    "text-brand-secondary",
    "text-fg-quaternary",
    "text-fg-quaternary_hover",
    "text-fg-tertiary_hover",
    "text-primary",
    "text-quaternary",
    "text-tertiary_hover",
    "text-utility-neutral-300"
  ],
  "changelog": [],
  "platforms": [
    "react",
    "next"
  ]
}
