@evinvest/uikit 0.18.0 → 0.19.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.
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ PaletteError,
4
+ brandFromToml,
5
+ readContract,
6
+ renderPalette
7
+ } from "./chunk-3IPB4QQJ.js";
8
+
9
+ // src/palette/cli.ts
10
+ import { readFileSync, writeFileSync } from "fs";
11
+ import { basename } from "path";
12
+ import { parseArgs } from "util";
13
+ var USAGE = `usage: evinvest-palette --brand <slug> [--out <file.css>] <brand.toml>
14
+
15
+ Writes the brand's palette as a flat sheet scoped to [data-brand="<slug>"],
16
+ after checking both polarities against the contract in @evinvest/uikit's
17
+ styles/theme.css. Prints to stdout without --out.`;
18
+ function main() {
19
+ const { values, positionals } = parseArgs({
20
+ options: { brand: { type: "string" }, out: { type: "string" }, help: { type: "boolean", short: "h" } },
21
+ allowPositionals: true
22
+ });
23
+ const [input] = positionals;
24
+ if (values.help || !values.brand || !input || positionals.length > 1) {
25
+ console.error(USAGE);
26
+ return values.help ? 0 : 2;
27
+ }
28
+ const contract = readContract(readFileSync(new URL("../styles/theme.css", import.meta.url), "utf8"));
29
+ const sheet = renderPalette(values.brand, brandFromToml(readFileSync(input, "utf8")), contract, basename(input));
30
+ if (values.out) writeFileSync(values.out, sheet);
31
+ else process.stdout.write(sheet);
32
+ return 0;
33
+ }
34
+ try {
35
+ process.exitCode = main();
36
+ } catch (error) {
37
+ if (error instanceof PaletteError) console.error(error.message);
38
+ else throw error;
39
+ process.exitCode = 1;
40
+ }
41
+ //# sourceMappingURL=palette-cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/palette/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { readFileSync, writeFileSync } from \"node:fs\";\nimport { basename } from \"node:path\";\nimport { parseArgs } from \"node:util\";\n\nimport { brandFromToml, PaletteError, renderPalette } from \"./brand\";\nimport { readContract } from \"./contract\";\n\nconst USAGE = `usage: evinvest-palette --brand <slug> [--out <file.css>] <brand.toml>\n\nWrites the brand's palette as a flat sheet scoped to [data-brand=\"<slug>\"],\nafter checking both polarities against the contract in @evinvest/uikit's\nstyles/theme.css. Prints to stdout without --out.`;\n\nfunction main(): number {\n const { values, positionals } = parseArgs({\n options: { brand: { type: \"string\" }, out: { type: \"string\" }, help: { type: \"boolean\", short: \"h\" } },\n allowPositionals: true,\n });\n const [input] = positionals;\n if (values.help || !values.brand || !input || positionals.length > 1) {\n console.error(USAGE);\n return values.help ? 0 : 2;\n }\n\n // Shipped beside `dist/`, so the contract is always the installed kit's own.\n const contract = readContract(readFileSync(new URL(\"../styles/theme.css\", import.meta.url), \"utf8\"));\n const sheet = renderPalette(values.brand, brandFromToml(readFileSync(input, \"utf8\")), contract, basename(input));\n if (values.out) writeFileSync(values.out, sheet);\n else process.stdout.write(sheet);\n return 0;\n}\n\ntry {\n process.exitCode = main();\n} catch (error) {\n // A bad brand file is the user's input, not a crash: the message, no stack.\n if (error instanceof PaletteError) console.error(error.message);\n else throw error;\n process.exitCode = 1;\n}\n"],"mappings":";;;;;;;;;AACA,SAAS,cAAc,qBAAqB;AAC5C,SAAS,gBAAgB;AACzB,SAAS,iBAAiB;AAK1B,IAAM,QAAQ;AAAA;AAAA;AAAA;AAAA;AAMd,SAAS,OAAe;AACtB,QAAM,EAAE,QAAQ,YAAY,IAAI,UAAU;AAAA,IACxC,SAAS,EAAE,OAAO,EAAE,MAAM,SAAS,GAAG,KAAK,EAAE,MAAM,SAAS,GAAG,MAAM,EAAE,MAAM,WAAW,OAAO,IAAI,EAAE;AAAA,IACrG,kBAAkB;AAAA,EACpB,CAAC;AACD,QAAM,CAAC,KAAK,IAAI;AAChB,MAAI,OAAO,QAAQ,CAAC,OAAO,SAAS,CAAC,SAAS,YAAY,SAAS,GAAG;AACpE,YAAQ,MAAM,KAAK;AACnB,WAAO,OAAO,OAAO,IAAI;AAAA,EAC3B;AAGA,QAAM,WAAW,aAAa,aAAa,IAAI,IAAI,uBAAuB,YAAY,GAAG,GAAG,MAAM,CAAC;AACnG,QAAM,QAAQ,cAAc,OAAO,OAAO,cAAc,aAAa,OAAO,MAAM,CAAC,GAAG,UAAU,SAAS,KAAK,CAAC;AAC/G,MAAI,OAAO,IAAK,eAAc,OAAO,KAAK,KAAK;AAAA,MAC1C,SAAQ,OAAO,MAAM,KAAK;AAC/B,SAAO;AACT;AAEA,IAAI;AACF,UAAQ,WAAW,KAAK;AAC1B,SAAS,OAAO;AAEd,MAAI,iBAAiB,aAAc,SAAQ,MAAM,MAAM,OAAO;AAAA,MACzD,OAAM;AACX,UAAQ,WAAW;AACrB;","names":[]}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * The colour contract, read out of the kit's own token sheet rather than
3
+ * restated here: `@theme inline` maps every name the class tables can paint
4
+ * (`--color-X: var(--X)`), and the derived rule lists the ones the contract
5
+ * computes from the rest. A palette owes the difference.
6
+ */
7
+ interface Contract {
8
+ /** Names every palette scope must declare. */
9
+ required: readonly string[];
10
+ /** Names the contract derives from the palette; a palette may override them. */
11
+ derived: readonly string[];
12
+ /** The tokens each derived default reads, so a palette's references can be checked for cycles through them. */
13
+ derivedFrom: Readonly<Record<string, readonly string[]>>;
14
+ }
15
+ /** The rule the derived tokens live on — see `theme.css`, the contract half of `tokens.css`. */
16
+ declare const DERIVED_SCOPE = ":where(:root, .dark, .light, [data-brand])";
17
+ interface CssRule {
18
+ selector: string;
19
+ /** Custom properties declared directly in the rule, in source order. */
20
+ declarations: Map<string, string>;
21
+ }
22
+ /**
23
+ * Top-level and `@media`-nested rules of a flat sheet, comments stripped. Only
24
+ * as much CSS as a token sheet is made of: no strings containing braces.
25
+ */
26
+ declare function readRules(css: string): CssRule[];
27
+ declare function readContract(css: string): Contract;
28
+
29
+ type PolarityName = "light" | "dark";
30
+ declare const POLARITIES: readonly PolarityName[];
31
+ /** A brand's palette: one value per contract name, for each polarity. */
32
+ interface BrandConfig {
33
+ colors: Record<PolarityName, Record<string, string>>;
34
+ /** `--brand-mark`: the URL `<Logo>` masks. */
35
+ mark?: string;
36
+ /** `--brand-aspect`: the mark's width / height, e.g. `"387 / 335"`. */
37
+ markAspect?: string;
38
+ /** `--brand-font-<role>`: a family name per role; unset roles keep the kit's. */
39
+ fonts?: Partial<Record<FontRole, string>>;
40
+ }
41
+ type FontRole = "sans" | "display" | "mono";
42
+ declare const FONT_ROLES: readonly FontRole[];
43
+ /** Every problem with a brand file at once, so one run fixes them all. */
44
+ declare class PaletteError extends Error {
45
+ readonly problems: readonly string[];
46
+ constructor(problems: readonly string[]);
47
+ }
48
+ /**
49
+ * Reads the aquafix `brand.toml` shape: `[colors.light]` and `[colors.dark]`
50
+ * keyed by contract name, optional top-level `mark` / `mark-aspect`, and the
51
+ * family names in `[fonts]` (`sans` or `text`, `display`, `mono`). The rest of
52
+ * `[fonts]` and every other table (`[print]`, …) belong to other tools and are
53
+ * ignored — loading the font files is the site's job, not the palette's.
54
+ */
55
+ declare function brandFromToml(text: string): BrandConfig;
56
+ /** The problems with `config` against `contract`; empty when it is complete. */
57
+ declare function validateBrand(config: BrandConfig, contract: Contract): string[];
58
+ /**
59
+ * A flat sheet scoping `config` under `[data-brand="<brand>"]`: light is the
60
+ * brand's default and `.light` / `.dark` beneath it pick a polarity. Selectors
61
+ * outrank the zero-specificity EV palette, so import order does not matter.
62
+ * Throws a {@link PaletteError} when a scope is incomplete — Tailwind answers an
63
+ * undefined token with no rule at all, so a hole would be silent on the page.
64
+ */
65
+ declare function renderPalette(brand: string, config: BrandConfig, contract: Contract, source?: string): string;
66
+
67
+ /**
68
+ * The subset of TOML a brand file is written in: `[table.headers]`, bare keys,
69
+ * and single-line values that are a basic string, a number, a boolean, or a
70
+ * flat array of those. Anything else is an error naming the line, rather than a
71
+ * silent misread — the kit stays dependency-free, and a brand file has no use
72
+ * for the rest of the language.
73
+ */
74
+ type TomlValue = string | number | boolean | TomlValue[];
75
+ interface TomlTable {
76
+ [key: string]: TomlValue | TomlTable;
77
+ }
78
+ declare function parseToml(text: string): TomlTable;
79
+
80
+ export { type BrandConfig, type Contract, type CssRule, DERIVED_SCOPE, FONT_ROLES, type FontRole, POLARITIES, PaletteError, type PolarityName, type TomlTable, type TomlValue, brandFromToml, parseToml, readContract, readRules, renderPalette, validateBrand };
@@ -0,0 +1,25 @@
1
+ import {
2
+ DERIVED_SCOPE,
3
+ FONT_ROLES,
4
+ POLARITIES,
5
+ PaletteError,
6
+ brandFromToml,
7
+ parseToml,
8
+ readContract,
9
+ readRules,
10
+ renderPalette,
11
+ validateBrand
12
+ } from "./chunk-3IPB4QQJ.js";
13
+ export {
14
+ DERIVED_SCOPE,
15
+ FONT_ROLES,
16
+ POLARITIES,
17
+ PaletteError,
18
+ brandFromToml,
19
+ parseToml,
20
+ readContract,
21
+ readRules,
22
+ renderPalette,
23
+ validateBrand
24
+ };
25
+ //# sourceMappingURL=palette.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evinvest/uikit",
3
- "version": "0.18.0",
3
+ "version": "0.19.0",
4
4
  "description": "Dep-light React UI kit: shadcn-semantics components with zero @radix-ui / cva, mirroring the ev_lib::uikit Rust feature. Ships the shared design tokens.",
