@robomous/ui-core 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -7
- package/dist/{primitives → components}/alert.js +1 -1
- package/dist/{primitives → components}/badge.js +1 -1
- package/dist/{primitives → components}/button.d.ts +1 -1
- package/dist/{primitives → components}/button.js +4 -1
- package/dist/{primitives → components}/card.d.ts +1 -1
- package/dist/{primitives → components}/card.js +2 -2
- package/dist/{primitives → components}/combobox.js +4 -4
- package/dist/{primitives → components}/dialog.js +1 -1
- package/dist/{primitives → components}/dropdown-menu.js +12 -7
- package/dist/{primitives → components}/field.js +2 -4
- package/dist/{primitives → components}/input-group.js +1 -1
- package/dist/{primitives → components}/input.js +1 -1
- package/dist/{primitives → components}/label.js +1 -1
- package/dist/components/progress.js +7 -0
- package/dist/{primitives → components}/select.d.ts +3 -1
- package/dist/{primitives → components}/select.js +7 -4
- package/dist/{primitives → components}/separator.js +1 -1
- package/dist/{primitives → components}/sheet.js +1 -1
- package/dist/{primitives → components}/skeleton.js +1 -1
- package/dist/{primitives → components}/sonner.js +17 -12
- package/dist/{primitives → components}/table.d.ts +1 -1
- package/dist/{primitives → components}/table.js +3 -3
- package/dist/{primitives → components}/tabs.js +1 -1
- package/dist/{primitives → components}/textarea.js +1 -1
- package/dist/{primitives → components}/tooltip.js +1 -1
- package/dist/gates/index.d.ts +28 -0
- package/dist/gates/index.js +142 -0
- package/dist/index.d.ts +31 -32
- package/dist/index.js +32 -33
- package/dist/{tokens.d.ts → theme/tokens.d.ts} +5 -5
- package/dist/{tokens.js → theme/tokens.js} +12 -12
- package/package.json +23 -12
- package/{shadcn → src/components}/alert.tsx +14 -21
- package/src/{primitives → components}/badge.tsx +15 -21
- package/src/{primitives → components}/button.tsx +13 -10
- package/{shadcn → src/components}/card.tsx +16 -31
- package/src/{primitives → components}/combobox.test.tsx +15 -2
- package/src/{primitives → components}/combobox.tsx +45 -74
- package/src/{primitives/primitives.test.tsx → components/components.test.tsx} +106 -69
- package/src/{primitives → components}/dialog.tsx +31 -54
- package/src/{primitives → components}/dropdown-menu.tsx +51 -65
- package/src/{primitives → components}/field.tsx +48 -62
- package/src/{primitives → components}/input-group.tsx +37 -49
- package/{shadcn → src/components}/input.tsx +5 -5
- package/{shadcn → src/components}/label.tsx +7 -10
- package/src/{primitives → components}/progress.tsx +7 -6
- package/{shadcn → src/components}/select.tsx +40 -40
- package/{shadcn → src/components}/separator.tsx +7 -7
- package/src/{primitives → components}/sheet.tsx +27 -44
- package/{shadcn → src/components}/skeleton.tsx +3 -3
- package/src/{primitives → components}/sonner.test.tsx +12 -4
- package/src/components/sonner.tsx +67 -0
- package/{shadcn → src/components}/table.tsx +17 -44
- package/{shadcn → src/components}/tabs.tsx +16 -26
- package/{shadcn → src/components}/textarea.tsx +5 -5
- package/{shadcn → src/components}/tooltip.tsx +12 -16
- package/src/gates/design.test.ts +188 -0
- package/src/gates/index.test.ts +11 -0
- package/src/gates/index.ts +165 -0
- package/src/gates/tokens.test.ts +215 -0
- package/src/harness.test.tsx +1 -1
- package/src/index.ts +63 -33
- package/src/theme/imports.test.ts +160 -0
- package/src/{statusTone.test.ts → theme/statusTone.test.ts} +5 -1
- package/src/{styles.css → theme/styles.css} +45 -31
- package/src/theme/tailwind.css +646 -0
- package/src/{tokens.test.ts → theme/tokens.test.ts} +30 -23
- package/src/{tokens.ts → theme/tokens.ts} +12 -12
- package/components.json +0 -13
- package/dist/lib/button.d.ts +0 -1
- package/dist/lib/button.js +0 -4
- package/dist/lib/cn.d.ts +0 -18
- package/dist/lib/cn.js +0 -21
- package/dist/lib/menu.d.ts +0 -1
- package/dist/lib/menu.js +0 -16
- package/dist/lib/progress.d.ts +0 -3
- package/dist/lib/progress.js +0 -6
- package/dist/lib/select.d.ts +0 -1
- package/dist/lib/select.js +0 -3
- package/dist/primitives/progress.js +0 -7
- package/gates/canonical.test.mjs +0 -71
- package/gates/extensions.test.mjs +0 -373
- package/gates/index.d.mts +0 -51
- package/gates/index.mjs +0 -514
- package/gates/tokens.test.mjs +0 -276
- package/shadcn/README.md +0 -13
- package/shadcn/badge.tsx +0 -49
- package/shadcn/button.tsx +0 -67
- package/shadcn/combobox.tsx +0 -299
- package/shadcn/dialog.tsx +0 -166
- package/shadcn/dropdown-menu.tsx +0 -267
- package/shadcn/field.tsx +0 -236
- package/shadcn/input-group.tsx +0 -154
- package/shadcn/progress.tsx +0 -29
- package/shadcn/sheet.tsx +0 -145
- package/shadcn/sonner.tsx +0 -49
- package/src/lib/button.ts +0 -4
- package/src/lib/cn.test.ts +0 -33
- package/src/lib/cn.ts +0 -23
- package/src/lib/menu.ts +0 -16
- package/src/lib/progress.ts +0 -6
- package/src/lib/select.ts +0 -4
- package/src/primitives/alert.tsx +0 -76
- package/src/primitives/card.tsx +0 -103
- package/src/primitives/input.tsx +0 -19
- package/src/primitives/label.tsx +0 -22
- package/src/primitives/select.tsx +0 -190
- package/src/primitives/separator.tsx +0 -28
- package/src/primitives/skeleton.tsx +0 -13
- package/src/primitives/sonner.tsx +0 -65
- package/src/primitives/table.tsx +0 -114
- package/src/primitives/tabs.tsx +0 -88
- package/src/primitives/textarea.tsx +0 -18
- package/src/primitives/tooltip.tsx +0 -57
- /package/dist/{primitives → components}/alert.d.ts +0 -0
- /package/dist/{primitives → components}/badge.d.ts +0 -0
- /package/dist/{primitives → components}/combobox.d.ts +0 -0
- /package/dist/{primitives → components}/dialog.d.ts +0 -0
- /package/dist/{primitives → components}/dropdown-menu.d.ts +0 -0
- /package/dist/{primitives → components}/field.d.ts +0 -0
- /package/dist/{primitives → components}/input-group.d.ts +0 -0
- /package/dist/{primitives → components}/input.d.ts +0 -0
- /package/dist/{primitives → components}/label.d.ts +0 -0
- /package/dist/{primitives → components}/progress.d.ts +0 -0
- /package/dist/{primitives → components}/separator.d.ts +0 -0
- /package/dist/{primitives → components}/sheet.d.ts +0 -0
- /package/dist/{primitives → components}/skeleton.d.ts +0 -0
- /package/dist/{primitives → components}/sonner.d.ts +0 -0
- /package/dist/{primitives → components}/tabs.d.ts +0 -0
- /package/dist/{primitives → components}/textarea.d.ts +0 -0
- /package/dist/{primitives → components}/tooltip.d.ts +0 -0
- /package/dist/{statusTone.d.ts → theme/statusTone.d.ts} +0 -0
- /package/dist/{statusTone.js → theme/statusTone.js} +0 -0
- /package/src/{statusTone.ts → theme/statusTone.ts} +0 -0
|
@@ -1,373 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { readFileSync } from "node:fs";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { spawnSync } from "node:child_process";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
|
-
import { test } from "node:test";
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
BUTTON_SIZES,
|
|
10
|
-
BUTTON_VARIANTS,
|
|
11
|
-
FOUNDATION_BADGE,
|
|
12
|
-
OFFICIAL_BADGE,
|
|
13
|
-
competingStatusPaletteIn,
|
|
14
|
-
legacyVocabularyIn,
|
|
15
|
-
menuSurfaceGapsIn,
|
|
16
|
-
statusPaletteIn,
|
|
17
|
-
statusTokenUtilitiesIn,
|
|
18
|
-
variantClasses,
|
|
19
|
-
variantKeys,
|
|
20
|
-
} from "./index.mjs";
|
|
21
|
-
|
|
22
|
-
const REPO = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
23
|
-
const read = (rel) => readFileSync(path.join(REPO, rel), "utf8");
|
|
24
|
-
|
|
25
|
-
const SOURCE = /\.(?:ts|tsx|css)$/;
|
|
26
|
-
const SHADCN_SNAPSHOT = /^shadcn\//;
|
|
27
|
-
|
|
28
|
-
/** Every tracked package file matching `SOURCE`. */
|
|
29
|
-
function packageSources({ includeSnapshots } = {}) {
|
|
30
|
-
const listed = spawnSync("git", ["ls-files", "-z", "src", "shadcn"], { cwd: REPO, encoding: "utf8" });
|
|
31
|
-
assert.equal(listed.status, 0, `git ls-files failed: ${listed.stderr}`);
|
|
32
|
-
return listed.stdout
|
|
33
|
-
.split("\0")
|
|
34
|
-
.filter((name) => SOURCE.test(name) && (includeSnapshots || !SHADCN_SNAPSHOT.test(name)));
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
test("variantKeys reads quoted and bare keys and ignores class text", () => {
|
|
38
|
-
assert.deepEqual(
|
|
39
|
-
variantKeys(`x({ variants: { variant: { default: "a: b", "icon-xs": "c" }, size: { sm: "d" } } })`),
|
|
40
|
-
["default", "icon-xs"],
|
|
41
|
-
);
|
|
42
|
-
assert.deepEqual(variantKeys(`variants: { variant: { a: "" }, size: { sm: "x", lg: "y" } }`, "size"), ["sm", "lg"]);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
const BUTTON = "src/primitives/button.tsx";
|
|
46
|
-
test("Button carries shadcn's variants and sizes, and nothing else", () => {
|
|
47
|
-
const src = read(BUTTON);
|
|
48
|
-
assert.deepEqual(variantKeys(src, "variant"), BUTTON_VARIANTS);
|
|
49
|
-
assert.deepEqual(variantKeys(src, "size"), BUTTON_SIZES);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
const BADGE = "src/primitives/badge.tsx";
|
|
53
|
-
|
|
54
|
-
test("Badge keeps shadcn's variants and adds exactly the four foundation status variants", () => {
|
|
55
|
-
const keys = variantKeys(read(BADGE), "variant");
|
|
56
|
-
for (const k of OFFICIAL_BADGE) assert.ok(keys.includes(k), `official Badge variant ${k} missing`);
|
|
57
|
-
assert.deepEqual(keys.filter((k) => !OFFICIAL_BADGE.includes(k)).sort(), [...FOUNDATION_BADGE].sort());
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
test("a status Badge paints a soft surface and readable ink, never a coloured stroke", () => {
|
|
61
|
-
const src = read(BADGE);
|
|
62
|
-
const stroke = /\bborder-(?:emerald|amber|sky|success|warning|destructive|primary)\b/;
|
|
63
|
-
for (const k of [...FOUNDATION_BADGE, "destructive"]) {
|
|
64
|
-
assert.doesNotMatch(variantClasses(src, k), stroke, `${k} adds a coloured border`);
|
|
65
|
-
}
|
|
66
|
-
assert.match(variantClasses(src, "success"), /\bbg-emerald-500\/10\b/);
|
|
67
|
-
assert.match(variantClasses(src, "success"), /\btext-emerald-700\b/);
|
|
68
|
-
assert.match(variantClasses(src, "warning"), /\bbg-amber-500\/10\b/);
|
|
69
|
-
assert.match(variantClasses(src, "warning"), /\btext-amber-700\b/);
|
|
70
|
-
assert.match(variantClasses(src, "info"), /\bbg-sky-500\/10\b/);
|
|
71
|
-
assert.match(variantClasses(src, "info"), /\btext-sky-700\b/);
|
|
72
|
-
assert.match(variantClasses(src, "quiet"), /\bbg-muted\b/);
|
|
73
|
-
assert.match(variantClasses(src, "quiet"), /\btext-muted-foreground\b/);
|
|
74
|
-
// Official destructive stays on the semantic token, never red-*.
|
|
75
|
-
assert.match(variantClasses(src, "destructive"), /\bbg-destructive\/10\b/);
|
|
76
|
-
assert.doesNotMatch(src, /\b(?:bg|text)-red-\d/);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
test("legacyVocabularyIn flags v1's shapes and stays silent on the shadcn contract", () => {
|
|
80
|
-
assert.deepEqual(legacyVocabularyIn("a.tsx", `<Button variant="primary">Go</Button>`), [
|
|
81
|
-
`a.tsx:1: <Button variant="primary">`,
|
|
82
|
-
]);
|
|
83
|
-
assert.deepEqual(legacyVocabularyIn("b.tsx", `<Button size="md">Go</Button>`), [`b.tsx:1: <Button size="md">`]);
|
|
84
|
-
assert.deepEqual(legacyVocabularyIn("c.tsx", `<Badge variant="neutral">Draft</Badge>`), [
|
|
85
|
-
`c.tsx:1: <Badge variant="neutral">`,
|
|
86
|
-
]);
|
|
87
|
-
assert.deepEqual(legacyVocabularyIn("d.tsx", `<Progress value={40} variant="thin" />`), [
|
|
88
|
-
`d.tsx:1: <Progress value={40} variant="thin" />`,
|
|
89
|
-
]);
|
|
90
|
-
assert.deepEqual(legacyVocabularyIn("e.tsx", `<SelectItem value="a" meta="12 items">A</SelectItem>`), [
|
|
91
|
-
`e.tsx:1: <SelectItem value="a" meta="12 items">`,
|
|
92
|
-
]);
|
|
93
|
-
assert.deepEqual(legacyVocabularyIn("f.tsx", `<Alert title="Heads up">…</Alert>`), [
|
|
94
|
-
`f.tsx:1: <Alert title="Heads up">`,
|
|
95
|
-
]);
|
|
96
|
-
// An outline Badge hand-rounded with a className utility is the `quiet`
|
|
97
|
-
// shape built by hand — forbidden regardless of attribute order.
|
|
98
|
-
assert.deepEqual(
|
|
99
|
-
legacyVocabularyIn("f2.tsx", `<Badge variant="outline" className="rounded-md">Chip</Badge>`),
|
|
100
|
-
[`f2.tsx:1: <Badge variant="outline" className="rounded-md">`],
|
|
101
|
-
);
|
|
102
|
-
assert.deepEqual(
|
|
103
|
-
legacyVocabularyIn("f3.tsx", `<Badge className="rounded-full" variant="outline">Chip</Badge>`),
|
|
104
|
-
[`f3.tsx:1: <Badge className="rounded-full" variant="outline">`],
|
|
105
|
-
);
|
|
106
|
-
// A multi-line tag is still one tag.
|
|
107
|
-
assert.deepEqual(
|
|
108
|
-
legacyVocabularyIn("g.tsx", `<Button\n variant="primary"\n onClick={go}\n>\n Go\n</Button>`),
|
|
109
|
-
[`g.tsx:1: <Button`],
|
|
110
|
-
);
|
|
111
|
-
// An arrow function's own `>` does not end the tag before the real attribute.
|
|
112
|
-
assert.deepEqual(
|
|
113
|
-
legacyVocabularyIn("g2.tsx", `<Button onClick={() => setOpen(true)} variant="primary">Go</Button>`),
|
|
114
|
-
[`g2.tsx:1: <Button onClick={() => setOpen(true)} variant="primary">`],
|
|
115
|
-
);
|
|
116
|
-
// Nor does a `>` comparison inside the expression.
|
|
117
|
-
assert.deepEqual(
|
|
118
|
-
legacyVocabularyIn("g3.tsx", `<Progress value={count > 0 ? count : 0} variant="thin" />`),
|
|
119
|
-
[`g3.tsx:1: <Progress value={count > 0 ? count : 0} variant="thin" />`],
|
|
120
|
-
);
|
|
121
|
-
// The same tricky arrow function with no forbidden attribute stays silent.
|
|
122
|
-
assert.deepEqual(legacyVocabularyIn("g4.tsx", `<Button onClick={() => go()}>ok</Button>`), []);
|
|
123
|
-
// Nor does a `>` quoted inside a string inside braces — the tag still
|
|
124
|
-
// extends to its real close, past the attribute that follows the string.
|
|
125
|
-
assert.deepEqual(
|
|
126
|
-
legacyVocabularyIn("g5.tsx", `<Alert data-note={"a > b"} title="Heads up">Body</Alert>`),
|
|
127
|
-
[`g5.tsx:1: <Alert data-note={"a > b"} title="Heads up">`],
|
|
128
|
-
);
|
|
129
|
-
assert.deepEqual(legacyVocabularyIn("h.tsx", `<FieldHint>Optional</FieldHint>`), [
|
|
130
|
-
`h.tsx:1: <FieldHint>Optional</FieldHint>`,
|
|
131
|
-
]);
|
|
132
|
-
assert.deepEqual(legacyVocabularyIn("i.tsx", `<TableEmpty>No rows</TableEmpty>`), [
|
|
133
|
-
`i.tsx:1: <TableEmpty>No rows</TableEmpty>`,
|
|
134
|
-
]);
|
|
135
|
-
assert.deepEqual(legacyVocabularyIn("j.ts", `import { Badge } from "../primitives/Badge.js";`), [
|
|
136
|
-
`j.ts:1: import { Badge } from "../primitives/Badge.js";`,
|
|
137
|
-
]);
|
|
138
|
-
assert.deepEqual(legacyVocabularyIn("k.tsx", `import { useTheme } from "next-themes";`), [
|
|
139
|
-
`k.tsx:1: import { useTheme } from "next-themes";`,
|
|
140
|
-
]);
|
|
141
|
-
|
|
142
|
-
// The shadcn contract itself must pass clean.
|
|
143
|
-
assert.deepEqual(legacyVocabularyIn("l.tsx", `<Button variant="outline" size="sm">Go</Button>`), []);
|
|
144
|
-
assert.deepEqual(legacyVocabularyIn("m.tsx", `<AlertTitle title="not this one">x</AlertTitle>`), []);
|
|
145
|
-
assert.deepEqual(legacyVocabularyIn("n.tsx", `<Badge variant="secondary">Draft</Badge>`), []);
|
|
146
|
-
// The `quiet` variant is the shape itself, not the hand-rolled shape.
|
|
147
|
-
assert.deepEqual(legacyVocabularyIn("n2.tsx", `<Badge variant="quiet">Chip</Badge>`), []);
|
|
148
|
-
// Only one of the two attributes is not yet the forbidden shape.
|
|
149
|
-
assert.deepEqual(legacyVocabularyIn("n3.tsx", `<Badge variant="outline">Chip</Badge>`), []);
|
|
150
|
-
assert.deepEqual(legacyVocabularyIn("n4.tsx", `<Badge className="rounded-md">Chip</Badge>`), []);
|
|
151
|
-
assert.deepEqual(legacyVocabularyIn("o.ts", `import { Badge } from "../primitives/badge.js";`), []);
|
|
152
|
-
// A comment recalling the retired shape states history, not a usage.
|
|
153
|
-
assert.deepEqual(legacyVocabularyIn("p.tsx", ` // <Button variant="primary">Go</Button>`), []);
|
|
154
|
-
assert.deepEqual(legacyVocabularyIn("q.tsx", `// reads next-themes for the mounted flag`), []);
|
|
155
|
-
|
|
156
|
-
// The same vocabulary as a test reads it back, the wrapped call included —
|
|
157
|
-
// the shape TAG_ATTRIBUTE_RULES cannot see.
|
|
158
|
-
assert.deepEqual(
|
|
159
|
-
legacyVocabularyIn("r.ts", `await expect(go).toHaveAttribute(\n "data-variant",\n "primary",\n);`),
|
|
160
|
-
[`r.ts:2: "data-variant", "primary"`],
|
|
161
|
-
);
|
|
162
|
-
assert.deepEqual(legacyVocabularyIn("r2.ts", `page.locator('[data-variant="primary"]')`), [
|
|
163
|
-
`r2.ts:1: [data-variant="primary"]`,
|
|
164
|
-
]);
|
|
165
|
-
assert.deepEqual(
|
|
166
|
-
legacyVocabularyIn("r3.tsx", `expect(el.getAttribute("data-variant")).toBe("accent");`),
|
|
167
|
-
[`r3.tsx:1: getAttribute("data-variant")).toBe("accent"`],
|
|
168
|
-
);
|
|
169
|
-
assert.deepEqual(legacyVocabularyIn("r4.tsx", `expect(el.dataset.size).toBe("md");`), [
|
|
170
|
-
`r4.tsx:1: dataset.size).toBe("md"`,
|
|
171
|
-
]);
|
|
172
|
-
// A variant that still exists is a question about the call site, not the vocabulary.
|
|
173
|
-
assert.deepEqual(legacyVocabularyIn("s.ts", `toHaveAttribute("data-variant", "outline")`), []);
|
|
174
|
-
assert.deepEqual(
|
|
175
|
-
legacyVocabularyIn("s2.tsx", `expect(b.getAttribute("data-variant")).toBe("success");`),
|
|
176
|
-
[],
|
|
177
|
-
);
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
test("no package source reaches for a name the extension contract retired", () => {
|
|
181
|
-
const tracked = packageSources();
|
|
182
|
-
assert.ok(tracked.length > 0, "the scan found no package sources, so it proves nothing");
|
|
183
|
-
|
|
184
|
-
const offenders = tracked.flatMap((file) => legacyVocabularyIn(file, readFileSync(path.join(REPO, file), "utf8")));
|
|
185
|
-
assert.deepEqual(
|
|
186
|
-
offenders,
|
|
187
|
-
[],
|
|
188
|
-
"a source still reaches for a name the extension contract retired:\n" + offenders.join("\n"),
|
|
189
|
-
);
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
test("statusPaletteIn finds the emerald/amber/sky family, and not a token or a comment", () => {
|
|
193
|
-
assert.deepEqual(statusPaletteIn("a.tsx", ` className="bg-emerald-500/10"`), [
|
|
194
|
-
`a.tsx:1: className="bg-emerald-500/10"`,
|
|
195
|
-
]);
|
|
196
|
-
assert.deepEqual(statusPaletteIn("b.tsx", ` className="border-amber-400 dark:border-amber-300"`), [
|
|
197
|
-
`b.tsx:1: className="border-amber-400 dark:border-amber-300"`,
|
|
198
|
-
]);
|
|
199
|
-
assert.deepEqual(statusPaletteIn("c.tsx", ` className="ring-sky-500"`), [`c.tsx:1: className="ring-sky-500"`]);
|
|
200
|
-
assert.deepEqual(statusPaletteIn("d.tsx", ` className="shadow-emerald-500/20"`), [
|
|
201
|
-
`d.tsx:1: className="shadow-emerald-500/20"`,
|
|
202
|
-
]);
|
|
203
|
-
// A token, not the palette.
|
|
204
|
-
assert.deepEqual(statusPaletteIn("e.tsx", ` className="bg-primary text-primary-foreground"`), []);
|
|
205
|
-
// The family name without a shade is not yet a colour.
|
|
206
|
-
assert.deepEqual(statusPaletteIn("f.tsx", ` className="bg-emerald"`), []);
|
|
207
|
-
// A comment recalling the palette states history, not a usage.
|
|
208
|
-
assert.deepEqual(statusPaletteIn("g.tsx", ` // never bg-emerald-500 outside statusTone`), []);
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
test("competingStatusPaletteIn finds a rival colour family, and not the status palette itself", () => {
|
|
212
|
-
assert.deepEqual(competingStatusPaletteIn("a.tsx", ` className="bg-green-500"`), [
|
|
213
|
-
`a.tsx:1: className="bg-green-500"`,
|
|
214
|
-
]);
|
|
215
|
-
assert.deepEqual(competingStatusPaletteIn("b.tsx", ` className="text-yellow-700"`), [
|
|
216
|
-
`b.tsx:1: className="text-yellow-700"`,
|
|
217
|
-
]);
|
|
218
|
-
assert.deepEqual(competingStatusPaletteIn("c.tsx", ` className="border-blue-400"`), [
|
|
219
|
-
`c.tsx:1: className="border-blue-400"`,
|
|
220
|
-
]);
|
|
221
|
-
assert.deepEqual(competingStatusPaletteIn("d.tsx", ` className="bg-emerald-500"`), []);
|
|
222
|
-
assert.deepEqual(competingStatusPaletteIn("e.tsx", ` // never bg-red-500 for a destructive state`), []);
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
test("the status palette lives in exactly Badge and statusTone, nowhere else", () => {
|
|
226
|
-
const ALLOWED_PALETTE_FILES = [
|
|
227
|
-
"src/primitives/badge.tsx",
|
|
228
|
-
"src/statusTone.ts",
|
|
229
|
-
"src/statusTone.test.ts",
|
|
230
|
-
];
|
|
231
|
-
const tracked = packageSources({ includeSnapshots: true });
|
|
232
|
-
assert.ok(tracked.length > 0, "the scan found no package sources, so it proves nothing");
|
|
233
|
-
|
|
234
|
-
const offenders = tracked
|
|
235
|
-
.filter((file) => !ALLOWED_PALETTE_FILES.includes(file))
|
|
236
|
-
.flatMap((file) => statusPaletteIn(file, readFileSync(path.join(REPO, file), "utf8")));
|
|
237
|
-
assert.deepEqual(
|
|
238
|
-
offenders,
|
|
239
|
-
[],
|
|
240
|
-
"the status palette has exactly one home outside Badge and statusTone — read the tone from " +
|
|
241
|
-
`src/statusTone.ts instead:\n${offenders.join("\n")}`,
|
|
242
|
-
);
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
test("no competing colour family stands in for the status palette anywhere in the package", () => {
|
|
246
|
-
const tracked = packageSources({ includeSnapshots: true });
|
|
247
|
-
assert.ok(tracked.length > 0, "the scan found no package sources, so it proves nothing");
|
|
248
|
-
|
|
249
|
-
const offenders = tracked.flatMap((file) =>
|
|
250
|
-
competingStatusPaletteIn(file, readFileSync(path.join(REPO, file), "utf8")),
|
|
251
|
-
);
|
|
252
|
-
assert.deepEqual(
|
|
253
|
-
offenders,
|
|
254
|
-
[],
|
|
255
|
-
`a competing colour family stands in for the status palette:\n${offenders.join("\n")}`,
|
|
256
|
-
);
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
test("statusTokenUtilitiesIn finds the retired success/warning utility, and not the emerald/amber tokens that replaced it", () => {
|
|
260
|
-
assert.deepEqual(statusTokenUtilitiesIn("a.tsx", ` className="bg-success"`), [`a.tsx:1: className="bg-success"`]);
|
|
261
|
-
assert.deepEqual(statusTokenUtilitiesIn("b.tsx", ` className="text-warning-foreground"`), [
|
|
262
|
-
`b.tsx:1: className="text-warning-foreground"`,
|
|
263
|
-
]);
|
|
264
|
-
assert.deepEqual(statusTokenUtilitiesIn("c.tsx", ` className="border-success"`), [
|
|
265
|
-
`c.tsx:1: className="border-success"`,
|
|
266
|
-
]);
|
|
267
|
-
assert.deepEqual(statusTokenUtilitiesIn("d.tsx", ` className="ring-warning"`), [
|
|
268
|
-
`d.tsx:1: className="ring-warning"`,
|
|
269
|
-
]);
|
|
270
|
-
assert.deepEqual(statusTokenUtilitiesIn("e.tsx", ` className="bg-emerald-500 text-emerald-700"`), []);
|
|
271
|
-
assert.deepEqual(statusTokenUtilitiesIn("f.tsx", ` className="bg-destructive"`), []);
|
|
272
|
-
assert.deepEqual(statusTokenUtilitiesIn("g.tsx", ` // bg-success no longer exists`), []);
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
test("no package source reaches for the retired success/warning token utility", () => {
|
|
276
|
-
const tracked = packageSources({ includeSnapshots: true });
|
|
277
|
-
assert.ok(tracked.length > 0, "the scan found no package sources, so it proves nothing");
|
|
278
|
-
|
|
279
|
-
const offenders = tracked.flatMap((file) =>
|
|
280
|
-
statusTokenUtilitiesIn(file, readFileSync(path.join(REPO, file), "utf8")),
|
|
281
|
-
);
|
|
282
|
-
assert.deepEqual(
|
|
283
|
-
offenders,
|
|
284
|
-
[],
|
|
285
|
-
`a source reaches for the retired success/warning token utility:\n${offenders.join("\n")}`,
|
|
286
|
-
);
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* The public surface `index.ts` promises: every canonical primitive
|
|
291
|
-
* re-exported, the retired pattern-layer `Combobox` gone now that it is a
|
|
292
|
-
* primitive, and no `*Variants` beyond the three shadcn's own `cva` calls
|
|
293
|
-
* produce.
|
|
294
|
-
*/
|
|
295
|
-
const INDEX_PATH = "src/index.ts";
|
|
296
|
-
const PUBLIC_PRIMITIVES = [
|
|
297
|
-
"badge",
|
|
298
|
-
"button",
|
|
299
|
-
"alert",
|
|
300
|
-
"field",
|
|
301
|
-
"dialog",
|
|
302
|
-
"sheet",
|
|
303
|
-
"combobox",
|
|
304
|
-
"input-group",
|
|
305
|
-
"select",
|
|
306
|
-
"dropdown-menu",
|
|
307
|
-
"tooltip",
|
|
308
|
-
"progress",
|
|
309
|
-
"skeleton",
|
|
310
|
-
"sonner",
|
|
311
|
-
"table",
|
|
312
|
-
"tabs",
|
|
313
|
-
"card",
|
|
314
|
-
"input",
|
|
315
|
-
"textarea",
|
|
316
|
-
"label",
|
|
317
|
-
];
|
|
318
|
-
|
|
319
|
-
test("index.ts exports every canonical primitive, drops the retired pattern Combobox, and adds no *Variants beyond shadcn's own three", () => {
|
|
320
|
-
const source = read(INDEX_PATH);
|
|
321
|
-
|
|
322
|
-
for (const name of PUBLIC_PRIMITIVES) {
|
|
323
|
-
const exported = new RegExp(String.raw`export\s*\{[^;]*\}\s*from\s*"\./primitives/${name}\.js"`).test(source);
|
|
324
|
-
assert.ok(exported, `${INDEX_PATH} does not export from ./primitives/${name}.js`);
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
assert.ok(
|
|
328
|
-
!source.includes('from "./patterns/Combobox.js"'),
|
|
329
|
-
`${INDEX_PATH} must not export the retired ./patterns/Combobox.js — Combobox is a primitive now`,
|
|
330
|
-
);
|
|
331
|
-
|
|
332
|
-
const variantsExports = [...new Set([...source.matchAll(/\b[a-zA-Z]*Variants\b/g)].map((m) => m[0]))].sort();
|
|
333
|
-
assert.deepEqual(
|
|
334
|
-
variantsExports,
|
|
335
|
-
["badgeVariants", "buttonVariants", "tabsListVariants"],
|
|
336
|
-
`${INDEX_PATH} exports a *Variants beyond shadcn's own three:\n${variantsExports.join(", ")}`,
|
|
337
|
-
);
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
test("menuSurfaceGapsIn flags a DropdownMenuContent missing menuSurface, and stays silent when it carries it", () => {
|
|
341
|
-
assert.deepEqual(
|
|
342
|
-
menuSurfaceGapsIn("a.tsx", `<DropdownMenuContent align="end">x</DropdownMenuContent>`),
|
|
343
|
-
[`a.tsx:1: <DropdownMenuContent align="end">`],
|
|
344
|
-
);
|
|
345
|
-
assert.deepEqual(
|
|
346
|
-
menuSurfaceGapsIn(
|
|
347
|
-
"b.tsx",
|
|
348
|
-
`<DropdownMenuContent align="end" className={menuSurface}>x</DropdownMenuContent>`,
|
|
349
|
-
),
|
|
350
|
-
[],
|
|
351
|
-
);
|
|
352
|
-
assert.deepEqual(
|
|
353
|
-
menuSurfaceGapsIn(
|
|
354
|
-
"c.tsx",
|
|
355
|
-
`<DropdownMenuContent className={cn(menuSurface, "w-64")}>x</DropdownMenuContent>`,
|
|
356
|
-
),
|
|
357
|
-
[],
|
|
358
|
-
);
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
test("every DropdownMenuContent call site outside the shadcn snapshot carries menuSurface", () => {
|
|
362
|
-
const tracked = packageSources();
|
|
363
|
-
assert.ok(tracked.length > 0, "the scan found no package sources, so it proves nothing");
|
|
364
|
-
|
|
365
|
-
const offenders = tracked.flatMap((file) =>
|
|
366
|
-
menuSurfaceGapsIn(file, readFileSync(path.join(REPO, file), "utf8")),
|
|
367
|
-
);
|
|
368
|
-
assert.deepEqual(
|
|
369
|
-
offenders,
|
|
370
|
-
[],
|
|
371
|
-
`a DropdownMenuContent call site is missing lib/menu.ts's menuSurface:\n${offenders.join("\n")}`,
|
|
372
|
-
);
|
|
373
|
-
});
|
package/gates/index.d.mts
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Hand-written declarations for `@robomous/ui-core/gates` — the module is
|
|
3
|
-
* plain ESM JavaScript (it runs under `node --test` with no build step), so
|
|
4
|
-
* its types are maintained here beside it.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
// ---- canonical ----
|
|
8
|
-
export function additiveOnly(
|
|
9
|
-
snapshot: string,
|
|
10
|
-
actual: string,
|
|
11
|
-
): { ok: true } | { ok: false; missing: string };
|
|
12
|
-
export const FRAMEWORK_ADAPTERS: string[];
|
|
13
|
-
export const ADAPTER_REMOVED_LINES: string[];
|
|
14
|
-
export function withoutLines(text: string, removed: readonly string[]): string;
|
|
15
|
-
export function checkAdapter(
|
|
16
|
-
file: string,
|
|
17
|
-
actualText: string,
|
|
18
|
-
): { ok: true; isAdapter: boolean } | { ok: false; reason: string };
|
|
19
|
-
|
|
20
|
-
// ---- rosters and vocabulary ----
|
|
21
|
-
export function variantKeys(source: string, block?: string): string[];
|
|
22
|
-
export function variantClasses(source: string, key: string): string;
|
|
23
|
-
export const OFFICIAL_BADGE: string[];
|
|
24
|
-
export const FOUNDATION_BADGE: string[];
|
|
25
|
-
export const BUTTON_VARIANTS: string[];
|
|
26
|
-
export const BUTTON_SIZES: string[];
|
|
27
|
-
export function openTagsIn(text: string, tagName: string): { at: number; tag: string }[];
|
|
28
|
-
export function legacyVocabularyIn(file: string, text: string): string[];
|
|
29
|
-
export function statusPaletteIn(file: string, text: string): string[];
|
|
30
|
-
export function competingStatusPaletteIn(file: string, text: string): string[];
|
|
31
|
-
export function statusTokenUtilitiesIn(file: string, text: string): string[];
|
|
32
|
-
export function menuSurfaceGapsIn(file: string, text: string): string[];
|
|
33
|
-
|
|
34
|
-
// ---- colour discipline ----
|
|
35
|
-
export function colouredClassesIn(file: string, text: string): string[];
|
|
36
|
-
export function brandUsagesIn(
|
|
37
|
-
file: string,
|
|
38
|
-
text: string,
|
|
39
|
-
): { file: string; at: number; text: string }[];
|
|
40
|
-
export function retiredDeclarationsIn(text: string): string[];
|
|
41
|
-
|
|
42
|
-
// ---- stylesheet parsing ----
|
|
43
|
-
export function normalize(value: string): string;
|
|
44
|
-
export function blockBody(css: string, header: string): string;
|
|
45
|
-
export function rawDeclarations(block: string): Map<string, string>;
|
|
46
|
-
export function declarations(block: string): Map<string, string>;
|
|
47
|
-
export const SEMANTIC_NAMES: string[];
|
|
48
|
-
|
|
49
|
-
// ---- foundation facts ----
|
|
50
|
-
export function snapshotsDir(): string;
|
|
51
|
-
export function foundationTokenNames(): string[];
|