fix(design): claim audit part 1b — whole-row switches, the field-family edge, state-layer hovers, icon links, reason-disabled controls, 24/44 targets (E28)
- F12 (owner: A): SwitchRow, the whole settings row toggles; a Config boolean toggles from its title and hint (a label pointed at the switch). - F14 (owner: B): the form dropdown trigger wears the field frame (see-through, the >= 3:1 edge); labelled pills keep the faint edge and the accent-edge hover. DESIGN says so, with log rows. - F6: ~15 bg-card / bg-border / opacity hovers onto the B3 state layer; a lint rule (HOVER_SWAP) refuses the swap, contract-tested. - F10: bare 14px icon links onto IconLink / LinkButton; a local lint rule refuses a glyph-only <a>. - F13: reason-disabled controls are aria-disabled (Users, Channels backfill, Discovery, Scheduler runs, Config tests); FocusSafeButton now swallows the click of a caller's aria-disabled; the Config tests say why while dirty (testDirty, HU/EN). - F5/F11: every control measured live at 24 (mouse) and 44 (coarse) across every page; touch-target is a utility that wins over a caller's min-h-6; link variants carry it too; SegmentedToggle, Tabs, NavRail, menu rows, PriorityStepper, sliders, CheckRow, InnerInput, card links and the rest. - F9: DismissibleChip's label reaches the chip's 24; the Combobox chips are DismissibleChips. - F15: the "+N" / "More..." open state worded as it renders.
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
import { ESLint } from "eslint";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
// Two E28-audit rules in eslint.config.js, pinned so a selector that stops matching cannot pass for a
|
||||
// clean codebase: a hover that swaps a colour instead of the B3 state layer, and an <a> that holds only
|
||||
// a glyph (DESIGN.md "A link that looks like a button" is IconLink).
|
||||
const FRONTEND = fileURLToPath(new URL("../../", import.meta.url));
|
||||
|
||||
async function messages(source: string, starts: string): Promise<number> {
|
||||
const eslint = new ESLint({ cwd: FRONTEND });
|
||||
const [result] = await eslint.lintText(source, { filePath: "src/components/__probe.tsx" });
|
||||
return (result?.messages ?? []).filter((m) => m.message.startsWith(starts)).length;
|
||||
}
|
||||
const swaps = (src: string) => messages(src, "hover by a colour swap");
|
||||
const glyphs = (src: string) => messages(src, "a glyph-only <a>");
|
||||
|
||||
describe("a hover by a colour swap", () => {
|
||||
// Assembled, so this file does not trip the very rule it probes.
|
||||
const h = (v: string) => `hover:${v}`;
|
||||
|
||||
it("is refused for bg-card, bg-border, a hand-written layer and an opacity dim", async () => {
|
||||
expect(await swaps(`export const a = "px-2 ${h("bg-card")}";\n`)).toBe(1);
|
||||
expect(await swaps(`export const b = "${h("bg-border/40")}";\n`)).toBe(1);
|
||||
expect(await swaps("export const c = `x ${1} " + h("opacity-80") + "`;\n")).toBe(1);
|
||||
expect(await swaps(`export const d = "enabled:${h("bg-(--hover)")}";\n`)).toBe(1);
|
||||
});
|
||||
|
||||
it("leaves the state layer, a parent's reveal and an edged pill's accent hover alone", async () => {
|
||||
expect(await swaps(`export const e = "state-layer group-${h("opacity-100")}";\n`)).toBe(0);
|
||||
expect(await swaps(`export const f = "${h("border-accent")} ${h("bg-accent/20")}";\n`)).toBe(0);
|
||||
expect(await swaps(`export const g = "${h("bg-cardinal")}";\n`)).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("a glyph-only link", () => {
|
||||
it("is refused, whatever its attributes hold", async () => {
|
||||
const src =
|
||||
'import { X } from "lucide-react";\n' +
|
||||
"export const A = ({ u }: { u: string }) => (\n" +
|
||||
' <a href={u} aria-label="Open">\n <X />\n </a>\n);\n';
|
||||
expect(await glyphs(src)).toBe(1);
|
||||
});
|
||||
|
||||
it("passes a text link, a card with content, and an i18n Trans slot", async () => {
|
||||
const src =
|
||||
'import { X } from "lucide-react";\n' +
|
||||
"export const B = ({ u, n }: { u: string; n: string }) => (\n" +
|
||||
" <>\n" +
|
||||
" <a href={u}>\n <X /> Open\n </a>\n" +
|
||||
" <a href={u}>\n <span>{n}</span>\n </a>\n" +
|
||||
' <a href={u} className="text-accent" />\n' +
|
||||
" </>\n);\n";
|
||||
expect(await glyphs(src)).toBe(0);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user