5
5
  "license": "BlueOak-1.0.0",
6
6
  "publishConfig": {
@@ -17,10 +17,19 @@
17
17
  "types": "./dist/index.d.ts",
18
18
  "import": "./dist/index.js"
19
19
  },
20
- "./styles/tokens.css": "./styles/tokens.css"
20
+ "./palette": {
21
+ "types": "./dist/palette.d.ts",
22
+ "import": "./dist/palette.js"
23
+ },
24
+ "./styles/tokens.css": "./styles/tokens.css",
25
+ "./styles/theme.css": "./styles/theme.css",
26
+ "./styles/ev.css": "./styles/ev.css"
21
27
  },
22
28
  "types": "./dist/index.d.ts",
23
29
  "main": "./dist/index.js",
30
+ "bin": {
31
+ "evinvest-palette": "./dist/palette-cli.js"
32
+ },
24
33
  "files": [
25
34
  "dist",
26
35
  "styles"
@@ -32,10 +41,10 @@
32
41
  "node": ">=20"
33
42
  },
34
43
  "scripts": {
35
- "build": "tsup",
44
+ "build": "rm -rf dist && tsup",
36
45
  "test": "vitest run",
37
46
  "typecheck": "tsc --noEmit",
38
- "prepare": "tsup",
47
+ "prepare": "rm -rf dist && tsup",
39
48
  "prepublishOnly": "node scripts/verify-pack.mjs"
40
49
  },
