AniBTUI
✦ 组件@anibt/breadcrumbregistry JSON

Breadcrumb

13 号弱色的路径:链接指针下变粉,当前页是墨色 600,分隔是一个小箭头。

安装

npx shadcn@latest add @anibt/breadcrumb

第一次用之前:在 components.json 里登记 @anibt,再单独运行一次 npx shadcn@latest add @anibt/theme(见快速开始)。只有单独安装主题时,CLI 才会把 shadcn 的标准颜色换成白樱 / 夜樱;随组件带进来的主题只补上 AniBT 自己的变量和配方。

手动安装
  1. 1. 先装主题组件只读 @anibt/theme 的 token 和配方,没有主题就没有样子。
    npx shadcn@latest add @anibt/theme
  2. 2. 安装依赖
    npm install lucide-react radix-ui
  3. 3. 把下面的文件放进项目
    components/ui/breadcrumb.tsx
    import * as React from "react"
    import { ChevronRightIcon, MoreHorizontalIcon } from "lucide-react"
    import { Slot } from "radix-ui"
    
    import { cn } from "@/lib/utils"
    
    /**
     * Breadcrumb — the AniBT 「贴纸手账」 trail.
     *
     * 13px, muted; a link is `--anibt-ink2` and turns pink under the pointer; the
     * current page is ink at 600 and not a link. Separators are a small muted
     * chevron. The trail wraps rather than scrolls; keep it to three or four
     * steps and fold the middle into `BreadcrumbEllipsis`.
     *
     * No washi tape here: the tape is the single "you are here" mark of the main
     * navigation, and a second one on the same page would compete with it.
     *
     * The parts are shadcn's. `BreadcrumbLink` takes `asChild` (Radix) or
     * `render={<Link … />}` (Base UI) to hand the link to a router.
     */
    function Breadcrumb({ ...props }: React.ComponentProps<"nav">) {
      return <nav aria-label="面包屑" data-slot="breadcrumb" {...props} />
    }
    
    function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
      return (
        <ol
          data-slot="breadcrumb-list"
          className={cn(
            "flex flex-wrap items-center gap-1.5 text-[13px] leading-5 break-words text-muted-foreground",
            className
          )}
          {...props}
        />
      )
    }
    
    function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) {
      return (
        <li data-slot="breadcrumb-item" className={cn("inline-flex min-w-0 items-center gap-1.5", className)} {...props} />
      )
    }
    
    function BreadcrumbLink({
      asChild = false,
      render,
      className,
      children,
      ...props
    }: React.ComponentProps<"a"> & {
      asChild?: boolean
      /** Base UI's form of `asChild`: `render={<Link href="…" />}`. */
      render?: React.ReactElement
    }) {
      const classes = cn(
        "inline-flex items-center gap-1 rounded-[6px] font-medium text-anibt-ink2 underline-offset-4 outline-hidden transition-colors duration-150 hover:text-accent-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring focus-visible:outline-solid [&>svg]:size-3.5 [&>svg]:shrink-0",
        className
      )
      if (asChild || render) {
        const child = render ? (children !== undefined ? React.cloneElement(render, undefined, children) : render) : children
        return (
          <Slot.Root data-slot="breadcrumb-link" className={classes} {...props}>
            {child}
          </Slot.Root>
        )
      }
      return (
        <a data-slot="breadcrumb-link" className={classes} {...props}>
          {children}
        </a>
      )
    }
    
    function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
      return (
        <span
          data-slot="breadcrumb-page"
          role="link"
          aria-disabled="true"
          aria-current="page"
          className={cn("anibt-ell font-semibold text-foreground", className)}
          {...props}
        />
      )
    }
    
    function BreadcrumbSeparator({ children, className, ...props }: React.ComponentProps<"li">) {
      return (
        <li
          data-slot="breadcrumb-separator"
          role="presentation"
          aria-hidden="true"
          className={cn("text-muted-foreground/80 [&>svg]:size-3.5", className)}
          {...props}
        >
          {children ?? <ChevronRightIcon />}
        </li>
      )
    }
    
    function BreadcrumbEllipsis({ className, label = "更多", ...props }: React.ComponentProps<"span"> & { label?: string }) {
      return (
        <span
          data-slot="breadcrumb-ellipsis"
          role="presentation"
          aria-hidden="true"
          className={cn("flex size-5 items-center justify-center [&>svg]:size-4", className)}
          {...props}
        >
          <MoreHorizontalIcon />
          <span className="sr-only">{label}</span>
        </span>
      )
    }
    
    export {
      Breadcrumb,
      BreadcrumbList,
      BreadcrumbItem,
      BreadcrumbLink,
      BreadcrumbPage,
      BreadcrumbSeparator,
      BreadcrumbEllipsis,
    }

用法

用法
import Link from "next/link"

import {
  Breadcrumb,
  BreadcrumbItem,
  BreadcrumbLink,
  BreadcrumbList,
  BreadcrumbPage,
  BreadcrumbSeparator,
} from "@/components/ui/breadcrumb"

<Breadcrumb>
  <BreadcrumbList>
    <BreadcrumbItem>
      <BreadcrumbLink asChild>
        <Link href="/anime">番剧</Link>
      </BreadcrumbLink>
    </BreadcrumbItem>
    <BreadcrumbSeparator />
    <BreadcrumbItem>
      <BreadcrumbPage>夜樱终电</BreadcrumbPage>
    </BreadcrumbItem>
  </BreadcrumbList>
</Breadcrumb>

路径换行,不横滚;保持三四步,中间的折进 BreadcrumbEllipsis。当前页不是链接,太长时省略。

这里不贴和纸胶带:胶带是主导航里唯一的「你在这里」,同一页上再贴一条会和它抢。

路由链接用 asChild(Radix 写法)或 render={<Link href="…" />}(Base UI 写法)。

属性

BreadcrumbLink
asChildboolean默认 false把样式交给唯一的子元素
renderReactElement默认 —Base UI 的写法:渲染成这个元素
BreadcrumbEllipsis
labelstring默认 "更多"给屏幕阅读器的文字

Breadcrumb 的 aria-label 默认是「面包屑」。其余部分与 shadcn 同名,属性透传。