Progress
下沉的轨道和带光泽的填充:蓝色是进行中,糖果粉是你自己的目标,薄荷是已完成。
progress-demo.tsx
import { Progress } from "@/components/ui/progress"
export default function ProgressDemo() {
return (
<div className="flex w-full max-w-sm flex-col gap-5">
<Progress value={62} aria-label="上传进度" />
<Progress value={38} tone="candy" aria-label="本月发布目标" />
<Progress value={100} tone="ok" aria-label="同步完成" />
<Progress value={45} size="compact" aria-label="下载进度" />
</div>
)
}安装
npx shadcn@latest add @anibt/progress第一次用之前:在 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 class-variance-authority radix-ui - 3. 把下面的文件放进项目components/ui/progress.tsx
"use client" import * as React from "react" import { cva, type VariantProps } from "class-variance-authority" import { Progress as ProgressPrimitive } from "radix-ui" import { cn } from "@/lib/utils" /** * Progress — the AniBT 「贴纸手账」 bar. * * A sunk track with a 1px edge and a glossy fill. Blue is A's colour for * "in progress", so the fill is `water` by default; `tone="candy"` is for a * quota or a goal the reader is filling (pink = the reader's own action) and * `tone="ok"` for work that is done. `value={null}` is indeterminate: a short * fill slides along the track (it holds still under reduced motion). * * `<Progress value={40} />` is shadcn's shape. Children turn it into Base UI's * shape — a label and a figure above the track: * * <Progress value={3} max={5}> * <ProgressLabel>同步目标</ProgressLabel> * <ProgressValue /> → 3 / 5 (or a percentage, see `format`) * </Progress> */ const trackVariants = cva( "relative w-full overflow-hidden rounded-full bg-muted shadow-[inset_0_0_0_1px_var(--border)]", { variants: { size: { default: "h-2.5", compact: "h-1.5", lg: "h-3.5", }, }, defaultVariants: { size: "default", }, } ) const indicatorVariants = cva( "h-full w-full rounded-full transition-transform duration-500 ease-anibt-out motion-reduce:transition-none", { variants: { tone: { water: "anibt-water shadow-[inset_0_1px_0_rgba(255,255,255,0.45)]", candy: "anibt-candy shadow-[inset_0_1px_0_rgba(255,255,255,0.45)]", ok: "bg-anibt-ok-fill shadow-[inset_0_1px_0_rgba(255,255,255,0.35)]", }, }, defaultVariants: { tone: "water", }, } ) type ProgressContextValue = { value: number | null; max: number } const ProgressContext = React.createContext<ProgressContextValue>({ value: null, max: 100 }) type ProgressProps = Omit<React.ComponentProps<typeof ProgressPrimitive.Root>, "value" | "max"> & VariantProps<typeof trackVariants> & VariantProps<typeof indicatorVariants> & { /** `null` (or leaving it out) is indeterminate. */ value?: number | null max?: number /** Classes for the track when the progress has children (label row). */ trackClassName?: string } function Progress({ className, trackClassName, value = null, max = 100, size, tone, children, ...props }: ProgressProps) { const track = ( <ProgressPrimitive.Root data-slot="progress" data-tone={tone ?? "water"} value={value} max={max} className={cn(trackVariants({ size }), children ? trackClassName : className)} {...props} > <ProgressIndicator tone={tone} /> </ProgressPrimitive.Root> ) if (!children) { return <ProgressContext.Provider value={{ value, max }}>{track}</ProgressContext.Provider> } return ( <ProgressContext.Provider value={{ value, max }}> <div data-slot="progress-group" className={cn("flex w-full flex-wrap items-baseline gap-x-3 gap-y-1.5", className)}> {children} {track} </div> </ProgressContext.Provider> ) } /** The fill. Rendered by `Progress`; exported for shadcn parity. */ function ProgressIndicator({ className, tone, style, ...props }: React.ComponentProps<typeof ProgressPrimitive.Indicator> & VariantProps<typeof indicatorVariants>) { const { value, max } = React.useContext(ProgressContext) const indeterminate = value === null const percent = indeterminate ? 0 : Math.min(100, Math.max(0, (value / max) * 100)) return ( <ProgressPrimitive.Indicator data-slot="progress-indicator" className={cn( indicatorVariants({ tone }), indeterminate && "w-2/5 animate-anibt-indeterminate motion-reduce:animate-none", className )} style={indeterminate ? style : { transform: `translateX(-${100 - percent}%)`, ...style }} {...props} /> ) } /** The track on its own, for a hand-built layout (shadcn `base-*` parity). */ function ProgressTrack({ className, size, ...props }: React.ComponentProps<"div"> & VariantProps<typeof trackVariants>) { return <div data-slot="progress-track" className={cn(trackVariants({ size }), className)} {...props} /> } /** What is progressing: 13/600, the field-label voice. */ function ProgressLabel({ className, ...props }: React.ComponentProps<"span">) { return ( <span data-slot="progress-label" className={cn("text-[13px] font-semibold text-anibt-ink2", className)} {...props} /> ) } /** * The figure at the right end of the label row, in the round face. Without * children it prints the value: `3 / 5`, or `60%` when `max` is 100 or * `format="percent"`. */ function ProgressValue({ className, children, format, ...props }: React.ComponentProps<"span"> & { format?: "fraction" | "percent" }) { const { value, max } = React.useContext(ProgressContext) const asPercent = format ? format === "percent" : max === 100 const text = value === null ? "—" : asPercent ? `${Math.round((value / max) * 100)}%` : `${value} / ${max}` return ( <span data-slot="progress-value" className={cn("anibt-num ml-auto text-[12px] font-semibold text-muted-foreground", className)} {...props} > {children ?? text} </span> ) } export { Progress, ProgressIndicator, ProgressTrack, ProgressLabel, ProgressValue } export type { ProgressProps }
用法
用法
import { Progress } from "@/components/ui/progress"
<Progress value={62} aria-label="上传进度" />一种颜色一种职责:tone="water"(默认)是蓝色,表示进行中;tone="candy" 给读者自己在填的目标(本月发布、追番进度);tone="ok" 表示已经完成。size 有 compact(6 高,放在行里)、default(10 高)和 lg(14 高)。
标签与数值
同步到外站3 / 5
本季追番进度72%
正在解析种子请稍候
progress-label.tsx
import { Progress, ProgressLabel, ProgressValue } from "@/components/ui/progress"
export default function ProgressLabelDemo() {
return (
<div className="flex w-full max-w-sm flex-col gap-5">
<Progress value={3} max={5}>
<ProgressLabel>同步到外站</ProgressLabel>
<ProgressValue />
</Progress>
<Progress value={72} tone="candy">
<ProgressLabel>本季追番进度</ProgressLabel>
<ProgressValue />
</Progress>
<Progress value={null}>
<ProgressLabel>正在解析种子</ProgressLabel>
<ProgressValue>请稍候</ProgressValue>
</Progress>
</div>
)
}有子元素时,进度条变成 Base UI 的样子:上面一行是 ProgressLabel 和 ProgressValue,下面是轨道。ProgressValue 不写内容时自己算:max 是 100 时显示百分比,否则显示 3 / 5;format 可以强制其中一种。
value={null}(或不传)是不确定进度:一小段填充沿着轨道来回滑动,减少动态效果时停住。
属性
Progress
valuenumber | null默认 null当前值;null 为不确定进度
maxnumber默认 100最大值
tone"water" | "candy" | "ok"默认 "water"进行中 / 你的目标 / 已完成
size"compact" | "default" | "lg"默认 "default"6 / 10 / 14 高
trackClassNamestring默认 —有标签行时,轨道自己的 class
ProgressValue
format"fraction" | "percent"默认 —不写时 max 为 100 显示百分比,否则显示分数
为了和 shadcn 对齐,还导出 ProgressIndicator、ProgressTrack、ProgressLabel。
