AniBTUI
✦ 组件@anibt/tabsregistry JSON

Tabs

缝线上的 32 高页签;当前页签下贴一条倾斜的和纸胶带,全站唯一的「你在这里」记号。

第 12 集1080p已发布
第 13 集1080p压制中
第 14 集1080p未放送

安装

npx shadcn@latest add @anibt/tabs

第一次用之前:在 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 class-variance-authority radix-ui
  3. 3. 把下面的文件放进项目
    components/ui/tabs.tsx
    "use client"
    
    import * as React from "react"
    import { cva, type VariantProps } from "class-variance-authority"
    import { Tabs as TabsPrimitive } from "radix-ui"
    
    import { cn } from "@/lib/utils"
    
    /**
     * Tabs — the AniBT 「贴纸手账」 tab strip (Radix Tabs).
     *
     * 32px tabs (13/600 `--anibt-ink2`, the pink wash under the pointer) over a
     * stitched line; the current tab turns to ink and wears a strip of washi tape
     * under its label, tilted 1.5° — the site's one "you are here" mark. The
     * recipes are `anibt-tabs`, `anibt-tab` and `anibt-tab-on` from
     * `@anibt/theme`. A disabled tab keeps a readable muted ink.
     *
     * The strip scrolls sideways instead of wrapping: a tab is one line, and
     * eight of them on a 390px phone have to go somewhere.
     *
     * For a sunk track with a sticker thumb (a view switch, a filter) use
     * `SegmentedControl` or `ToggleGroup`: that is a choice, not navigation.
     * `TabsList`'s `variant` (`default` / `line`) exists only so code written for
     * shadcn's tabs keeps compiling; both draw the taped strip.
     */
    
    function Tabs({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Root>) {
      return <TabsPrimitive.Root data-slot="tabs" className={cn("flex min-w-0 flex-col gap-3", className)} {...props} />
    }
    
    const tabsListVariants = cva(
      "anibt-tabs w-full justify-start overflow-x-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden",
      {
        variants: {
          variant: {
            default: "",
            line: "",
          },
        },
        defaultVariants: {
          variant: "default",
        },
      }
    )
    
    function TabsList({
      className,
      variant = "default",
      ...props
    }: React.ComponentProps<typeof TabsPrimitive.List> & VariantProps<typeof tabsListVariants>) {
      return (
        <TabsPrimitive.List
          data-slot="tabs-list"
          data-variant={variant}
          className={cn(tabsListVariants({ variant }), className)}
          {...props}
        />
      )
    }
    
    function TabsTrigger({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
      return (
        <TabsPrimitive.Trigger
          data-slot="tabs-trigger"
          className={cn(
            "anibt-tab cursor-pointer justify-center outline-hidden data-[state=active]:anibt-tab-on",
            "focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-ring focus-visible:outline-solid",
            "disabled:pointer-events-none disabled:text-muted-foreground",
            "[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground data-[state=active]:[&_svg:not([class*='text-'])]:text-anibt-mark",
            className
          )}
          {...props}
        />
      )
    }
    
    function TabsContent({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Content>) {
      return (
        <TabsPrimitive.Content
          data-slot="tabs-content"
          className={cn(
            "min-w-0 flex-1 rounded-[10px] outline-hidden focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring focus-visible:outline-solid",
            className
          )}
          {...props}
        />
      )
    }
    
    export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants }

用法

用法
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"

<Tabs defaultValue="progress">
  <TabsList>
    <TabsTrigger value="progress">发布进度</TabsTrigger>
    <TabsTrigger value="releases">资源</TabsTrigger>
  </TabsList>
  <TabsContent value="progress">…</TabsContent>
  <TabsContent value="releases">…</TabsContent>
</Tabs>

图标与计数

当前页签是墨色字,下面贴一条倾斜的和纸胶带;图标变粉。

页签是 13/600 的 ink2,悬停是粉色淡底;当前页签换成墨色,标签下面贴一条 6px 的条纹胶带,倾斜 1.5°,切换时从左往右贴上去。图标是 16px,当前页签的图标变粉。可以放一个 count-quiet 计数。禁用的页签字变灰,不变淡。

放不下时

页签永远一行:放不下就横向滚动(不显示滚动条),不换行。手机上一排八个番组表页签就是这样。

什么时候不用 Tabs

Tabs 是同一页面里几块内容之间的切换。视图切换和筛选(列表 / 海报墙、简中 / 繁中)是「选择」,用 Segmented Control 或 Toggle Group,它们是下沉轨道里的贴纸拇指,不贴胶带。

属性

Tabs
valuestring默认 —受控的当前页签
defaultValuestring默认 —非受控的初始页签
onValueChange(value: string) => void默认 —切换时调用
activationMode"automatic" | "manual"默认 "automatic"方向键移过去就切换,还是要按回车
TabsList
variant"default" | "line"默认 "default"只为兼容 shadcn 的写法;两者都是贴胶带的页签条

基于 Radix Tabs。另外导出 tabsListVariants。