export type ButtonVariant = | "primary" | "secondary" | "outline" | "ghost" | "danger"; export type ButtonSize = "sm" | "md" | "icon"; export type BadgeVariant = | "neutral" | "accent" | "success" | "warning" | "danger" | "info"; export type InputState = "default" | "error"; export type StatusNoteVariant = "info" | "success" | "warning" | "danger"; export function cn(...classes: Array): string { return classes.filter(Boolean).join(" "); } // 统一焦点环(供按钮/输入/导航等所有可聚焦元素复用,统一到 cinnabar/35)。 export const focusRing = "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-cinnabar/35"; // 统一交互过渡(颜色变化 + 动效 token),供按钮/输入/导航复用,全局可调。 export const transitionUi = "transition-colors duration-fast ease-standard"; // 手稿正文统一排版:衬线 18px(text-prose 档)、行高 1.9。 export const proseBody = "font-serif text-prose"; // Drawer / CommandPalette 共用的全屏遮罩。 export const overlayScrim = "fixed inset-0 z-40 bg-black/30"; const buttonBase = `inline-flex items-center justify-center gap-1.5 rounded-md border text-sm disabled:cursor-not-allowed ${transitionUi} ${focusRing}`; // 非主按钮的禁用淡化(主按钮改用奶油化禁用底,见下)。 const disabledFade = "disabled:opacity-45"; const buttonVariants: Record = { primary: "border-cinnabar bg-cinnabar text-panel shadow-paper hover:bg-cinnabar-active active:bg-cinnabar-active disabled:border-cinnabar-disabled disabled:bg-cinnabar-disabled disabled:text-muted-soft", secondary: `border-line bg-panel text-ink hover:border-cinnabar hover:text-cinnabar ${disabledFade}`, outline: `border-cinnabar bg-transparent text-cinnabar hover:bg-[var(--color-cinnabar-wash)] active:bg-[var(--color-cinnabar-wash)] ${disabledFade}`, ghost: `border-transparent bg-transparent text-ink-soft hover:bg-[var(--color-cinnabar-wash)] hover:text-cinnabar ${disabledFade}`, danger: `border-conflict bg-transparent text-conflict hover:bg-conflict/10 ${disabledFade}`, }; const buttonSizes: Record = { sm: "px-3 py-1.5 text-xs", md: "px-4 py-2", icon: "h-9 w-9 p-0", }; export function buttonClass({ variant = "secondary", size = "md", className, }: { variant?: ButtonVariant; size?: ButtonSize; className?: string; } = {}): string { return cn(buttonBase, buttonVariants[variant], buttonSizes[size], className); } const badgeBase = "inline-flex items-center gap-1 rounded-full border px-2.5 py-0.5 text-xs leading-5"; const badgeVariants: Record = { neutral: "border-line bg-bg text-ink-soft", accent: "border-cinnabar/20 bg-[var(--color-cinnabar-wash)] text-cinnabar", success: "border-pass/25 bg-pass/10 text-pass", warning: "border-overdue/35 bg-overdue/10 text-ink", danger: "border-conflict/25 bg-conflict/10 text-conflict", info: "border-info/25 bg-info/10 text-info", }; export function badgeClass({ variant = "neutral", className, }: { variant?: BadgeVariant; className?: string; } = {}): string { return cn(badgeBase, badgeVariants[variant], className); } export function cardClass(className?: string): string { return cn("rounded-lg border border-line bg-panel shadow-paper", className); } const fieldTextBase = "block text-sm font-medium text-ink"; export function fieldLabelClass(className?: string): string { return cn(fieldTextBase, className); } export function fieldHelpClass(className?: string): string { return cn("mt-1 text-xs leading-5 text-ink-soft", className); } export function fieldErrorClass(className?: string): string { return cn("mt-1 text-xs leading-5 text-conflict", className); } const inputBase = `w-full rounded-md border bg-bg text-ink placeholder:text-ink-soft/80 disabled:cursor-not-allowed disabled:bg-line/20 disabled:text-ink-soft ${transitionUi} ${focusRing}`; const inputStates: Record = { default: "border-line focus:border-cinnabar", error: "border-conflict focus:border-conflict focus-visible:ring-conflict/25", }; export function inputClass({ state = "default", className, }: { state?: InputState; className?: string; } = {}): string { return cn(inputBase, inputStates[state], className); } const statusNoteBase = "rounded border px-3 py-2 text-sm leading-6"; const statusNoteVariants: Record = { info: "border-info/25 bg-info/10 text-info", success: "border-pass/25 bg-pass/10 text-pass", warning: "border-overdue/35 bg-overdue/10 text-ink", danger: "border-conflict/25 bg-conflict/10 text-conflict", }; export function statusNoteClass({ variant = "info", className, }: { variant?: StatusNoteVariant; className?: string; } = {}): string { return cn(statusNoteBase, statusNoteVariants[variant], className); } export function segmentedClass(className?: string): string { return cn("inline-flex rounded border border-line bg-bg p-1", className); }