Accordion
折叠的段落:缝线分开的行,或一张张分组卡;指针下标题变粉,展开的那节箭头翻转成粉色。
在番剧页点「订阅 RSS」,挑好字幕组、语言和画质,把生成的地址填进 qBittorrent 的 RSS 下载器即可。
新的一集发布后,下载器会自己开始下载。
accordion-demo.tsx
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion"
export default function AccordionDemo() {
return (
<Accordion defaultValue="rss" className="max-w-lg">
<AccordionItem value="rss">
<AccordionTrigger>怎么用 RSS 订阅一部番?</AccordionTrigger>
<AccordionContent>
<p>在番剧页点「订阅 RSS」,挑好字幕组、语言和画质,把生成的地址填进 qBittorrent 的 RSS 下载器即可。</p>
<p>新的一集发布后,下载器会自己开始下载。</p>
</AccordionContent>
</AccordionItem>
<AccordionItem value="magnet">
<AccordionTrigger>磁力链接和种子文件有什么区别?</AccordionTrigger>
<AccordionContent>
磁力链接只是一段地址,需要先从别的用户那里拿到种子信息;种子文件已经带着这些信息,开始得更快。
</AccordionContent>
</AccordionItem>
<AccordionItem value="claim">
<AccordionTrigger>字幕组怎么认领开坑计划?</AccordionTrigger>
<AccordionContent>
进入工作台的「开坑计划」,搜索番剧并选择要做的集数范围。认领后会显示在番剧页的字幕组列表里。
</AccordionContent>
</AccordionItem>
</Accordion>
)
}安装
npx shadcn@latest add @anibt/accordion第一次用之前:在 components.json 里登记 @anibt,再单独运行一次 npx shadcn@latest add @anibt/theme(见快速开始)。只有单独安装主题时,CLI 才会把 shadcn 的标准颜色换成白樱 / 夜樱;随组件带进来的主题只补上 AniBT 自己的变量和配方。
手动安装
- 1. 先装主题组件只读
@anibt/theme的 token 和配方,没有主题就没有样子。npx shadcn@latest add @anibt/theme - 2. 安装依赖
npm install lucide-react radix-ui - 3. 把下面的文件放进项目components/ui/accordion.tsx
"use client" import * as React from "react" import { ChevronDownIcon } from "lucide-react" import { Accordion as AccordionPrimitive } from "radix-ui" import { cn } from "@/lib/utils" /** * Accordion — AniBT 「贴纸手账」 folding sections. * * `variant="default"`: rows parted by the dashed stitch, straight on the * surface they sit on. `variant="cards"`: every section is its own well * (1px edge, radius 10) with 8px between them; the open one takes the pink * edge. Triggers are 14/700 and turn pink under the pointer; the chevron * turns pink and flips when its section is open. * * Radix underneath (keyboard, `aria-expanded`, the height animation from * `tw-animate-css`). It is one section at a time and collapsible unless you * say otherwise: `type="multiple"` (or Base UI's `multiple`) keeps several * open, `collapsible={false}` keeps one always open. */ type AccordionVariant = "default" | "cards" type AccordionProps = ( | Omit<AccordionPrimitive.AccordionSingleProps, "type"> | Omit<AccordionPrimitive.AccordionMultipleProps, "type"> ) & { type?: "single" | "multiple" /** Base UI's spelling of `type="multiple"`. */ multiple?: boolean variant?: AccordionVariant } function Accordion({ className, type, multiple, variant = "default", ...props }: AccordionProps) { const shared = { "data-slot": "accordion", "data-variant": variant, className: cn("group/accordion flex w-full flex-col", variant === "cards" && "gap-2", className), } if (type === "multiple" || multiple) { return <AccordionPrimitive.Root type="multiple" {...shared} {...(props as Omit<AccordionPrimitive.AccordionMultipleProps, "type">)} /> } const single = props as Omit<AccordionPrimitive.AccordionSingleProps, "type"> return <AccordionPrimitive.Root type="single" {...shared} {...single} collapsible={single.collapsible ?? true} /> } function AccordionItem({ className, ...props }: React.ComponentProps<typeof AccordionPrimitive.Item>) { return ( <AccordionPrimitive.Item data-slot="accordion-item" className={cn( "group-data-[variant=default]/accordion:anibt-stitch-under group-data-[variant=default]/accordion:last:bg-none", "group-data-[variant=cards]/accordion:anibt-well group-data-[variant=cards]/accordion:px-4 group-data-[variant=cards]/accordion:transition-shadow group-data-[variant=cards]/accordion:data-[state=open]:shadow-[0_0_0_1px_var(--anibt-wash-line)]", className )} {...props} /> ) } function AccordionTrigger({ className, children, ...props }: React.ComponentProps<typeof AccordionPrimitive.Trigger>) { return ( <AccordionPrimitive.Header className="flex"> <AccordionPrimitive.Trigger data-slot="accordion-trigger" className={cn( "group/accordion-trigger flex min-h-11 flex-1 items-center gap-2.5 rounded-[10px] py-2.5 text-left text-[14px] leading-[1.45] font-bold text-foreground 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 disabled:pointer-events-none disabled:text-muted-foreground [&>svg:not([data-slot=accordion-chevron])]:size-4 [&>svg:not([data-slot=accordion-chevron])]:shrink-0 [&>svg:not([data-slot=accordion-chevron])]:text-muted-foreground", className )} {...props} > {children} <ChevronDownIcon data-slot="accordion-chevron" aria-hidden="true" className="pointer-events-none ml-auto size-4 shrink-0 text-muted-foreground transition-transform duration-300 ease-anibt-spring group-hover/accordion-trigger:text-accent-foreground group-data-[state=open]/accordion-trigger:rotate-180 group-data-[state=open]/accordion-trigger:text-anibt-mark" /> </AccordionPrimitive.Trigger> </AccordionPrimitive.Header> ) } function AccordionContent({ className, children, ...props }: React.ComponentProps<typeof AccordionPrimitive.Content>) { return ( <AccordionPrimitive.Content data-slot="accordion-content" className="overflow-hidden text-[13px] text-anibt-ink2 data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down motion-reduce:animate-none" {...props} > <div className={cn( "pb-3.5 leading-relaxed [&_a]:font-semibold [&_a]:text-accent-foreground [&_a]:underline-offset-4 [&_a:hover]:underline [&_p:not(:last-child)]:mb-3", className )} > {children} </div> </AccordionPrimitive.Content> ) } export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } export type { AccordionProps }
用法
用法
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion"
<Accordion defaultValue="rss">
<AccordionItem value="rss">
<AccordionTrigger>怎么用 RSS 订阅一部番?</AccordionTrigger>
<AccordionContent>在番剧页点「订阅 RSS」……</AccordionContent>
</AccordionItem>
</Accordion>默认一次只开一节,并且可以全部收起——不用再写 type="single" collapsible。type="multiple"(或 Base UI 的 multiple)允许同时展开几节,collapsible={false} 让一节始终开着。
标题是 14/700,指针放上去变粉;展开的那节,右端的箭头翻转并变成粉色。标题里的图标(第一个 svg)是弱色的 16px。键盘、aria-expanded 和高度动画都来自 Radix 与 tw-animate-css。
分组卡
1080pHEVC10bitWebRip
accordion-cards.tsx
import { FileVideoIcon, LanguagesIcon, SubtitlesIcon } from "lucide-react"
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion"
import { Badge } from "@/components/ui/badge"
export default function AccordionCards() {
return (
<Accordion variant="cards" type="multiple" defaultValue={["video"]} className="max-w-lg">
<AccordionItem value="video">
<AccordionTrigger>
<FileVideoIcon />
视频
</AccordionTrigger>
<AccordionContent className="flex flex-wrap gap-1.5">
<Badge variant="res">1080p</Badge>
<Badge>HEVC</Badge>
<Badge>10bit</Badge>
<Badge>WebRip</Badge>
</AccordionContent>
</AccordionItem>
<AccordionItem value="audio">
<AccordionTrigger>
<LanguagesIcon />
音轨
</AccordionTrigger>
<AccordionContent>日语 · AAC 2.0 · 48 kHz</AccordionContent>
</AccordionItem>
<AccordionItem value="subs">
<AccordionTrigger>
<SubtitlesIcon />
字幕
</AccordionTrigger>
<AccordionContent className="flex flex-wrap gap-1.5">
<Badge variant="lang" face="text">
简中
</Badge>
<Badge variant="lang" face="text">
繁中
</Badge>
<Badge face="text">内封字幕</Badge>
</AccordionContent>
</AccordionItem>
</Accordion>
)
}variant="cards" 让每一节成为一个 well(1px 边、圆角 10),节与节之间 8px,展开的那节换成粉色的边。
属性
Accordion
variant"default" | "cards"默认 "default"缝线分隔的行 / 一节一张分组卡
type"single" | "multiple"默认 "single"一次开一节 / 可以开几节
multipleboolean默认 falseBase UI 的写法,等同 type=multiple
collapsibleboolean默认 truesingle 时能否全部收起
defaultValue · value · onValueChangeRadix默认 —single 时是 string,multiple 时是 string[]
AccordionItem、AccordionTrigger、AccordionContent 是 Radix 的对应部分,属性原样透传。
