Radio Group
18px 的糖果单选:选中的一项是糖果粉加白点,和勾选框同一种填充。
radio-group-demo.tsx
import { Field, FieldLabel, FieldLegend, FieldSet } from "@/components/ui/field"
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
const QUALITIES = [
{ value: "2160p", label: "2160p · 4K" },
{ value: "1080p", label: "1080p" },
{ value: "720p", label: "720p · 省流量" },
]
export default function RadioGroupDemo() {
return (
<FieldSet className="w-fit">
<FieldLegend variant="label">默认画质</FieldLegend>
<RadioGroup defaultValue="1080p">
{QUALITIES.map((quality) => (
<Field key={quality.value} orientation="horizontal">
<RadioGroupItem value={quality.value} id={`radio-group-demo-${quality.value}`} />
<FieldLabel htmlFor={`radio-group-demo-${quality.value}`}>{quality.label}</FieldLabel>
</Field>
))}
</RadioGroup>
</FieldSet>
)
}安装
npx shadcn@latest add @anibt/radio-group第一次用之前:在 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 radix-ui - 3. 把下面的文件放进项目components/ui/radio-group.tsx
"use client" import * as React from "react" import { RadioGroup as RadioGroupPrimitive } from "radix-ui" import { cn } from "@/lib/utils" /** * RadioGroup — A's candy radio (AniBT 「贴纸手账」). * * An 18px paper disc inside a 1.5px `--input` ring (pink under the pointer); * the chosen one is candy with a white dot that pops in, the same fill as a * ticked checkbox, so a column of options reads at a glance. Focus is the * 2px `--ring` outline outside the disc; disabled is the sunk well with a * grey dot. Arrow keys move the choice (Radix roving focus). */ function RadioGroup({ className, ...props }: React.ComponentProps<typeof RadioGroupPrimitive.Root>) { return <RadioGroupPrimitive.Root data-slot="radio-group" className={cn("grid w-full gap-3", className)} {...props} /> } function RadioGroupItem({ className, ...props }: React.ComponentProps<typeof RadioGroupPrimitive.Item>) { return ( <RadioGroupPrimitive.Item data-slot="radio-group-item" className={cn( "group/radio peer relative inline-flex aspect-square size-[18px] shrink-0 items-center justify-center rounded-full bg-card shadow-[inset_0_0_0_1.5px_var(--input)] outline-hidden transition-[background-color,box-shadow] duration-150 after:absolute after:-inset-2", "enabled:hover:shadow-[inset_0_0_0_1.5px_var(--anibt-wash-line)]", "data-[state=checked]:bg-anibt-candy-edge data-[state=checked]:bg-(image:--anibt-candy) data-[state=checked]:shadow-[0_1px_0_var(--anibt-candy-edge)]", "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring focus-visible:outline-solid", "aria-invalid:outline-2 aria-invalid:outline-offset-2 aria-invalid:outline-anibt-danger-fill aria-invalid:outline-solid", "disabled:cursor-not-allowed disabled:bg-muted disabled:shadow-[inset_0_0_0_1.5px_var(--border)] disabled:data-[state=checked]:bg-anibt-deep2 disabled:data-[state=checked]:bg-none disabled:data-[state=checked]:shadow-[inset_0_0_0_1.5px_var(--border)]", className )} {...props} > <RadioGroupPrimitive.Indicator data-slot="radio-group-indicator" className="flex items-center justify-center"> <span className="block size-2 animate-anibt-pop rounded-full bg-white group-disabled/radio:bg-muted-foreground" /> </RadioGroupPrimitive.Indicator> </RadioGroupPrimitive.Item> ) } export { RadioGroup, RadioGroupItem }
用法
用法
import { Field, FieldLabel } from "@/components/ui/field"
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
<RadioGroup defaultValue="1080p">
<Field orientation="horizontal">
<RadioGroupItem value="1080p" id="q-1080" />
<FieldLabel htmlFor="q-1080">1080p</FieldLabel>
</Field>
<Field orientation="horizontal">
<RadioGroupItem value="720p" id="q-720" />
<FieldLabel htmlFor="q-720">720p</FieldLabel>
</Field>
</RadioGroup>两三个短选项横着排、而且是视图或筛选时,用 Segmented Control;选项需要说明时用下面的选择卡。
选择卡
radio-group-cards.tsx
import {
Field,
FieldContent,
FieldDescription,
FieldLabel,
FieldLegend,
FieldSet,
FieldTitle,
} from "@/components/ui/field"
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
const METHODS = [
{ value: "magnet", title: "磁力链接", description: "交给本机的下载器打开。" },
{ value: "torrent", title: "种子文件", description: "下载 .torrent,适合离线导入。" },
{ value: "push", title: "推送到 qBittorrent", description: "需要先在设置里连接 WebUI。", disabled: true },
]
export default function RadioGroupCards() {
return (
<FieldSet className="w-full max-w-md">
<FieldLegend variant="label">下载方式</FieldLegend>
<RadioGroup defaultValue="magnet" className="gap-2">
{METHODS.map((method) => (
<FieldLabel key={method.value} htmlFor={`radio-group-cards-${method.value}`}>
<Field orientation="horizontal" data-disabled={method.disabled || undefined}>
<RadioGroupItem
value={method.value}
id={`radio-group-cards-${method.value}`}
disabled={method.disabled}
/>
<FieldContent>
<FieldTitle>{method.title}</FieldTitle>
<FieldDescription>{method.description}</FieldDescription>
</FieldContent>
</Field>
</FieldLabel>
))}
</RadioGroup>
</FieldSet>
)
}FieldLabel 包住横排的 Field:整张卡都能点,选中的卡是粉色淡底加粉边。禁用的选项字变灰、悬停不变色。
状态
纸面圆点加 1.5px input 色内边,悬停变粉;选中是糖果渐变加一个弹入的白点。焦点是 2px ring 色轮廓;禁用是下沉的浅底、灰点。方向键在选项间移动并选中(Radix 的 roving focus),整组只占一个 Tab 位置。
属性
RadioGroup
valuestring默认 —受控的选中值
defaultValuestring默认 —非受控的初始值
onValueChange(value: string) => void默认 —选中改变时调用
namestring默认 —放进 form 时提交的字段名
disabledboolean默认 —整组禁用
RadioGroupItem
valuestring默认 —这一项的值
disabledboolean默认 —只禁用这一项