41
50
  "peerDependencies": {
package/styles/ev.css ADDED
@@ -0,0 +1,73 @@
1
+ /* AUTO-GENERATED by `nix run .#gen` from the repo-root ev.css — do not edit. */
2
+ /* ═══ PALETTE: EV ════════════════════════════════════════════════
3
+ *
4
+ * Zero specificity: EV is the default a brand scope overrides, whatever order
5
+ * the two sheets are imported in.
6
+ *
7
+ * Surface ladder — dark UI reads as unfinished when every plane is the same
8
+ * value, so the steps below are deliberate and measured; keep them apart if you
9
+ * retune. background #070d18 → secondary #0d1526 (1.07:1) → card #16233c (1.24:1
10
+ * vs background) → popover #1d2c49 (1.13:1 vs card) → muted #26375c. Elevation
11
+ * rides the same ladder via --shadow-card / -elevated / -overlay. Card is also
12
+ * pinned from above by a second floor: --primary has to clear 3:1 against it
13
+ * (3.39:1 today), so lifting card one step breaks the tokens test.
14
+ */
15
+ :where(:root, [data-brand="ev"]) {
16
+ /* the polarity, for what the platform draws itself (a native <select>'s
17
+ * popup); a custom property so nothing but the kit's opt-ins reads it */
18
+ --scheme: dark;
19
+
20
+ /* ── surfaces ────────────────────────────────────────────────── */
21
+ --background: #070d18;
22
+ --card: #16233c;
23
+ --popover: #1d2c49;
24
+ --muted: #26375c;
25
+
26
+ /* ── ink ─────────────────────────────────────────────────────── */
27
+ --ink: #e6e1d3;
28
+
29
+ /* ── roles ───────────────────────────────────────────────────── */
30
+ --brand: #001e4e;
31
+ /* teal, not the brand navy: navy filled a Button/Switch at ~1.1:1 against
32
+ * these surfaces, i.e. an invisible control, and consumers hardcoded teal to
33
+ * get around it. The brand navy still lives in --brand.
34
+ *
35
+ * A fill and an ink are different jobs, so the primary role carries two
36
+ * values (docs/spec/accents.md). --primary-ink is the teal that reads on the
37
+ * surfaces: 5.85:1 on background, 4.72:1 on card. --primary is the same hue
38
+ * and chroma one step down in lightness — OKLCH L 0.550 C 0.094 H 183°
39
+ * against the ink's L 0.630 C 0.101 H 183° — so the CTA carries white:
40
+ * --on-primary reads on it at 4.63:1, and the fill clears the 3:1 non-text
41
+ * floor against background (4.20:1), secondary (3.94:1), card (3.39:1) and
42
+ * popover (3.01:1), so a checked Switch on a Card still reads as one. Muted
43
+ * is the one surface under the floor (2.54:1); the controls that fill there
44
+ * carry an on-primary mark, see the spec. Darkening the single value instead
45
+ * would have dropped every teal label below AA. */
46
+ --primary: #128377;
47
+ --on-primary: #ffffff;
48
+ --primary-ink: #2a9d8f;
49
+ --secondary: #0d1526;
50
+ --on-secondary: var(--ink);
51
+ /* valence, not significance: a success reads as good while staying quiet */
52
+ --positive: #2e9e5b;
53
+ --on-positive: #070d18;
54
+ --accent-trace: #6b7fa0;
55
+ --on-accent-trace: #070d18;
56
+ --accent-debug: #2a9d8f;
57
+ --on-accent-debug: #070d18;
58
+ --accent-info: #4a90d9;
59
+ --on-accent-info: #070d18;
60
+ --accent-warn: #f2c94c;
61
+ --on-accent-warn: #070d18;
62
+ /* lifted off the brand red #da251d, which failed AA on a card. This one value
63
+ * carries both the fill and the text; the vivid brand red is a gitops
64
+ * constant, not a UI token. */
65
+ --accent-error: #ef5b52;
66
+ --on-accent-error: #070d18;
67
+
68
+ --chart-1: #2a9d8f;
69
+ --chart-2: #2e9e5b;
70
+ --chart-3: #f2c94c;
71
+ --chart-4: #e58aae;
72
+ --chart-5: #4a90d9;
73
+ }