Files
writer-work-flow/apps/web/lib/ui/variants.test.ts
Yaojia Wang b9f9134748 feat(ui): 补齐分层设计 token 并迁移共享变体(对标 Claude DESIGN.md)
- 面色阶 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;双主题成对定义
2026-07-11 07:16:00 +02:00

98 lines
3.1 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
badgeClass,
buttonClass,
cardClass,
cn,
focusRing,
inputClass,
overlayScrim,
proseBody,
segmentedClass,
statusNoteClass,
transitionUi,
} from "./variants";
describe("ui variants", () => {
it("joins classes without falsey values", () => {
expect(cn("a", false, undefined, null, "b")).toBe("a b");
});
it("builds stable primary button classes", () => {
const klass = buttonClass({ variant: "primary", size: "sm" });
expect(klass).toContain("bg-cinnabar");
expect(klass).toContain("px-3");
expect(klass).toContain("focus-visible:ring-2");
});
it("uses layered radius tokens (button md / card lg / badge pill)", () => {
expect(buttonClass({ variant: "primary" })).toContain("rounded-md");
expect(cardClass()).toContain("rounded-lg");
expect(badgeClass()).toContain("rounded-full");
expect(inputClass()).toContain("rounded-md");
});
it("gives the primary button a press-darken and cream disabled state", () => {
const klass = buttonClass({ variant: "primary" });
expect(klass).toContain("hover:bg-cinnabar-active");
expect(klass).toContain("active:bg-cinnabar-active");
expect(klass).toContain("disabled:bg-cinnabar-disabled");
expect(klass).not.toContain("hover:bg-cinnabar/95");
});
it("shares one interaction-transition token across controls", () => {
expect(transitionUi).toContain("duration-fast");
expect(transitionUi).toContain("ease-standard");
expect(buttonClass({ variant: "primary" })).toContain(transitionUi);
expect(inputClass()).toContain(transitionUi);
});
it("keeps warning badges readable on the paper background", () => {
const klass = badgeClass({ variant: "warning" });
expect(klass).toContain("bg-overdue/10");
expect(klass).toContain("text-ink");
});
it("marks invalid inputs with conflict styling", () => {
const klass = inputClass({ state: "error" });
expect(klass).toContain("border-conflict");
expect(klass).toContain("focus-visible:ring-conflict/25");
});
it("gives warning status notes a non-color icon slot friendly shell", () => {
const klass = statusNoteClass({ variant: "warning" });
expect(klass).toContain("border-overdue");
expect(klass).toContain("px-3");
});
it("builds segmented control chrome", () => {
const klass = segmentedClass();
expect(klass).toContain("inline-flex");
expect(klass).toContain("border-line");
});
it("shares the cinnabar focus ring with the button base", () => {
expect(focusRing).toContain("focus-visible:ring-2");
expect(focusRing).toContain("focus-visible:ring-cinnabar/35");
expect(buttonClass({ variant: "primary" })).toContain(focusRing);
});
it("renders manuscript body as serif prose scale", () => {
expect(proseBody).toContain("font-serif");
expect(proseBody).toContain("text-prose");
});
it("provides a full-screen overlay scrim below dialog chrome", () => {
expect(overlayScrim).toContain("fixed");
expect(overlayScrim).toContain("inset-0");
expect(overlayScrim).toContain("z-40");
});
});