- 面色阶 surface-soft/card/strong + 文字 5 级 body-strong/body/muted-soft - 边框 line-soft、coral 状态 cinnabar-active/disabled(按压变深 + 奶油化禁用) - 圆角刻度 xs..xl/full:按钮 rounded-md、卡片 rounded-lg、徽章 pill - 排版档 display/title/caption/eyebrow/prose(衬线负字距) - 动效 token duration-fast/base + ease-standard,transitionUi 常量统一过渡 - 焦点环统一 cinnabar/35;双主题成对定义
51 lines
1.2 KiB
TypeScript
51 lines
1.2 KiB
TypeScript
import type { ReactNode } from "react";
|
|
|
|
import { cn, focusRing, segmentedClass, transitionUi } from "@/lib/ui/variants";
|
|
|
|
export interface SegmentOption<T extends string> {
|
|
value: T;
|
|
label: ReactNode;
|
|
}
|
|
|
|
interface SegmentedControlProps<T extends string> {
|
|
options: Array<SegmentOption<T>>;
|
|
value: T;
|
|
onChange: (value: T) => void;
|
|
ariaLabel: string;
|
|
className?: string;
|
|
}
|
|
|
|
export function SegmentedControl<T extends string>({
|
|
options,
|
|
value,
|
|
onChange,
|
|
ariaLabel,
|
|
className,
|
|
}: SegmentedControlProps<T>) {
|
|
return (
|
|
<div className={segmentedClass(className)} role="group" aria-label={ariaLabel}>
|
|
{options.map((option) => {
|
|
const selected = option.value === value;
|
|
return (
|
|
<button
|
|
key={option.value}
|
|
type="button"
|
|
aria-pressed={selected}
|
|
onClick={() => onChange(option.value)}
|
|
className={cn(
|
|
"rounded-md px-3 py-1.5 text-sm",
|
|
transitionUi,
|
|
focusRing,
|
|
selected
|
|
? "bg-panel text-cinnabar shadow-paper"
|
|
: "text-ink-soft hover:text-cinnabar",
|
|
)}
|
|
>
|
|
{option.label}
|
|
</button>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
}
|