AniBTUI
✦ 组件@anibt/cardregistry JSON

Card

AniBT 的面板:纸面、1px 外边、面板阴影、圆角 14,带缝线的头和脚;面板里的分组用 well。

我的订阅
喵萌奶茶屋 · 简繁内封 · 1080p
3
药屋少女的呢喃 第三季
1080p第 12 集
已更新
葬送的芙莉莲 第二季
1080p第 9 集
搬运中
间谍过家家 第三季
1080p第 4 集
延期

安装

npx shadcn@latest add @anibt/card

第一次用之前:在 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. 把下面的文件放进项目
    components/ui/card.tsx
    import * as React from "react"
    
    import { cn } from "@/lib/utils"
    
    /**
     * Card — an AniBT 「贴纸手账」 panel.
     *
     * `variant="panel"` (default) is paper on the ground under a 1px border edge
     * and the panel shadow, radius 14 — the `anibt-sheet` recipe. `variant="well"`
     * is a group inside a panel: the edge only, radius 10, no lift.
     *
     * The box is A's: 16px of padding and 12px between header, body and footer;
     * `density="compact"` tightens it to 12 / 8. Vertical padding and the gap are
     * the `--card-py` / `--card-gap` variables, so a caller's `py-0` / `gap-0`
     * still wins through `cn`.
     *
     * `CardHeader stitched` / `CardFooter stitched` part the head or the foot
     * from the body with A's dashed stitch, edge to edge.
     */
    function Card({
      className,
      variant = "panel",
      density = "default",
      ...props
    }: React.ComponentProps<"div"> & {
      variant?: "panel" | "well"
      density?: "default" | "compact"
    }) {
      return (
        <div
          data-slot="card"
          data-variant={variant}
          data-density={density}
          className={cn(
            "group/card flex flex-col gap-(--card-gap) py-(--card-py) [--card-gap:0.75rem] [--card-py:1rem] data-[density=compact]:[--card-gap:0.5rem] data-[density=compact]:[--card-py:0.75rem]",
            variant === "well" ? "anibt-well" : "anibt-sheet",
            className
          )}
          {...props}
        />
      )
    }
    
    function CardHeader({
      className,
      stitched = false,
      ...props
    }: React.ComponentProps<"div"> & { stitched?: boolean }) {
      return (
        <div
          data-slot="card-header"
          data-stitched={stitched || undefined}
          className={cn(
            "@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1 px-4 group-data-[density=compact]/card:px-3 has-data-[slot=card-action]:grid-cols-[1fr_auto]",
            stitched && "anibt-stitch-under pb-3 group-data-[density=compact]/card:pb-2",
            className
          )}
          {...props}
        />
      )
    }
    
    /** A panel title: 16/800. */
    function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
      return (
        <div
          data-slot="card-title"
          className={cn("text-[16px] leading-[1.4] font-extrabold text-card-foreground", className)}
          {...props}
        />
      )
    }
    
    /** The line under a title: 12, muted. */
    function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
      return (
        <div
          data-slot="card-description"
          className={cn("text-[12px] leading-normal text-muted-foreground", className)}
          {...props}
        />
      )
    }
    
    /** The tools at the right end of a header row. */
    function CardAction({ className, ...props }: React.ComponentProps<"div">) {
      return (
        <div
          data-slot="card-action"
          className={cn("col-start-2 row-span-2 row-start-1 flex items-center gap-2 self-start justify-self-end", className)}
          {...props}
        />
      )
    }
    
    function CardContent({ className, ...props }: React.ComponentProps<"div">) {
      return (
        <div
          data-slot="card-content"
          className={cn("px-4 text-[14px] group-data-[density=compact]/card:px-3", className)}
          {...props}
        />
      )
    }
    
    function CardFooter({
      className,
      stitched = false,
      ...props
    }: React.ComponentProps<"div"> & { stitched?: boolean }) {
      return (
        <div
          data-slot="card-footer"
          data-stitched={stitched || undefined}
          className={cn(
            "flex items-center gap-2 px-4 group-data-[density=compact]/card:px-3",
            stitched && "anibt-stitch-over pt-3 group-data-[density=compact]/card:pt-2",
            className
          )}
          {...props}
        />
      )
    }
    
    export { Card, CardHeader, CardFooter, CardTitle, CardAction, CardDescription, CardContent }

用法

components/subscription-card.tsx
import { Button } from "@/components/ui/button"
import {
  Card,
  CardAction,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"

export function SubscriptionCard() {
  return (
    <Card>
      <CardHeader stitched>
        <CardTitle>我的订阅</CardTitle>
        <CardDescription>喵萌奶茶屋 · 简繁内封</CardDescription>
        <CardAction>…</CardAction>
      </CardHeader>
      <CardContent>…</CardContent>
      <CardFooter stitched className="justify-end">
        <Button>保存</Button>
      </CardFooter>
    </Card>
  )
}

CardHeader stitched 在头下面缝一条虚线,CardFooter stitched 在脚上面缝一条,都从卡片一边通到另一边。标题是 16/800,说明是 12 号弱字,CardAction 放在头的右端。

分组

订阅设置
面板里的分组用 well:只留 1px 边,不再抬起。
字幕
简中繁中
画质与格式
1080pMKV

面板里的面板用 variant="well":只留 1px 边,圆角 10,不再抬起。不要在面板里再叠一层带阴影的面板。

密度

默认 16 / 12
页面里的面板
24
紧凑 12 / 8
后台、工作台的密集卡
24

默认的盒子是 16px 内边距、12px 的块间距;density="compact" 收紧到 12 / 8,给后台和工作台的密集卡用。垂直内边距和间距是 --card-py / --card-gap 变量,调用处的 py-0、gap-0 依然能覆盖。

属性

Card
variant"panel" | "well"默认 "panel"panel 是带阴影的面板(anibt-sheet);well 是面板里的分组(anibt-well)
density"default" | "compact"默认 "default"16 / 12 或 12 / 8 的内边距与间距
CardHeader · CardFooter
stitchedboolean默认 false头的下边 / 脚的上边缝一条虚线

CardTitle、CardDescription、CardAction、CardContent 没有额外属性,其余属性都透传给 div。