@kinetixui/cli 0.23.2 → 0.24.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 +89 -0
- package/dist/index.js +410 -251
- package/package.json +9 -5
package/README.md
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# @kinetixui/cli
|
|
2
|
+
|
|
3
|
+
Adds KinetixUI components and tokens to your project by writing source into your repo — the
|
|
4
|
+
copy-the-code model, not a dependency you import from.
|
|
5
|
+
|
|
6
|
+
**Beta.** Ships alongside `@kinetixui/ui` and `@kinetixui/tokens` on one version line.
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npx @kinetixui/cli init
|
|
10
|
+
npx @kinetixui/cli add button card dialog
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
No install step is needed; `npx` is the intended way to run it. The binary is `kinetixui` if you do
|
|
14
|
+
install it.
|
|
15
|
+
|
|
16
|
+
## What it is
|
|
17
|
+
|
|
18
|
+
A registry client and a code writer. `init` sets up `kinetixui.json` and writes the token contract into
|
|
19
|
+
your global stylesheet. `add` resolves a component's registry dependencies, installs the npm packages it
|
|
20
|
+
needs with *your* package manager, and writes the component source into your tree. From that point the
|
|
21
|
+
code is yours to edit — nothing rewrites it unless you ask.
|
|
22
|
+
|
|
23
|
+
## Commands
|
|
24
|
+
|
|
25
|
+
| Command | Does |
|
|
26
|
+
| --- | --- |
|
|
27
|
+
| `init` | Create `kinetixui.json`, write `:root` / `.dark` token blocks into your stylesheet |
|
|
28
|
+
| `add <name…>` | Resolve and write component source, plus its registry and npm dependencies |
|
|
29
|
+
| `list` | List what the registry offers |
|
|
30
|
+
| `inspect <name>` | Show a component's files, dependencies and registry metadata |
|
|
31
|
+
| `parity <name>` | Show which platforms carry a component, and why one does not |
|
|
32
|
+
| `lint` | Check your usage against the token contract |
|
|
33
|
+
| `doctor` | Check that your project is set up correctly |
|
|
34
|
+
| `theme` | Work with themes |
|
|
35
|
+
| `preset` | Read a Kinetix Create preset code (`KX1_…`) and emit CSS or native token files |
|
|
36
|
+
| `create` | Scaffolding helpers |
|
|
37
|
+
|
|
38
|
+
Run any command with `--help` for its flags. Full reference: <https://kinetixui.com/docs/cli>.
|
|
39
|
+
|
|
40
|
+
## Where components come from
|
|
41
|
+
|
|
42
|
+
`add` fetches from a registry over HTTPS. The default is `https://kinetixui.com/r`, which serves a
|
|
43
|
+
shadcn-compatible JSON descriptor per component. You can point it elsewhere:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npx @kinetixui/cli add button --registry https://your-mirror.example.com/r
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`-r, --registry <url>` is accepted by `init`, `add`, `list` and `inspect`. To set it once for a project
|
|
50
|
+
instead of on every command, put it in `kinetixui.json`:
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
{
|
|
54
|
+
"registry": "https://your-mirror.example.com/r"
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
The flag wins when both are present. The URL is validated before use, and component names are validated
|
|
59
|
+
against a strict character set before they are used to build any path — a registry response cannot direct
|
|
60
|
+
the CLI to write outside your project.
|
|
61
|
+
|
|
62
|
+
**One consequence to be aware of:** `add` needs that origin to be reachable, and there is no offline
|
|
63
|
+
mode. If your builds cannot depend on `kinetixui.com`, mirror the registry JSON and set `registry` in
|
|
64
|
+
`kinetixui.json`. When the registry is unreachable the CLI says so and names both ways to point
|
|
65
|
+
elsewhere, rather than failing with a bare network error.
|
|
66
|
+
|
|
67
|
+
## Requirements
|
|
68
|
+
|
|
69
|
+
- **Node 18 or newer** (declared in `engines`)
|
|
70
|
+
- A JavaScript package manager on `PATH` — npm, pnpm, yarn or bun; the CLI detects which you use and
|
|
71
|
+
calls it rather than assuming npm
|
|
72
|
+
- A project with a global stylesheet for `init` to write into
|
|
73
|
+
|
|
74
|
+
## What it does not do
|
|
75
|
+
|
|
76
|
+
It does not publish, does not phone home, and does not write outside the directory you run it in. It
|
|
77
|
+
installs dependencies by invoking your package manager, so anything it installs is visible in your
|
|
78
|
+
lockfile like any other dependency.
|
|
79
|
+
|
|
80
|
+
## Links
|
|
81
|
+
|
|
82
|
+
- CLI reference — <https://kinetixui.com/docs/cli>
|
|
83
|
+
- Installation — <https://kinetixui.com/docs/installation>
|
|
84
|
+
- Components — <https://kinetixui.com/components>
|
|
85
|
+
- Source — <https://github.com/zedalleys/kinetixui>
|
|
86
|
+
|
|
87
|
+
## License
|
|
88
|
+
|
|
89
|
+
MIT
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// src/
|
|
3
|
+
// src/program.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
import pc10 from "picocolors";
|
|
6
6
|
|
|
@@ -39,6 +39,11 @@ async function readConfig(cwd) {
|
|
|
39
39
|
async function writeConfig(cwd, config) {
|
|
40
40
|
await writeFile(path.join(cwd, CONFIG_FILE), JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
41
41
|
}
|
|
42
|
+
async function resolveRegistry(cwd, fromFlag) {
|
|
43
|
+
if (fromFlag && fromFlag !== DEFAULT_REGISTRY) return fromFlag;
|
|
44
|
+
const config = await readConfig(cwd);
|
|
45
|
+
return config?.registry?.trim() || DEFAULT_REGISTRY;
|
|
46
|
+
}
|
|
42
47
|
function resolveAliasDir(cwd, config, alias) {
|
|
43
48
|
const raw = config.aliases[alias].replace(/^@\//, "");
|
|
44
49
|
return path.join(cwd, config.srcDir ? "src" : "", raw);
|
|
@@ -147,13 +152,49 @@ function runInstall(cwd, pm, packages) {
|
|
|
147
152
|
}
|
|
148
153
|
|
|
149
154
|
// src/lib/registry.ts
|
|
155
|
+
async function request(url) {
|
|
156
|
+
try {
|
|
157
|
+
return await fetch(url);
|
|
158
|
+
} catch (cause) {
|
|
159
|
+
const origin = safeOrigin(url);
|
|
160
|
+
throw new Error(
|
|
161
|
+
`Could not reach the registry at ${origin} (${cause.message}). Check the network, or point somewhere else with --registry <url> or "registry" in kinetixui.json.`,
|
|
162
|
+
{ cause }
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function safeOrigin(url) {
|
|
167
|
+
try {
|
|
168
|
+
return new URL(url).origin;
|
|
169
|
+
} catch {
|
|
170
|
+
return url;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
async function parse(res, url, describe, valid) {
|
|
174
|
+
let data;
|
|
175
|
+
try {
|
|
176
|
+
data = await res.json();
|
|
177
|
+
} catch (cause) {
|
|
178
|
+
throw new Error(
|
|
179
|
+
`The registry at ${safeOrigin(url)} did not return JSON for ${describe} (${cause.message}). If you are behind a proxy, it may be answering instead of the registry.`,
|
|
180
|
+
{ cause }
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
if (!valid(data)) {
|
|
184
|
+
throw new Error(`The registry at ${safeOrigin(url)} returned JSON that is not a valid ${describe} (${url}).`);
|
|
185
|
+
}
|
|
186
|
+
return data;
|
|
187
|
+
}
|
|
188
|
+
var isIndex = (v) => typeof v === "object" && v !== null && Array.isArray(v.items);
|
|
189
|
+
var isItem = (v) => typeof v === "object" && v !== null && typeof v.name === "string" && Array.isArray(v.files);
|
|
190
|
+
var isSpec = (v) => typeof v === "object" && v !== null && typeof v.name === "string";
|
|
150
191
|
async function fetchRegistryIndex(registryUrl) {
|
|
151
192
|
const url = `${assertRegistryUrl(registryUrl).replace(/\/$/, "")}/registry.json`;
|
|
152
|
-
const res = await
|
|
193
|
+
const res = await request(url);
|
|
153
194
|
if (!res.ok) {
|
|
154
195
|
throw new Error(`Could not load the registry index (${res.status} at ${url}).`);
|
|
155
196
|
}
|
|
156
|
-
const data = await res
|
|
197
|
+
const data = await parse(res, url, "registry index", isIndex);
|
|
157
198
|
return data.items;
|
|
158
199
|
}
|
|
159
200
|
async function fetchComponentNames(registryUrl) {
|
|
@@ -163,22 +204,22 @@ async function fetchComponentNames(registryUrl) {
|
|
|
163
204
|
async function fetchRegistryItem(registryUrl, name) {
|
|
164
205
|
const base = assertRegistryUrl(registryUrl).replace(/\/$/, "");
|
|
165
206
|
const url = `${base}/${assertComponentName(name)}.json`;
|
|
166
|
-
const res = await
|
|
207
|
+
const res = await request(url);
|
|
167
208
|
if (!res.ok) {
|
|
168
209
|
throw new Error(`Could not find "${name}" in the registry (${res.status} at ${url}).`);
|
|
169
210
|
}
|
|
170
|
-
return await res
|
|
211
|
+
return await parse(res, url, `registry item for "${name}"`, isItem);
|
|
171
212
|
}
|
|
172
213
|
async function fetchComponentSpec(registryUrl, name) {
|
|
173
214
|
const base = assertRegistryUrl(registryUrl).replace(/\/$/, "");
|
|
174
215
|
const specsBase = base.endsWith("/r") ? `${base.slice(0, -2)}/specs` : `${base}/specs`;
|
|
175
216
|
const url = `${specsBase}/${assertComponentName(name)}.json`;
|
|
176
|
-
const res = await
|
|
217
|
+
const res = await request(url);
|
|
177
218
|
if (res.status === 404) return null;
|
|
178
219
|
if (!res.ok) {
|
|
179
220
|
throw new Error(`Could not load the component spec for "${name}" (${res.status} at ${url}).`);
|
|
180
221
|
}
|
|
181
|
-
return await res
|
|
222
|
+
return await parse(res, url, `component spec for "${name}"`, isSpec);
|
|
182
223
|
}
|
|
183
224
|
async function resolveTree(registryUrl, names) {
|
|
184
225
|
const resolved = /* @__PURE__ */ new Map();
|
|
@@ -199,17 +240,18 @@ async function resolveTree(registryUrl, names) {
|
|
|
199
240
|
async function add(names, options) {
|
|
200
241
|
const cwd = process.cwd();
|
|
201
242
|
const config = await readConfig(cwd) ?? DEFAULT_CONFIG;
|
|
243
|
+
const registry = await resolveRegistry(cwd, options.registry);
|
|
202
244
|
if (!existsSync4(path4.join(cwd, CONFIG_FILE))) {
|
|
203
245
|
console.log(pc.yellow("!"), `No ${CONFIG_FILE} found \u2014 using defaults. Run "kinetixui init" first to set your own aliases.`);
|
|
204
246
|
}
|
|
205
247
|
if (options.all) {
|
|
206
248
|
console.log("Fetching the full component list\u2026");
|
|
207
|
-
names = await fetchComponentNames(
|
|
249
|
+
names = await fetchComponentNames(registry);
|
|
208
250
|
} else if (names.length === 0) {
|
|
209
251
|
throw new Error('Pass component names to add (e.g. "kinetixui add button card"), or use --all for every component.');
|
|
210
252
|
}
|
|
211
253
|
console.log(options.all ? `Resolving all ${names.length} components\u2026` : `Resolving ${names.join(", ")}\u2026`);
|
|
212
|
-
const items = await resolveTree(
|
|
254
|
+
const items = await resolveTree(registry, names);
|
|
213
255
|
const { deps, written, skipped } = await writeItems(cwd, config, items.values(), options.overwrite);
|
|
214
256
|
for (const file of written) console.log(pc.green("\u2714"), `Added ${file}`);
|
|
215
257
|
for (const file of skipped) console.log(pc.yellow("skip"), `${file} already exists (pass --overwrite to replace it)`);
|
|
@@ -270,13 +312,15 @@ async function doctor() {
|
|
|
270
312
|
levels.push(report("warn", `Tailwind CSS file not found at ${path5.relative(cwd, cssPath2)} \u2014 the token contract has nowhere to land.`));
|
|
271
313
|
}
|
|
272
314
|
}
|
|
315
|
+
const registry = await resolveRegistry(cwd);
|
|
316
|
+
const origin = registry === DEFAULT_REGISTRY ? registry : `${registry} (from ${CONFIG_FILE})`;
|
|
273
317
|
let registryNames = null;
|
|
274
318
|
try {
|
|
275
|
-
const items = await fetchRegistryIndex(
|
|
319
|
+
const items = await fetchRegistryIndex(registry);
|
|
276
320
|
registryNames = new Set(items.filter((item) => item.type === "registry:ui").map((item) => item.name));
|
|
277
|
-
levels.push(report("pass", `Registry reachable (${
|
|
321
|
+
levels.push(report("pass", `Registry reachable (${origin}) \u2014 ${registryNames.size} component(s) listed.`));
|
|
278
322
|
} catch (err) {
|
|
279
|
-
levels.push(report("fail", `Registry unreachable at ${
|
|
323
|
+
levels.push(report("fail", `Registry unreachable at ${origin}: ${err.message}`));
|
|
280
324
|
}
|
|
281
325
|
if (config && registryNames) {
|
|
282
326
|
const uiDir = resolveAliasDir(cwd, config, "ui");
|
|
@@ -374,7 +418,7 @@ async function init(options) {
|
|
|
374
418
|
};
|
|
375
419
|
await writeConfig(cwd, config);
|
|
376
420
|
console.log(pc3.green("\u2714"), `Created ${CONFIG_FILE}`);
|
|
377
|
-
const items = await resolveTree(options.registry, ["tokens"]);
|
|
421
|
+
const items = await resolveTree(await resolveRegistry(cwd, options.registry), ["tokens"]);
|
|
378
422
|
const { written, skipped } = await writeItems(cwd, config, items.values(), false);
|
|
379
423
|
for (const file of written) console.log(pc3.green("\u2714"), `Wrote ${file}`);
|
|
380
424
|
for (const file of skipped) {
|
|
@@ -390,7 +434,8 @@ import { existsSync as existsSync7 } from "fs";
|
|
|
390
434
|
import path7 from "path";
|
|
391
435
|
import pc4 from "picocolors";
|
|
392
436
|
async function inspect(name, options) {
|
|
393
|
-
const
|
|
437
|
+
const registry = await resolveRegistry(process.cwd(), options.registry);
|
|
438
|
+
const item = await fetchRegistryItem(registry, name);
|
|
394
439
|
console.log(`${pc4.bold(item.title ?? item.name)} ${pc4.dim(`(${item.name})`)}`);
|
|
395
440
|
if (item.description) console.log(item.description);
|
|
396
441
|
console.log();
|
|
@@ -420,7 +465,7 @@ async function inspect(name, options) {
|
|
|
420
465
|
for (const file of item.files) {
|
|
421
466
|
console.log(` ${file.path} ${pc4.dim("\u2192")} ${file.target}`);
|
|
422
467
|
}
|
|
423
|
-
const spec = await fetchComponentSpec(
|
|
468
|
+
const spec = await fetchComponentSpec(registry, name);
|
|
424
469
|
if (spec) {
|
|
425
470
|
if (spec.since || spec.status) {
|
|
426
471
|
console.log();
|
|
@@ -617,7 +662,7 @@ async function lint(targetArg, options) {
|
|
|
617
662
|
// src/commands/list.ts
|
|
618
663
|
import pc6 from "picocolors";
|
|
619
664
|
async function list(options) {
|
|
620
|
-
const items = await fetchRegistryIndex(options.registry);
|
|
665
|
+
const items = await fetchRegistryIndex(await resolveRegistry(process.cwd(), options.registry));
|
|
621
666
|
const components = items.filter((item) => item.type === "registry:ui").sort((a, b) => a.name.localeCompare(b.name));
|
|
622
667
|
console.log(`${components.length} component(s):`);
|
|
623
668
|
console.log();
|
|
@@ -1236,6 +1281,7 @@ var tokens = {
|
|
|
1236
1281
|
"secondary-foreground": "#465245",
|
|
1237
1282
|
"muted": "#f6f6f6",
|
|
1238
1283
|
"muted-foreground": "#6d6d6d",
|
|
1284
|
+
"surface-grouped": "#f6f6f6",
|
|
1239
1285
|
"accent": "#f0f7ff",
|
|
1240
1286
|
"accent-foreground": "#1d4ed8",
|
|
1241
1287
|
"destructive": "#c60a0a",
|
|
@@ -1258,7 +1304,8 @@ var tokens = {
|
|
|
1258
1304
|
"on-success-container": "#465245",
|
|
1259
1305
|
"warning": "#f97907",
|
|
1260
1306
|
"info-container": "#f0f7ff",
|
|
1261
|
-
"on-info-container": "#395a70"
|
|
1307
|
+
"on-info-container": "#395a70",
|
|
1308
|
+
"muted-on-container": "#616161"
|
|
1262
1309
|
},
|
|
1263
1310
|
"chart-1": "#1b3c53",
|
|
1264
1311
|
"chart-2": "#748873",
|
|
@@ -1280,6 +1327,8 @@ var tokens = {
|
|
|
1280
1327
|
"tertiary-foreground": "#f0f7ff",
|
|
1281
1328
|
"info": "#57778d",
|
|
1282
1329
|
"info-foreground": "#f0f7ff",
|
|
1330
|
+
"scrim": "rgba(0, 0, 0, 0.4)",
|
|
1331
|
+
"on-info-container": "#395a70",
|
|
1283
1332
|
"brand": "#1b3c53",
|
|
1284
1333
|
"brand-foreground": "#f0f7ff",
|
|
1285
1334
|
"action": "#1d4ed8",
|
|
@@ -1395,40 +1444,40 @@ var tokens = {
|
|
|
1395
1444
|
}
|
|
1396
1445
|
},
|
|
1397
1446
|
"fontSize": {
|
|
1398
|
-
"display-lg": "
|
|
1399
|
-
"display-md": "
|
|
1400
|
-
"display-sm": "
|
|
1401
|
-
"headline-lg": "
|
|
1402
|
-
"headline-md": "
|
|
1403
|
-
"headline-sm": "
|
|
1404
|
-
"title-lg": "
|
|
1405
|
-
"title-md": "
|
|
1406
|
-
"title-sm": "
|
|
1407
|
-
"title-dialog": "
|
|
1408
|
-
"label-lg": "
|
|
1409
|
-
"label-md": "
|
|
1410
|
-
"label-sm": "
|
|
1411
|
-
"body-lg": "
|
|
1412
|
-
"body-md": "
|
|
1413
|
-
"body-sm": "
|
|
1447
|
+
"display-lg": "3.5625rem",
|
|
1448
|
+
"display-md": "2.8125rem",
|
|
1449
|
+
"display-sm": "2.25rem",
|
|
1450
|
+
"headline-lg": "2rem",
|
|
1451
|
+
"headline-md": "1.75rem",
|
|
1452
|
+
"headline-sm": "1.5rem",
|
|
1453
|
+
"title-lg": "1.375rem",
|
|
1454
|
+
"title-md": "1rem",
|
|
1455
|
+
"title-sm": "0.875rem",
|
|
1456
|
+
"title-dialog": "1.125rem",
|
|
1457
|
+
"label-lg": "0.875rem",
|
|
1458
|
+
"label-md": "0.75rem",
|
|
1459
|
+
"label-sm": "0.6875rem",
|
|
1460
|
+
"body-lg": "1rem",
|
|
1461
|
+
"body-md": "0.875rem",
|
|
1462
|
+
"body-sm": "0.75rem"
|
|
1414
1463
|
},
|
|
1415
1464
|
"lineHeight": {
|
|
1416
|
-
"display-lg": "
|
|
1417
|
-
"display-md": "
|
|
1418
|
-
"display-sm": "
|
|
1419
|
-
"headline-lg": "
|
|
1420
|
-
"headline-md": "
|
|
1421
|
-
"headline-sm": "
|
|
1422
|
-
"title-lg": "
|
|
1423
|
-
"title-md": "
|
|
1424
|
-
"title-sm": "
|
|
1425
|
-
"title-dialog": "
|
|
1426
|
-
"label-lg": "
|
|
1427
|
-
"label-md": "
|
|
1428
|
-
"label-sm": "
|
|
1429
|
-
"body-lg": "
|
|
1430
|
-
"body-md": "
|
|
1431
|
-
"body-sm": "
|
|
1465
|
+
"display-lg": "4rem",
|
|
1466
|
+
"display-md": "3.25rem",
|
|
1467
|
+
"display-sm": "2.75rem",
|
|
1468
|
+
"headline-lg": "2.5rem",
|
|
1469
|
+
"headline-md": "2.25rem",
|
|
1470
|
+
"headline-sm": "2rem",
|
|
1471
|
+
"title-lg": "1.75rem",
|
|
1472
|
+
"title-md": "1.5rem",
|
|
1473
|
+
"title-sm": "1.25rem",
|
|
1474
|
+
"title-dialog": "1.5rem",
|
|
1475
|
+
"label-lg": "1.25rem",
|
|
1476
|
+
"label-md": "1rem",
|
|
1477
|
+
"label-sm": "1rem",
|
|
1478
|
+
"body-lg": "1.5rem",
|
|
1479
|
+
"body-md": "1.25rem",
|
|
1480
|
+
"body-sm": "1rem"
|
|
1432
1481
|
},
|
|
1433
1482
|
"letterSpacing": {
|
|
1434
1483
|
"tighter": "-0.25px",
|
|
@@ -1515,14 +1564,14 @@ var tokens = {
|
|
|
1515
1564
|
],
|
|
1516
1565
|
"focus-destructive": [
|
|
1517
1566
|
{
|
|
1518
|
-
"color": "#
|
|
1567
|
+
"color": "#c60a0a",
|
|
1519
1568
|
"offsetX": "0",
|
|
1520
1569
|
"offsetY": "0",
|
|
1521
1570
|
"blur": "0",
|
|
1522
1571
|
"spread": "1px"
|
|
1523
1572
|
},
|
|
1524
1573
|
{
|
|
1525
|
-
"color": "#
|
|
1574
|
+
"color": "#c60a0a33",
|
|
1526
1575
|
"offsetX": "0",
|
|
1527
1576
|
"offsetY": "0",
|
|
1528
1577
|
"blur": "0",
|
|
@@ -1566,113 +1615,113 @@ var tokens = {
|
|
|
1566
1615
|
"display-lg": {
|
|
1567
1616
|
"fontFamily": "Montserrat, ui-sans-serif, system-ui, sans-serif",
|
|
1568
1617
|
"fontWeight": "500",
|
|
1569
|
-
"fontSize": "
|
|
1570
|
-
"lineHeight": "
|
|
1618
|
+
"fontSize": "3.5625rem",
|
|
1619
|
+
"lineHeight": "4rem",
|
|
1571
1620
|
"letterSpacing": "-0.25px"
|
|
1572
1621
|
},
|
|
1573
1622
|
"display-md": {
|
|
1574
1623
|
"fontFamily": "Montserrat, ui-sans-serif, system-ui, sans-serif",
|
|
1575
1624
|
"fontWeight": "500",
|
|
1576
|
-
"fontSize": "
|
|
1577
|
-
"lineHeight": "
|
|
1625
|
+
"fontSize": "2.8125rem",
|
|
1626
|
+
"lineHeight": "3.25rem",
|
|
1578
1627
|
"letterSpacing": "0"
|
|
1579
1628
|
},
|
|
1580
1629
|
"display-sm": {
|
|
1581
1630
|
"fontFamily": "Montserrat, ui-sans-serif, system-ui, sans-serif",
|
|
1582
1631
|
"fontWeight": "400",
|
|
1583
|
-
"fontSize": "
|
|
1584
|
-
"lineHeight": "
|
|
1632
|
+
"fontSize": "2.25rem",
|
|
1633
|
+
"lineHeight": "2.75rem",
|
|
1585
1634
|
"letterSpacing": "0"
|
|
1586
1635
|
},
|
|
1587
1636
|
"headline-lg": {
|
|
1588
1637
|
"fontFamily": "Montserrat, ui-sans-serif, system-ui, sans-serif",
|
|
1589
1638
|
"fontWeight": "600",
|
|
1590
|
-
"fontSize": "
|
|
1591
|
-
"lineHeight": "
|
|
1639
|
+
"fontSize": "2rem",
|
|
1640
|
+
"lineHeight": "2.5rem",
|
|
1592
1641
|
"letterSpacing": "0"
|
|
1593
1642
|
},
|
|
1594
1643
|
"headline-md": {
|
|
1595
1644
|
"fontFamily": "Montserrat, ui-sans-serif, system-ui, sans-serif",
|
|
1596
1645
|
"fontWeight": "400",
|
|
1597
|
-
"fontSize": "
|
|
1598
|
-
"lineHeight": "
|
|
1646
|
+
"fontSize": "1.75rem",
|
|
1647
|
+
"lineHeight": "2.25rem",
|
|
1599
1648
|
"letterSpacing": "0"
|
|
1600
1649
|
},
|
|
1601
1650
|
"headline-sm": {
|
|
1602
1651
|
"fontFamily": "Montserrat, ui-sans-serif, system-ui, sans-serif",
|
|
1603
1652
|
"fontWeight": "400",
|
|
1604
|
-
"fontSize": "
|
|
1605
|
-
"lineHeight": "
|
|
1653
|
+
"fontSize": "1.5rem",
|
|
1654
|
+
"lineHeight": "2rem",
|
|
1606
1655
|
"letterSpacing": "0"
|
|
1607
1656
|
},
|
|
1608
1657
|
"title-lg": {
|
|
1609
1658
|
"fontFamily": "Montserrat, ui-sans-serif, system-ui, sans-serif",
|
|
1610
1659
|
"fontWeight": "400",
|
|
1611
|
-
"fontSize": "
|
|
1612
|
-
"lineHeight": "
|
|
1660
|
+
"fontSize": "1.375rem",
|
|
1661
|
+
"lineHeight": "1.75rem",
|
|
1613
1662
|
"letterSpacing": "0"
|
|
1614
1663
|
},
|
|
1615
1664
|
"title-md": {
|
|
1616
1665
|
"fontFamily": "Montserrat, ui-sans-serif, system-ui, sans-serif",
|
|
1617
1666
|
"fontWeight": "500",
|
|
1618
|
-
"fontSize": "
|
|
1619
|
-
"lineHeight": "
|
|
1667
|
+
"fontSize": "1rem",
|
|
1668
|
+
"lineHeight": "1.5rem",
|
|
1620
1669
|
"letterSpacing": "0.15px"
|
|
1621
1670
|
},
|
|
1622
1671
|
"title-sm": {
|
|
1623
1672
|
"fontFamily": "Montserrat, ui-sans-serif, system-ui, sans-serif",
|
|
1624
1673
|
"fontWeight": "500",
|
|
1625
|
-
"fontSize": "
|
|
1626
|
-
"lineHeight": "
|
|
1674
|
+
"fontSize": "0.875rem",
|
|
1675
|
+
"lineHeight": "1.25rem",
|
|
1627
1676
|
"letterSpacing": "0.1px"
|
|
1628
1677
|
},
|
|
1629
1678
|
"title-dialog": {
|
|
1630
1679
|
"fontFamily": "Montserrat, ui-sans-serif, system-ui, sans-serif",
|
|
1631
1680
|
"fontWeight": "600",
|
|
1632
|
-
"fontSize": "
|
|
1633
|
-
"lineHeight": "
|
|
1681
|
+
"fontSize": "1.125rem",
|
|
1682
|
+
"lineHeight": "1.5rem",
|
|
1634
1683
|
"letterSpacing": "0"
|
|
1635
1684
|
},
|
|
1636
1685
|
"label-lg": {
|
|
1637
1686
|
"fontFamily": "Montserrat, ui-sans-serif, system-ui, sans-serif",
|
|
1638
1687
|
"fontWeight": "500",
|
|
1639
|
-
"fontSize": "
|
|
1640
|
-
"lineHeight": "
|
|
1688
|
+
"fontSize": "0.875rem",
|
|
1689
|
+
"lineHeight": "1.25rem",
|
|
1641
1690
|
"letterSpacing": "0.1px"
|
|
1642
1691
|
},
|
|
1643
1692
|
"label-md": {
|
|
1644
1693
|
"fontFamily": "Montserrat, ui-sans-serif, system-ui, sans-serif",
|
|
1645
1694
|
"fontWeight": "500",
|
|
1646
|
-
"fontSize": "
|
|
1647
|
-
"lineHeight": "
|
|
1695
|
+
"fontSize": "0.75rem",
|
|
1696
|
+
"lineHeight": "1rem",
|
|
1648
1697
|
"letterSpacing": "0.5px"
|
|
1649
1698
|
},
|
|
1650
1699
|
"label-sm": {
|
|
1651
1700
|
"fontFamily": "Montserrat, ui-sans-serif, system-ui, sans-serif",
|
|
1652
1701
|
"fontWeight": "500",
|
|
1653
|
-
"fontSize": "
|
|
1654
|
-
"lineHeight": "
|
|
1702
|
+
"fontSize": "0.6875rem",
|
|
1703
|
+
"lineHeight": "1rem",
|
|
1655
1704
|
"letterSpacing": "0.5px"
|
|
1656
1705
|
},
|
|
1657
1706
|
"body-lg": {
|
|
1658
1707
|
"fontFamily": "Montserrat, ui-sans-serif, system-ui, sans-serif",
|
|
1659
1708
|
"fontWeight": "400",
|
|
1660
|
-
"fontSize": "
|
|
1661
|
-
"lineHeight": "
|
|
1709
|
+
"fontSize": "1rem",
|
|
1710
|
+
"lineHeight": "1.5rem",
|
|
1662
1711
|
"letterSpacing": "0.5px"
|
|
1663
1712
|
},
|
|
1664
1713
|
"body-md": {
|
|
1665
1714
|
"fontFamily": "Montserrat, ui-sans-serif, system-ui, sans-serif",
|
|
1666
1715
|
"fontWeight": "400",
|
|
1667
|
-
"fontSize": "
|
|
1668
|
-
"lineHeight": "
|
|
1716
|
+
"fontSize": "0.875rem",
|
|
1717
|
+
"lineHeight": "1.25rem",
|
|
1669
1718
|
"letterSpacing": "0.25px"
|
|
1670
1719
|
},
|
|
1671
1720
|
"body-sm": {
|
|
1672
1721
|
"fontFamily": "Montserrat, ui-sans-serif, system-ui, sans-serif",
|
|
1673
1722
|
"fontWeight": "400",
|
|
1674
|
-
"fontSize": "
|
|
1675
|
-
"lineHeight": "
|
|
1723
|
+
"fontSize": "0.75rem",
|
|
1724
|
+
"lineHeight": "1rem",
|
|
1676
1725
|
"letterSpacing": "0"
|
|
1677
1726
|
}
|
|
1678
1727
|
}
|
|
@@ -1691,6 +1740,7 @@ var TOKEN_CONTRACT = [
|
|
|
1691
1740
|
{ token: "secondary-foreground", light: "#465245", dark: "#e3e7e3" },
|
|
1692
1741
|
{ token: "muted", light: "#f6f6f6", dark: "#102432" },
|
|
1693
1742
|
{ token: "muted-foreground", light: "#6d6d6d", dark: "#92b2c8" },
|
|
1743
|
+
{ token: "surface-grouped", light: "#f6f6f6", dark: "#081219", note: "grouped section under a card" },
|
|
1694
1744
|
{ token: "accent", light: "#f0f7ff", dark: "#102432" },
|
|
1695
1745
|
{ token: "accent-foreground", light: "#1d4ed8", dark: "#f0f7ff" },
|
|
1696
1746
|
{ token: "destructive", light: "#c60a0a", dark: "#dd6a6a", note: "darkened for AA" },
|
|
@@ -1709,6 +1759,12 @@ var TOKEN_CONTRACT = [
|
|
|
1709
1759
|
{ token: "link", light: "#1d4ed8", dark: "#60a5fa", note: "defaults to primary" },
|
|
1710
1760
|
{ token: "focus", light: "#1d4ed8", dark: "#60a5fa", note: "defaults to ring" }
|
|
1711
1761
|
];
|
|
1762
|
+
var ROLE_DEFAULTS = {
|
|
1763
|
+
action: "primary",
|
|
1764
|
+
"action-foreground": "primary-foreground",
|
|
1765
|
+
link: "primary",
|
|
1766
|
+
focus: "ring"
|
|
1767
|
+
};
|
|
1712
1768
|
var RADIUS_STEP_NAMES = ["sm", "md", "lg", "xl"];
|
|
1713
1769
|
var ELEVATION_STEP_NAMES = ["sm", "md", "lg", "xl"];
|
|
1714
1770
|
var px = (value) => Number(value.replace("px", ""));
|
|
@@ -1842,6 +1898,9 @@ var NEUTRAL_LADDER = {
|
|
|
1842
1898
|
card: 1,
|
|
1843
1899
|
popover: 1,
|
|
1844
1900
|
muted: 0.975,
|
|
1901
|
+
// The grouped section a card sits on. Light shares `muted`'s step; dark sits between the page and
|
|
1902
|
+
// `card`, because dark `muted` is above `card` and a card on it would read as recessed.
|
|
1903
|
+
"surface-grouped": 0.975,
|
|
1845
1904
|
border: 0.86,
|
|
1846
1905
|
input: 0.86,
|
|
1847
1906
|
"muted-foreground": 0.535,
|
|
@@ -1852,6 +1911,7 @@ var NEUTRAL_LADDER = {
|
|
|
1852
1911
|
card: 0.22,
|
|
1853
1912
|
popover: 0.22,
|
|
1854
1913
|
muted: 0.27,
|
|
1914
|
+
"surface-grouped": 0.19,
|
|
1855
1915
|
border: 0.4,
|
|
1856
1916
|
input: 0.4,
|
|
1857
1917
|
"muted-foreground": 0.72,
|
|
@@ -1933,6 +1993,7 @@ var ALIASED_FROM = {
|
|
|
1933
1993
|
input: "border"
|
|
1934
1994
|
};
|
|
1935
1995
|
var FOREGROUND_OF = Object.fromEntries(CONTRAST_PAIRS);
|
|
1996
|
+
var BASE_OF = Object.fromEntries(CONTRAST_PAIRS.map(([base, fg]) => [fg, base]));
|
|
1936
1997
|
function shippedColorsFor(mode) {
|
|
1937
1998
|
const out = {};
|
|
1938
1999
|
for (const row2 of TOKEN_CONTRACT) out[row2.token] = mode === "dark" ? row2.dark : row2.light;
|
|
@@ -1963,14 +2024,29 @@ function resolveMode(design, mode) {
|
|
|
1963
2024
|
Object.assign(colors, deriveChart(design.chartPalette, design.brand, mode));
|
|
1964
2025
|
const border = deriveSurfaceBorder(design.surface, mode, colors.border ?? "#000000");
|
|
1965
2026
|
if (border) colors.border = border;
|
|
2027
|
+
const generatedAction = colors.action;
|
|
2028
|
+
const pinned = design.manualOverrides;
|
|
1966
2029
|
for (const [token, hex] of Object.entries(design.manualOverrides)) {
|
|
1967
2030
|
if (hex) colors[token] = hex;
|
|
1968
2031
|
}
|
|
1969
2032
|
for (const [base, fg] of Object.entries(FOREGROUND_OF)) {
|
|
1970
|
-
if (
|
|
2033
|
+
if (pinned[base] && !pinned[fg]) {
|
|
1971
2034
|
colors[fg] = foregroundFor(colors[base]);
|
|
1972
2035
|
}
|
|
1973
2036
|
}
|
|
2037
|
+
for (const [role, source] of Object.entries(ROLE_DEFAULTS)) {
|
|
2038
|
+
if (pinned[role]) continue;
|
|
2039
|
+
if (BASE_OF[role] && pinned[BASE_OF[role]]) continue;
|
|
2040
|
+
const sourceMoved = Boolean(pinned[source] || BASE_OF[source] && pinned[BASE_OF[source]]);
|
|
2041
|
+
if (sourceMoved && colors[source]) colors[role] = colors[source];
|
|
2042
|
+
}
|
|
2043
|
+
const action = colors.action ? hexToOklch(colors.action) : null;
|
|
2044
|
+
if (action && colors.action !== generatedAction) {
|
|
2045
|
+
const states = interactionStates(action, mode);
|
|
2046
|
+
colors["action-hover"] = states.hover;
|
|
2047
|
+
colors["action-pressed"] = states.pressed;
|
|
2048
|
+
if (!pinned["action-foreground"] && !pinned["primary-foreground"]) colors["action-foreground"] = states.foreground;
|
|
2049
|
+
}
|
|
1974
2050
|
return {
|
|
1975
2051
|
colors,
|
|
1976
2052
|
radius: deriveRadius(design.radius),
|
|
@@ -2027,14 +2103,14 @@ function cssVarOverrides(resolved, base = SHIPPED_THEME.light) {
|
|
|
2027
2103
|
return out;
|
|
2028
2104
|
}
|
|
2029
2105
|
var varLines = (resolved, base) => Object.entries(cssVarOverrides(resolved, base)).map(([token, value]) => declaration(token, value));
|
|
2030
|
-
function exportCss(
|
|
2106
|
+
function exportCss(theme, options = {}) {
|
|
2031
2107
|
const rootSelector = options.rootSelector ?? ":root";
|
|
2032
2108
|
const darkSelector = options.darkSelector ?? ".dark";
|
|
2033
|
-
const rootVars = varLines(
|
|
2034
|
-
const root = [...colorLines(
|
|
2109
|
+
const rootVars = varLines(theme.light, SHIPPED_THEME.light);
|
|
2110
|
+
const root = [...colorLines(theme.light, SHIPPED_THEME.light), ...rootVars];
|
|
2035
2111
|
const dark = [
|
|
2036
|
-
...colorLines(
|
|
2037
|
-
...varLines(
|
|
2112
|
+
...colorLines(theme.dark, SHIPPED_THEME.dark),
|
|
2113
|
+
...varLines(theme.dark, SHIPPED_THEME.dark).filter((line) => !rootVars.includes(line))
|
|
2038
2114
|
];
|
|
2039
2115
|
return [block(rootSelector, root), block(darkSelector, dark)].filter(Boolean).join("\n\n");
|
|
2040
2116
|
}
|
|
@@ -2127,7 +2203,9 @@ var FLUTTER_COLOR_FIELDS = [
|
|
|
2127
2203
|
["info", "info"],
|
|
2128
2204
|
["infoForeground", "info-foreground"],
|
|
2129
2205
|
["popover", "popover"],
|
|
2130
|
-
["popoverForeground", "popover-foreground"]
|
|
2206
|
+
["popoverForeground", "popover-foreground"],
|
|
2207
|
+
["onInfoContainer", "on-info-container"],
|
|
2208
|
+
["scrim", "scrim"]
|
|
2131
2209
|
];
|
|
2132
2210
|
var FLUTTER_CHART_STOPS = [1, 2, 3, 4, 5];
|
|
2133
2211
|
var shippedRef = (appearance, field) => `KinetixColors.${appearance}.${field}`;
|
|
@@ -2147,8 +2225,8 @@ function colorSet(mode, appearance) {
|
|
|
2147
2225
|
);
|
|
2148
2226
|
return [...fields, " chart: <Color>[", ...chart, " ],"].join("\n");
|
|
2149
2227
|
}
|
|
2150
|
-
function header(symbol,
|
|
2151
|
-
const design =
|
|
2228
|
+
function header(symbol, theme) {
|
|
2229
|
+
const design = theme.meta.design;
|
|
2152
2230
|
return [
|
|
2153
2231
|
"//",
|
|
2154
2232
|
`// ${symbol} \u2014 a KinetixUI Create theme.`,
|
|
@@ -2167,9 +2245,9 @@ function header(symbol, theme2) {
|
|
|
2167
2245
|
"// They apply on the web. That is the only thing this file does not carry.",
|
|
2168
2246
|
"//",
|
|
2169
2247
|
"// A field written as `KinetixColors.light.\u2026` is one this design did not change, and it keeps",
|
|
2170
|
-
"// following the library.
|
|
2171
|
-
"// successForeground and warningForeground \u2014 are always references, because
|
|
2172
|
-
"// those tokens.",
|
|
2248
|
+
"// following the library. Eight of them \u2014 tertiary, tertiaryForeground, info, infoForeground,",
|
|
2249
|
+
"// onInfoContainer, scrim, successForeground and warningForeground \u2014 are always references, because",
|
|
2250
|
+
"// Create does not model those tokens.",
|
|
2173
2251
|
"//",
|
|
2174
2252
|
"// These are `static final` rather than `static const`: Dart's constant expressions do not include",
|
|
2175
2253
|
"// instance field access, so a `static const` could not reference the shipped palette. The theme is",
|
|
@@ -2186,12 +2264,12 @@ function header(symbol, theme2) {
|
|
|
2186
2264
|
"//"
|
|
2187
2265
|
].join("\n");
|
|
2188
2266
|
}
|
|
2189
|
-
function exportFlutter(
|
|
2267
|
+
function exportFlutter(theme, options = {}) {
|
|
2190
2268
|
const symbol = options.symbol ?? DEFAULT_FLUTTER_SYMBOL;
|
|
2191
2269
|
const reason = dartSymbolError(symbol);
|
|
2192
2270
|
if (reason) throw new Error(reason);
|
|
2193
2271
|
return [
|
|
2194
|
-
header(symbol,
|
|
2272
|
+
header(symbol, theme),
|
|
2195
2273
|
"",
|
|
2196
2274
|
"import 'package:flutter/widgets.dart';",
|
|
2197
2275
|
"import 'package:kinetix_ui/kinetix_ui.dart';",
|
|
@@ -2201,11 +2279,11 @@ function exportFlutter(theme2, options = {}) {
|
|
|
2201
2279
|
// `enum` and Compose's `object` express.
|
|
2202
2280
|
`abstract final class ${symbol} {`,
|
|
2203
2281
|
" static final KinetixColors light = KinetixColors(",
|
|
2204
|
-
colorSet(
|
|
2282
|
+
colorSet(theme.light, "light"),
|
|
2205
2283
|
" );",
|
|
2206
2284
|
"",
|
|
2207
2285
|
" static final KinetixColors dark = KinetixColors(",
|
|
2208
|
-
colorSet(
|
|
2286
|
+
colorSet(theme.dark, "dark"),
|
|
2209
2287
|
" );",
|
|
2210
2288
|
"}",
|
|
2211
2289
|
""
|
|
@@ -2295,7 +2373,9 @@ var COMPOSE_COLOR_FIELDS = [
|
|
|
2295
2373
|
["info", "info"],
|
|
2296
2374
|
["infoForeground", "info-foreground"],
|
|
2297
2375
|
["popover", "popover"],
|
|
2298
|
-
["popoverForeground", "popover-foreground"]
|
|
2376
|
+
["popoverForeground", "popover-foreground"],
|
|
2377
|
+
["onInfoContainer", "on-info-container"],
|
|
2378
|
+
["scrim", "scrim"]
|
|
2299
2379
|
];
|
|
2300
2380
|
var COMPOSE_CHART_STOPS = [1, 2, 3, 4, 5];
|
|
2301
2381
|
var COMPOSE_UNMAPPED_PRESET_ROLES = ["input", "ring"];
|
|
@@ -2318,8 +2398,8 @@ function colorSet2(mode, appearance) {
|
|
|
2318
2398
|
);
|
|
2319
2399
|
return [...fields, " chart = listOf(", ...chart, " ),"].join("\n");
|
|
2320
2400
|
}
|
|
2321
|
-
function header2(symbol,
|
|
2322
|
-
const design =
|
|
2401
|
+
function header2(symbol, theme) {
|
|
2402
|
+
const design = theme.meta.design;
|
|
2323
2403
|
return [
|
|
2324
2404
|
"//",
|
|
2325
2405
|
`// ${symbol} \u2014 a KinetixUI Create theme.`,
|
|
@@ -2349,15 +2429,15 @@ function header2(symbol, theme2) {
|
|
|
2349
2429
|
"//"
|
|
2350
2430
|
].join("\n");
|
|
2351
2431
|
}
|
|
2352
|
-
function exportCompose(
|
|
2432
|
+
function exportCompose(theme, options = {}) {
|
|
2353
2433
|
const symbol = options.symbol ?? DEFAULT_COMPOSE_SYMBOL;
|
|
2354
2434
|
const reason = kotlinSymbolError(symbol);
|
|
2355
2435
|
if (reason) throw new Error(reason);
|
|
2356
|
-
const light = colorSet2(
|
|
2357
|
-
const dark = colorSet2(
|
|
2436
|
+
const light = colorSet2(theme.light, "light");
|
|
2437
|
+
const dark = colorSet2(theme.dark, "dark");
|
|
2358
2438
|
const usesColor = `${light}${dark}`.includes("Color(0x");
|
|
2359
2439
|
return [
|
|
2360
|
-
header2(symbol,
|
|
2440
|
+
header2(symbol, theme),
|
|
2361
2441
|
"",
|
|
2362
2442
|
"package com.kinetixui.create",
|
|
2363
2443
|
"",
|
|
@@ -2468,6 +2548,59 @@ function swiftColor(hex) {
|
|
|
2468
2548
|
const [r, g, b] = swiftUiChannels(`#${h}`);
|
|
2469
2549
|
return `Color(red: ${r}, green: ${g}, blue: ${b})`;
|
|
2470
2550
|
}
|
|
2551
|
+
function swiftShadowColor(hex) {
|
|
2552
|
+
const h = hex.replace("#", "").trim();
|
|
2553
|
+
if (!/^[0-9a-fA-F]{6}([0-9a-fA-F]{2})?$/.test(h)) return null;
|
|
2554
|
+
const base = h.slice(0, 6) === "000000" ? "Color.black" : swiftColor(`#${h.slice(0, 6)}`);
|
|
2555
|
+
if (!base) return null;
|
|
2556
|
+
if (h.length === 6) return base;
|
|
2557
|
+
const alpha = Number((parseInt(h.slice(6, 8), 16) / 255).toFixed(3));
|
|
2558
|
+
return `${base}.opacity(${alpha})`;
|
|
2559
|
+
}
|
|
2560
|
+
var RADIUS_STEPS2 = ["sm", "md", "lg", "xl"];
|
|
2561
|
+
var ELEVATION_STEPS = ["sm", "md", "lg", "xl"];
|
|
2562
|
+
var sameLayers = (a, b) => a.length === b.length && a.every((layer, i) => {
|
|
2563
|
+
const other = b[i];
|
|
2564
|
+
return layer.color.toLowerCase() === other.color.toLowerCase() && layer.offsetX === other.offsetX && layer.offsetY === other.offsetY && layer.blur === other.blur && layer.spread === other.spread;
|
|
2565
|
+
});
|
|
2566
|
+
var swiftLength = (value) => Number(Number(value).toFixed(3));
|
|
2567
|
+
function swiftRadii(radius) {
|
|
2568
|
+
if (RADIUS_STEPS2.every((step) => radius[step] === SHIPPED_RADIUS[step])) return "KinetixRadii.default";
|
|
2569
|
+
return [
|
|
2570
|
+
"KinetixRadii(",
|
|
2571
|
+
` field: ${swiftLength(String(radius.sm))},`,
|
|
2572
|
+
` control: ${swiftLength(String(radius.md))},`,
|
|
2573
|
+
` container: ${swiftLength(String(radius.lg))},`,
|
|
2574
|
+
` surface: ${swiftLength(String(radius.xl))}`,
|
|
2575
|
+
" )"
|
|
2576
|
+
].join("\n");
|
|
2577
|
+
}
|
|
2578
|
+
function swiftElevations(elevation) {
|
|
2579
|
+
if (ELEVATION_STEPS.every((s) => sameLayers(elevation[s], SHIPPED_ELEVATION[s]))) return "KinetixElevations.default";
|
|
2580
|
+
const step = (layers) => {
|
|
2581
|
+
if (layers.length === 0) return ".none";
|
|
2582
|
+
const rendered = layers.map((layer) => {
|
|
2583
|
+
const color = swiftShadowColor(layer.color) ?? "Color.black.opacity(0.05)";
|
|
2584
|
+
return [
|
|
2585
|
+
" KinetixShadowLayer(",
|
|
2586
|
+
` color: ${color},`,
|
|
2587
|
+
` radius: ${swiftLength(layer.blur)},`,
|
|
2588
|
+
` x: ${swiftLength(layer.offsetX)},`,
|
|
2589
|
+
` y: ${swiftLength(layer.offsetY)}`,
|
|
2590
|
+
" ),"
|
|
2591
|
+
].join("\n");
|
|
2592
|
+
});
|
|
2593
|
+
return ["KinetixElevation([", ...rendered, " ])"].join("\n");
|
|
2594
|
+
};
|
|
2595
|
+
return [
|
|
2596
|
+
"KinetixElevations(",
|
|
2597
|
+
` sm: ${step(elevation.sm)},`,
|
|
2598
|
+
` md: ${step(elevation.md)},`,
|
|
2599
|
+
` lg: ${step(elevation.lg)},`,
|
|
2600
|
+
` xl: ${step(elevation.xl)}`,
|
|
2601
|
+
" )"
|
|
2602
|
+
].join("\n");
|
|
2603
|
+
}
|
|
2471
2604
|
var SWIFT_COLOR_FIELDS = [
|
|
2472
2605
|
"primary",
|
|
2473
2606
|
"primary-foreground",
|
|
@@ -2503,7 +2636,9 @@ var SWIFT_COLOR_FIELDS = [
|
|
|
2503
2636
|
"info",
|
|
2504
2637
|
"info-foreground",
|
|
2505
2638
|
"popover",
|
|
2506
|
-
"popover-foreground"
|
|
2639
|
+
"popover-foreground",
|
|
2640
|
+
"on-info-container",
|
|
2641
|
+
"scrim"
|
|
2507
2642
|
];
|
|
2508
2643
|
var SWIFT_CHART_STOPS = [1, 2, 3, 4, 5];
|
|
2509
2644
|
var SHIPPED_ENUM = { light: "KinetixColorsSwiftUI", dark: "KinetixColorsSwiftUIDark" };
|
|
@@ -2524,8 +2659,8 @@ function colorSet3(mode, appearance) {
|
|
|
2524
2659
|
);
|
|
2525
2660
|
return [...fields, " chart: [", ...chart, " ]"].join("\n");
|
|
2526
2661
|
}
|
|
2527
|
-
function header3(symbol,
|
|
2528
|
-
const design =
|
|
2662
|
+
function header3(symbol, theme) {
|
|
2663
|
+
const design = theme.meta.design;
|
|
2529
2664
|
return [
|
|
2530
2665
|
"//",
|
|
2531
2666
|
`// ${symbol} \u2014 a KinetixUI Create theme.`,
|
|
@@ -2534,38 +2669,50 @@ function header3(symbol, theme2) {
|
|
|
2534
2669
|
"// editing this file: it is a rendering of a preset, not a source of one.",
|
|
2535
2670
|
"//",
|
|
2536
2671
|
`// Theme colour ${design.brand} \xB7 ${design.neutral} neutral \xB7 ${design.chartPalette} charts`,
|
|
2672
|
+
`// Radius ${design.radius} \xB7 surface ${design.surface}`,
|
|
2673
|
+
"//",
|
|
2674
|
+
"// Colours, corner radii and elevation \u2014 the whole design this file can express. Radii and",
|
|
2675
|
+
"// elevation are one value rather than a light/dark pair, because the runtime takes one and the",
|
|
2676
|
+
"// two appearances resolve to the same ladder.",
|
|
2537
2677
|
"//",
|
|
2538
|
-
"//
|
|
2539
|
-
"//
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
"// `KinetixTheme(light: \u2026, dark: \u2026, radii: \u2026, elevations: \u2026)`. Nothing is silently dropped.",
|
|
2678
|
+
"// ONE THING DOES NOT SURVIVE THE PORT. CSS shadows have `spread`; SwiftUI's `.shadow` has no",
|
|
2679
|
+
"// equivalent, so the spread on this design's larger steps is dropped and they render slightly",
|
|
2680
|
+
"// wider here than on the web. The shipped `KinetixElevations.default` is mapped the same way, so",
|
|
2681
|
+
"// a generated theme and the built-in one differ from the web in exactly the same manner.",
|
|
2543
2682
|
"//",
|
|
2544
2683
|
"// A field written as `KinetixColorsSwiftUI.\u2026` is one this design did not change, and it keeps",
|
|
2545
2684
|
"// following the library.",
|
|
2546
2685
|
"//"
|
|
2547
2686
|
].join("\n");
|
|
2548
2687
|
}
|
|
2549
|
-
function exportSwiftUi(
|
|
2688
|
+
function exportSwiftUi(theme, options = {}) {
|
|
2550
2689
|
const symbol = options.symbol ?? DEFAULT_SWIFT_SYMBOL;
|
|
2551
2690
|
const reason = swiftSymbolError(symbol);
|
|
2552
2691
|
if (reason) throw new Error(reason);
|
|
2553
2692
|
return [
|
|
2554
|
-
header3(symbol,
|
|
2693
|
+
header3(symbol, theme),
|
|
2555
2694
|
"",
|
|
2556
2695
|
"import SwiftUI",
|
|
2557
2696
|
"import KinetixUI",
|
|
2558
2697
|
"",
|
|
2559
2698
|
`public enum ${symbol} {`,
|
|
2560
|
-
" /// Apply
|
|
2561
|
-
|
|
2699
|
+
" /// Apply the whole design with",
|
|
2700
|
+
` /// \`KinetixTheme(light: .light, dark: .dark, radii: .radii, elevations: .elevations) { \u2026 }\`,`,
|
|
2701
|
+
" /// or set `\\.kinetixColors` directly to pin one appearance.",
|
|
2562
2702
|
` public static let light = KinetixColors(`,
|
|
2563
|
-
colorSet3(
|
|
2703
|
+
colorSet3(theme.light, "light"),
|
|
2564
2704
|
" )",
|
|
2565
2705
|
"",
|
|
2566
2706
|
` public static let dark = KinetixColors(`,
|
|
2567
|
-
colorSet3(
|
|
2707
|
+
colorSet3(theme.dark, "dark"),
|
|
2568
2708
|
" )",
|
|
2709
|
+
"",
|
|
2710
|
+
// One value, not a light/dark pair: the runtime takes one, and the resolver produces the same
|
|
2711
|
+
// ladder for both appearances — a card's corner does not change when the lights go out.
|
|
2712
|
+
// `swiftui.test.ts` asserts that equality rather than trusting it.
|
|
2713
|
+
` public static let radii = ${swiftRadii(theme.light.radius)}`,
|
|
2714
|
+
"",
|
|
2715
|
+
` public static let elevations = ${swiftElevations(theme.light.elevation)}`,
|
|
2569
2716
|
"}",
|
|
2570
2717
|
""
|
|
2571
2718
|
].join("\n");
|
|
@@ -2637,7 +2784,10 @@ async function presetSwiftUi(input, opts) {
|
|
|
2637
2784
|
const swift = exportSwiftUi(resolveCreateTheme(read(input)), { symbol });
|
|
2638
2785
|
if (opts.output) {
|
|
2639
2786
|
await writeFile3(opts.output, swift, "utf8");
|
|
2640
|
-
console.log(
|
|
2787
|
+
console.log(
|
|
2788
|
+
pc8.green("\u2714"),
|
|
2789
|
+
`Wrote ${opts.output} \u2014 apply it with KinetixTheme(light: ${symbol}.light, dark: ${symbol}.dark, radii: ${symbol}.radii, elevations: ${symbol}.elevations)`
|
|
2790
|
+
);
|
|
2641
2791
|
return;
|
|
2642
2792
|
}
|
|
2643
2793
|
process.stdout.write(swift);
|
|
@@ -2886,7 +3036,7 @@ async function themeBuild(name, options) {
|
|
|
2886
3036
|
// package.json
|
|
2887
3037
|
var package_default = {
|
|
2888
3038
|
name: "@kinetixui/cli",
|
|
2889
|
-
version: "0.
|
|
3039
|
+
version: "0.24.0",
|
|
2890
3040
|
description: "Add KinetixUI components and tokens to your project.",
|
|
2891
3041
|
type: "module",
|
|
2892
3042
|
license: "MIT",
|
|
@@ -2900,8 +3050,10 @@ var package_default = {
|
|
|
2900
3050
|
],
|
|
2901
3051
|
scripts: {
|
|
2902
3052
|
build: "tsup src/index.ts --format esm --clean",
|
|
2903
|
-
|
|
2904
|
-
|
|
3053
|
+
dev: 'tsup src/index.ts --format esm --watch --onSuccess "node dist/index.js"',
|
|
3054
|
+
test: "vitest run",
|
|
3055
|
+
"test:watch": "vitest",
|
|
3056
|
+
typecheck: "tsc --noEmit"
|
|
2905
3057
|
},
|
|
2906
3058
|
dependencies: {
|
|
2907
3059
|
commander: "^15.0.0",
|
|
@@ -2911,10 +3063,12 @@ var package_default = {
|
|
|
2911
3063
|
devDependencies: {
|
|
2912
3064
|
"@kinetixui/create-preset": "workspace:*",
|
|
2913
3065
|
"@kinetixui/create-theme": "workspace:*",
|
|
2914
|
-
"@types/node": "^22.20.
|
|
3066
|
+
"@types/node": "^22.20.4",
|
|
2915
3067
|
"@types/prompts": "^2.4.9",
|
|
3068
|
+
jsdom: "^30.1.0",
|
|
2916
3069
|
tsup: "^8.5.1",
|
|
2917
|
-
typescript: "^5.9.3"
|
|
3070
|
+
typescript: "^5.9.3",
|
|
3071
|
+
vitest: "^5.0.2"
|
|
2918
3072
|
},
|
|
2919
3073
|
engines: {
|
|
2920
3074
|
node: ">=18"
|
|
@@ -2925,129 +3079,134 @@ var package_default = {
|
|
|
2925
3079
|
}
|
|
2926
3080
|
};
|
|
2927
3081
|
|
|
3082
|
+
// src/program.ts
|
|
3083
|
+
function createProgram() {
|
|
3084
|
+
const program = new Command();
|
|
3085
|
+
program.name("kinetixui").description("Add KinetixUI components and tokens to your project.").version(package_default.version);
|
|
3086
|
+
program.command("init").description("Set up kinetixui.json and pull in the token contract.").option("-y, --yes", "skip prompts and use defaults", false).option("-r, --registry <url>", "registry base URL", DEFAULT_REGISTRY).action(async (opts) => {
|
|
3087
|
+
try {
|
|
3088
|
+
await init({ registry: opts.registry, yes: opts.yes });
|
|
3089
|
+
} catch (err) {
|
|
3090
|
+
console.error(pc10.red("\u2716"), err.message);
|
|
3091
|
+
process.exitCode = 1;
|
|
3092
|
+
}
|
|
3093
|
+
});
|
|
3094
|
+
program.command("add").description("Add one or more components to your project.").argument("[components...]", "component names, e.g. button card").option("-a, --all", "add every component in the registry", false).option("-o, --overwrite", "overwrite existing files", false).option("-r, --registry <url>", "registry base URL", DEFAULT_REGISTRY).action(async (components, opts) => {
|
|
3095
|
+
try {
|
|
3096
|
+
await add(components, { registry: opts.registry, overwrite: opts.overwrite, all: opts.all });
|
|
3097
|
+
} catch (err) {
|
|
3098
|
+
console.error(pc10.red("\u2716"), err.message);
|
|
3099
|
+
process.exitCode = 1;
|
|
3100
|
+
}
|
|
3101
|
+
});
|
|
3102
|
+
program.command("list").description("List every component available in the registry.").option("-r, --registry <url>", "registry base URL", DEFAULT_REGISTRY).action(async (opts) => {
|
|
3103
|
+
try {
|
|
3104
|
+
await list({ registry: opts.registry });
|
|
3105
|
+
} catch (err) {
|
|
3106
|
+
console.error(pc10.red("\u2716"), err.message);
|
|
3107
|
+
process.exitCode = 1;
|
|
3108
|
+
}
|
|
3109
|
+
});
|
|
3110
|
+
program.command("inspect").description("Show everything the registry knows about one component.").argument("<name>", "component name, e.g. button").option("-r, --registry <url>", "registry base URL", DEFAULT_REGISTRY).action(async (name, opts) => {
|
|
3111
|
+
try {
|
|
3112
|
+
await inspect(name, { registry: opts.registry });
|
|
3113
|
+
} catch (err) {
|
|
3114
|
+
console.error(pc10.red("\u2716"), err.message);
|
|
3115
|
+
process.exitCode = 1;
|
|
3116
|
+
}
|
|
3117
|
+
});
|
|
3118
|
+
program.command("parity").description("Show which platforms carry each component (or one, by name).").argument("[components...]", "component names, e.g. button card \u2014 omit for the full matrix").option("-r, --registry <url>", "registry base URL", DEFAULT_REGISTRY).action(async (components, opts) => {
|
|
3119
|
+
try {
|
|
3120
|
+
await parity(components, { registry: opts.registry });
|
|
3121
|
+
} catch (err) {
|
|
3122
|
+
console.error(pc10.red("\u2716"), err.message);
|
|
3123
|
+
process.exitCode = 1;
|
|
3124
|
+
}
|
|
3125
|
+
});
|
|
3126
|
+
program.command("lint").description("Scan for hardcoded colors and spacing that should be semantic tokens.").argument("[path]", "directory to scan \u2014 defaults to the components/ui aliases from kinetixui.json").option("--no-fail", "exit 0 even if violations are found").action(async (target, opts) => {
|
|
3127
|
+
try {
|
|
3128
|
+
await lint(target, { fail: opts.fail });
|
|
3129
|
+
} catch (err) {
|
|
3130
|
+
console.error(pc10.red("\u2716"), err.message);
|
|
3131
|
+
process.exitCode = 1;
|
|
3132
|
+
}
|
|
3133
|
+
});
|
|
3134
|
+
program.command("doctor").description("Check kinetixui.json, its aliases, and the registry connection.").action(async () => {
|
|
3135
|
+
try {
|
|
3136
|
+
await doctor();
|
|
3137
|
+
} catch (err) {
|
|
3138
|
+
console.error(pc10.red("\u2716"), err.message);
|
|
3139
|
+
process.exitCode = 1;
|
|
3140
|
+
}
|
|
3141
|
+
});
|
|
3142
|
+
const theme = program.command("theme").description("Scaffold and compile a local token override (CSS only \u2014 see 'theme build --help').");
|
|
3143
|
+
theme.command("create").description("Scaffold kinetixui-themes/<name>.csv with every overridable token, commented out.").argument("<name>", "theme name, e.g. acme").option("-f, --force", "overwrite an existing theme file", false).action(async (name, opts) => {
|
|
3144
|
+
try {
|
|
3145
|
+
await themeCreate(name, { force: opts.force });
|
|
3146
|
+
} catch (err) {
|
|
3147
|
+
console.error(pc10.red("\u2716"), err.message);
|
|
3148
|
+
process.exitCode = 1;
|
|
3149
|
+
}
|
|
3150
|
+
});
|
|
3151
|
+
theme.command("build").description("Compile kinetixui-themes/<name>.csv to CSS and print a WCAG AA contrast report. This command is CSS only; native themes come from `preset swiftui|compose|flutter`.").argument("<name>", "theme name, e.g. acme").option("--no-fail-on-contrast", "exit 0 even if a pair fails WCAG AA").action(async (name, opts) => {
|
|
3152
|
+
try {
|
|
3153
|
+
await themeBuild(name, { failOnContrast: opts.failOnContrast });
|
|
3154
|
+
} catch (err) {
|
|
3155
|
+
console.error(pc10.red("\u2716"), err.message);
|
|
3156
|
+
process.exitCode = 1;
|
|
3157
|
+
}
|
|
3158
|
+
});
|
|
3159
|
+
const preset = program.command("preset").description("Read a Kinetix Create preset code (KX1_\u2026) produced by kinetixui.com/create.");
|
|
3160
|
+
preset.command("decode").description("Show what a preset contains. Accepts a KX1_ code or a full /create?preset=\u2026 URL.").argument("<preset>", "a KX1_ code, or a share URL").option("--json", "print the configuration as JSON", false).action((input, opts) => {
|
|
3161
|
+
try {
|
|
3162
|
+
presetDecode(input, { json: opts.json });
|
|
3163
|
+
} catch (err) {
|
|
3164
|
+
console.error(pc10.red("\u2716"), err.message);
|
|
3165
|
+
process.exitCode = 1;
|
|
3166
|
+
}
|
|
3167
|
+
});
|
|
3168
|
+
preset.command("css").description("Resolve a preset into its web CSS override block \u2014 the same output the workspace exports for Web CSS. This command is CSS only; see `preset swiftui|compose|flutter`.").argument("<preset>", "a KX1_ code, or a share URL").option("-o, --output <file>", "write to a file instead of stdout").action(async (input, opts) => {
|
|
3169
|
+
try {
|
|
3170
|
+
await presetCss(input, { output: opts.output });
|
|
3171
|
+
} catch (err) {
|
|
3172
|
+
console.error(pc10.red("\u2716"), err.message);
|
|
3173
|
+
process.exitCode = 1;
|
|
3174
|
+
}
|
|
3175
|
+
});
|
|
3176
|
+
preset.command("swiftui").description("Resolve a preset into a SwiftUI theme file (KinetixColors, light and dark). Colours only \u2014 SwiftUI has no radius or elevation token to carry. No Compose or Flutter output.").argument("<preset>", "a KX1_ code, or a share URL").option("-o, --output <file>", "write to a file instead of stdout").option("-n, --name <symbol>", "the Swift enum to generate", DEFAULT_SWIFT_SYMBOL).action(async (input, opts) => {
|
|
3177
|
+
try {
|
|
3178
|
+
await presetSwiftUi(input, { output: opts.output, name: opts.name });
|
|
3179
|
+
} catch (err) {
|
|
3180
|
+
console.error(pc10.red("\u2716"), err.message);
|
|
3181
|
+
process.exitCode = 1;
|
|
3182
|
+
}
|
|
3183
|
+
});
|
|
3184
|
+
preset.command("compose").description("Resolve a preset into a Jetpack Compose theme file (KinetixColors, light and dark). Colours only \u2014 Compose has no runtime radius or elevation token to carry. No Flutter or Android XML output.").argument("<preset>", "a KX1_ code, or a share URL").option("-o, --output <file>", "write to a file instead of stdout").option("-n, --name <symbol>", "the Kotlin object to generate", DEFAULT_COMPOSE_SYMBOL).action(async (input, opts) => {
|
|
3185
|
+
try {
|
|
3186
|
+
await presetCompose(input, { output: opts.output, name: opts.name });
|
|
3187
|
+
} catch (err) {
|
|
3188
|
+
console.error(pc10.red("\u2716"), err.message);
|
|
3189
|
+
process.exitCode = 1;
|
|
3190
|
+
}
|
|
3191
|
+
});
|
|
3192
|
+
preset.command("flutter").description("Resolve a preset into a Flutter theme file (KinetixColors, light and dark). Colours only \u2014 Flutter's radius and elevation are not runtime-themeable. No Android XML output.").argument("<preset>", "a KX1_ code, or a share URL").option("-o, --output <file>", "write to a file instead of stdout").option("-n, --name <symbol>", "the Dart class to generate", DEFAULT_FLUTTER_SYMBOL).action(async (input, opts) => {
|
|
3193
|
+
try {
|
|
3194
|
+
await presetFlutter(input, { output: opts.output, name: opts.name });
|
|
3195
|
+
} catch (err) {
|
|
3196
|
+
console.error(pc10.red("\u2716"), err.message);
|
|
3197
|
+
process.exitCode = 1;
|
|
3198
|
+
}
|
|
3199
|
+
});
|
|
3200
|
+
preset.command("url").description("Print the canonical share URL for a preset. Prints it \u2014 piping it to your opener is up to you.").argument("<preset>", "a KX1_ code, or a share URL").option("-s, --site <origin>", "site origin", "https://kinetixui.com").action((input, opts) => {
|
|
3201
|
+
try {
|
|
3202
|
+
presetUrlCommand(input, { site: opts.site });
|
|
3203
|
+
} catch (err) {
|
|
3204
|
+
console.error(pc10.red("\u2716"), err.message);
|
|
3205
|
+
process.exitCode = 1;
|
|
3206
|
+
}
|
|
3207
|
+
});
|
|
3208
|
+
return program;
|
|
3209
|
+
}
|
|
3210
|
+
|
|
2928
3211
|
// src/index.ts
|
|
2929
|
-
|
|
2930
|
-
program.name("kinetixui").description("Add KinetixUI components and tokens to your project.").version(package_default.version);
|
|
2931
|
-
program.command("init").description("Set up kinetixui.json and pull in the token contract.").option("-y, --yes", "skip prompts and use defaults", false).option("-r, --registry <url>", "registry base URL", DEFAULT_REGISTRY).action(async (opts) => {
|
|
2932
|
-
try {
|
|
2933
|
-
await init({ registry: opts.registry, yes: opts.yes });
|
|
2934
|
-
} catch (err) {
|
|
2935
|
-
console.error(pc10.red("\u2716"), err.message);
|
|
2936
|
-
process.exitCode = 1;
|
|
2937
|
-
}
|
|
2938
|
-
});
|
|
2939
|
-
program.command("add").description("Add one or more components to your project.").argument("[components...]", "component names, e.g. button card").option("-a, --all", "add every component in the registry", false).option("-o, --overwrite", "overwrite existing files", false).option("-r, --registry <url>", "registry base URL", DEFAULT_REGISTRY).action(async (components, opts) => {
|
|
2940
|
-
try {
|
|
2941
|
-
await add(components, { registry: opts.registry, overwrite: opts.overwrite, all: opts.all });
|
|
2942
|
-
} catch (err) {
|
|
2943
|
-
console.error(pc10.red("\u2716"), err.message);
|
|
2944
|
-
process.exitCode = 1;
|
|
2945
|
-
}
|
|
2946
|
-
});
|
|
2947
|
-
program.command("list").description("List every component available in the registry.").option("-r, --registry <url>", "registry base URL", DEFAULT_REGISTRY).action(async (opts) => {
|
|
2948
|
-
try {
|
|
2949
|
-
await list({ registry: opts.registry });
|
|
2950
|
-
} catch (err) {
|
|
2951
|
-
console.error(pc10.red("\u2716"), err.message);
|
|
2952
|
-
process.exitCode = 1;
|
|
2953
|
-
}
|
|
2954
|
-
});
|
|
2955
|
-
program.command("inspect").description("Show everything the registry knows about one component.").argument("<name>", "component name, e.g. button").option("-r, --registry <url>", "registry base URL", DEFAULT_REGISTRY).action(async (name, opts) => {
|
|
2956
|
-
try {
|
|
2957
|
-
await inspect(name, { registry: opts.registry });
|
|
2958
|
-
} catch (err) {
|
|
2959
|
-
console.error(pc10.red("\u2716"), err.message);
|
|
2960
|
-
process.exitCode = 1;
|
|
2961
|
-
}
|
|
2962
|
-
});
|
|
2963
|
-
program.command("parity").description("Show which platforms carry each component (or one, by name).").argument("[components...]", "component names, e.g. button card \u2014 omit for the full matrix").option("-r, --registry <url>", "registry base URL", DEFAULT_REGISTRY).action(async (components, opts) => {
|
|
2964
|
-
try {
|
|
2965
|
-
await parity(components, { registry: opts.registry });
|
|
2966
|
-
} catch (err) {
|
|
2967
|
-
console.error(pc10.red("\u2716"), err.message);
|
|
2968
|
-
process.exitCode = 1;
|
|
2969
|
-
}
|
|
2970
|
-
});
|
|
2971
|
-
program.command("lint").description("Scan for hardcoded colors and spacing that should be semantic tokens.").argument("[path]", "directory to scan \u2014 defaults to the components/ui aliases from kinetixui.json").option("--no-fail", "exit 0 even if violations are found").action(async (target, opts) => {
|
|
2972
|
-
try {
|
|
2973
|
-
await lint(target, { fail: opts.fail });
|
|
2974
|
-
} catch (err) {
|
|
2975
|
-
console.error(pc10.red("\u2716"), err.message);
|
|
2976
|
-
process.exitCode = 1;
|
|
2977
|
-
}
|
|
2978
|
-
});
|
|
2979
|
-
program.command("doctor").description("Check kinetixui.json, its aliases, and the registry connection.").action(async () => {
|
|
2980
|
-
try {
|
|
2981
|
-
await doctor();
|
|
2982
|
-
} catch (err) {
|
|
2983
|
-
console.error(pc10.red("\u2716"), err.message);
|
|
2984
|
-
process.exitCode = 1;
|
|
2985
|
-
}
|
|
2986
|
-
});
|
|
2987
|
-
var theme = program.command("theme").description("Scaffold and compile a local token override (CSS only \u2014 see 'theme build --help').");
|
|
2988
|
-
theme.command("create").description("Scaffold kinetixui-themes/<name>.csv with every overridable token, commented out.").argument("<name>", "theme name, e.g. acme").option("-f, --force", "overwrite an existing theme file", false).action(async (name, opts) => {
|
|
2989
|
-
try {
|
|
2990
|
-
await themeCreate(name, { force: opts.force });
|
|
2991
|
-
} catch (err) {
|
|
2992
|
-
console.error(pc10.red("\u2716"), err.message);
|
|
2993
|
-
process.exitCode = 1;
|
|
2994
|
-
}
|
|
2995
|
-
});
|
|
2996
|
-
theme.command("build").description("Compile kinetixui-themes/<name>.csv to CSS and print a WCAG AA contrast report. This command is CSS only; native themes come from `preset swiftui|compose|flutter`.").argument("<name>", "theme name, e.g. acme").option("--no-fail-on-contrast", "exit 0 even if a pair fails WCAG AA").action(async (name, opts) => {
|
|
2997
|
-
try {
|
|
2998
|
-
await themeBuild(name, { failOnContrast: opts.failOnContrast });
|
|
2999
|
-
} catch (err) {
|
|
3000
|
-
console.error(pc10.red("\u2716"), err.message);
|
|
3001
|
-
process.exitCode = 1;
|
|
3002
|
-
}
|
|
3003
|
-
});
|
|
3004
|
-
var preset = program.command("preset").description("Read a Kinetix Create preset code (KX1_\u2026) produced by kinetixui.com/create.");
|
|
3005
|
-
preset.command("decode").description("Show what a preset contains. Accepts a KX1_ code or a full /create?preset=\u2026 URL.").argument("<preset>", "a KX1_ code, or a share URL").option("--json", "print the configuration as JSON", false).action((input, opts) => {
|
|
3006
|
-
try {
|
|
3007
|
-
presetDecode(input, { json: opts.json });
|
|
3008
|
-
} catch (err) {
|
|
3009
|
-
console.error(pc10.red("\u2716"), err.message);
|
|
3010
|
-
process.exitCode = 1;
|
|
3011
|
-
}
|
|
3012
|
-
});
|
|
3013
|
-
preset.command("css").description("Resolve a preset into its web CSS override block \u2014 the same output the workspace exports for Web CSS. This command is CSS only; see `preset swiftui|compose|flutter`.").argument("<preset>", "a KX1_ code, or a share URL").option("-o, --output <file>", "write to a file instead of stdout").action(async (input, opts) => {
|
|
3014
|
-
try {
|
|
3015
|
-
await presetCss(input, { output: opts.output });
|
|
3016
|
-
} catch (err) {
|
|
3017
|
-
console.error(pc10.red("\u2716"), err.message);
|
|
3018
|
-
process.exitCode = 1;
|
|
3019
|
-
}
|
|
3020
|
-
});
|
|
3021
|
-
preset.command("swiftui").description("Resolve a preset into a SwiftUI theme file (KinetixColors, light and dark). Colours only \u2014 SwiftUI has no radius or elevation token to carry. No Compose or Flutter output.").argument("<preset>", "a KX1_ code, or a share URL").option("-o, --output <file>", "write to a file instead of stdout").option("-n, --name <symbol>", "the Swift enum to generate", DEFAULT_SWIFT_SYMBOL).action(async (input, opts) => {
|
|
3022
|
-
try {
|
|
3023
|
-
await presetSwiftUi(input, { output: opts.output, name: opts.name });
|
|
3024
|
-
} catch (err) {
|
|
3025
|
-
console.error(pc10.red("\u2716"), err.message);
|
|
3026
|
-
process.exitCode = 1;
|
|
3027
|
-
}
|
|
3028
|
-
});
|
|
3029
|
-
preset.command("compose").description("Resolve a preset into a Jetpack Compose theme file (KinetixColors, light and dark). Colours only \u2014 Compose has no runtime radius or elevation token to carry. No Flutter or Android XML output.").argument("<preset>", "a KX1_ code, or a share URL").option("-o, --output <file>", "write to a file instead of stdout").option("-n, --name <symbol>", "the Kotlin object to generate", DEFAULT_COMPOSE_SYMBOL).action(async (input, opts) => {
|
|
3030
|
-
try {
|
|
3031
|
-
await presetCompose(input, { output: opts.output, name: opts.name });
|
|
3032
|
-
} catch (err) {
|
|
3033
|
-
console.error(pc10.red("\u2716"), err.message);
|
|
3034
|
-
process.exitCode = 1;
|
|
3035
|
-
}
|
|
3036
|
-
});
|
|
3037
|
-
preset.command("flutter").description("Resolve a preset into a Flutter theme file (KinetixColors, light and dark). Colours only \u2014 Flutter's radius and elevation are not runtime-themeable. No Android XML output.").argument("<preset>", "a KX1_ code, or a share URL").option("-o, --output <file>", "write to a file instead of stdout").option("-n, --name <symbol>", "the Dart class to generate", DEFAULT_FLUTTER_SYMBOL).action(async (input, opts) => {
|
|
3038
|
-
try {
|
|
3039
|
-
await presetFlutter(input, { output: opts.output, name: opts.name });
|
|
3040
|
-
} catch (err) {
|
|
3041
|
-
console.error(pc10.red("\u2716"), err.message);
|
|
3042
|
-
process.exitCode = 1;
|
|
3043
|
-
}
|
|
3044
|
-
});
|
|
3045
|
-
preset.command("url").description("Print the canonical share URL for a preset. Prints it \u2014 piping it to your opener is up to you.").argument("<preset>", "a KX1_ code, or a share URL").option("-s, --site <origin>", "site origin", "https://kinetixui.com").action((input, opts) => {
|
|
3046
|
-
try {
|
|
3047
|
-
presetUrlCommand(input, { site: opts.site });
|
|
3048
|
-
} catch (err) {
|
|
3049
|
-
console.error(pc10.red("\u2716"), err.message);
|
|
3050
|
-
process.exitCode = 1;
|
|
3051
|
-
}
|
|
3052
|
-
});
|
|
3053
|
-
program.parseAsync();
|
|
3212
|
+
createProgram().parseAsync();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kinetixui/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.0",
|
|
4
4
|
"description": "Add KinetixUI components and tokens to your project.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -23,10 +23,12 @@
|
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@kinetixui/create-preset": "0.0.0",
|
|
25
25
|
"@kinetixui/create-theme": "0.0.0",
|
|
26
|
-
"@types/node": "^22.20.
|
|
26
|
+
"@types/node": "^22.20.4",
|
|
27
27
|
"@types/prompts": "^2.4.9",
|
|
28
|
+
"jsdom": "^30.1.0",
|
|
28
29
|
"tsup": "^8.5.1",
|
|
29
|
-
"typescript": "^5.9.3"
|
|
30
|
+
"typescript": "^5.9.3",
|
|
31
|
+
"vitest": "^5.0.2"
|
|
30
32
|
},
|
|
31
33
|
"engines": {
|
|
32
34
|
"node": ">=18"
|
|
@@ -37,7 +39,9 @@
|
|
|
37
39
|
},
|
|
38
40
|
"scripts": {
|
|
39
41
|
"build": "tsup src/index.ts --format esm --clean",
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
+
"dev": "tsup src/index.ts --format esm --watch --onSuccess \"node dist/index.js\"",
|
|
43
|
+
"test": "vitest run",
|
|
44
|
+
"test:watch": "vitest",
|
|
45
|
+
"typecheck": "tsc --noEmit"
|
|
42
46
|
}
|
|
43
47
|
}
|