@apollion-dsi/tokens 4.0.0 → 4.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 +18 -9
- package/lib/build.cjs +6 -6
- package/lib/build.esm.js +6 -6
- package/lib/cli.mjs +8 -8
- package/package.json +4 -2
- package/src/build.ts +61 -25
- package/src/ir.ts +715 -0
- package/src/renderers/css.ts +36 -44
- package/src/renderers/dtcg-project.ts +108 -0
- package/src/renderers/json.ts +29 -69
- package/src/renderers/resolver.ts +129 -0
- package/src/renderers/ts.ts +31 -32
- package/src/set-plan.ts +136 -0
- package/src/theme-factory.ts +25 -10
- package/src/token-meta.ts +86 -0
package/src/theme-factory.ts
CHANGED
|
@@ -31,7 +31,14 @@ import { mountGreyscaleColors, mountNeutralColors, mountSingleColor } from './bu
|
|
|
31
31
|
import { createSpacing } from './builders/spacing';
|
|
32
32
|
import type { Variant } from './config-schema';
|
|
33
33
|
|
|
34
|
-
type ColorsThemeInterface = ReturnType<typeof composeColors>;
|
|
34
|
+
export type ColorsThemeInterface = ReturnType<typeof composeColors>;
|
|
35
|
+
|
|
36
|
+
/** A variant's resolved themes: the consumer Foundation layer + the structural
|
|
37
|
+
* palette (colour primitives / SSOT). */
|
|
38
|
+
export interface VariantThemes {
|
|
39
|
+
foundation: FoundationLayer;
|
|
40
|
+
colors: ColorsThemeInterface;
|
|
41
|
+
}
|
|
35
42
|
|
|
36
43
|
function composeColors(input: Variant['colors']) {
|
|
37
44
|
return {
|
|
@@ -56,25 +63,33 @@ function composeColors(input: Variant['colors']) {
|
|
|
56
63
|
}
|
|
57
64
|
|
|
58
65
|
/**
|
|
59
|
-
* Build the Foundation layer
|
|
66
|
+
* Build a variant's resolved themes — the Foundation layer plus the structural
|
|
67
|
+
* palette (colour SSOT consumed by the IR for reference emission). Pure +
|
|
68
|
+
* deterministic.
|
|
60
69
|
*/
|
|
61
|
-
export function
|
|
62
|
-
variant: Variant,
|
|
63
|
-
spacingInput: SpacingInput = defaultInputSpacing,
|
|
64
|
-
): FoundationLayer {
|
|
70
|
+
export function buildVariantThemes(variant: Variant, spacingInput: SpacingInput = defaultInputSpacing): VariantThemes {
|
|
65
71
|
const themeColors = composeColors(variant.colors) as unknown as ColorsThemeInterface;
|
|
66
72
|
const themeSpacing = createSpacing(spacingInput, variant.dimension);
|
|
67
73
|
const semantic = createSemantic({ colors: themeColors as never, spacing: themeSpacing as never });
|
|
68
74
|
|
|
69
|
-
|
|
75
|
+
let foundation = createFoundation(semantic);
|
|
70
76
|
|
|
71
77
|
if (variant.surface === 'negative') {
|
|
72
78
|
// invertForSurface acts on the full Theme — assemble a minimal shim.
|
|
73
79
|
// S6 will refactor to accept (semantic, surface) directly.
|
|
74
80
|
const fullTheme = { semantic, colors: themeColors as never } as never;
|
|
75
|
-
|
|
76
|
-
return createFoundation(inverted.semantic);
|
|
81
|
+
foundation = createFoundation(invertForSurface(fullTheme, 'negative').semantic);
|
|
77
82
|
}
|
|
78
83
|
|
|
79
|
-
return
|
|
84
|
+
return { foundation, colors: themeColors };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Build the Foundation layer for one variant. Pure function, deterministic.
|
|
89
|
+
*/
|
|
90
|
+
export function buildFoundationForVariant(
|
|
91
|
+
variant: Variant,
|
|
92
|
+
spacingInput: SpacingInput = defaultInputSpacing,
|
|
93
|
+
): FoundationLayer {
|
|
94
|
+
return buildVariantThemes(variant, spacingInput).foundation;
|
|
80
95
|
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token metadata registry — the SSOT for DTCG `$description` / `$deprecated`
|
|
3
|
+
* annotations (Format Module 2025.10 §"Description" + §"Deprecation").
|
|
4
|
+
*
|
|
5
|
+
* **Why a side-table (not inline on each builder):** descriptions and
|
|
6
|
+
* deprecations are editorial metadata, not derived from the foundation math.
|
|
7
|
+
* Keeping them in one keyed map lets the IR stay a pure projection of the
|
|
8
|
+
* resolved theme while `decorateMeta` (see `ir.ts`) stitches the prose on in a
|
|
9
|
+
* single post-pass — smaller diff than threading copy through every builder.
|
|
10
|
+
*
|
|
11
|
+
* **Keying:** each key is a token's dotted IR path **after** kebab-case
|
|
12
|
+
* normalization (`kebabPath`), i.e. exactly the path the JSON renderer emits in
|
|
13
|
+
* a `{...}` alias. So `bg.primary`, `border.radius.md`, `space.medium`. A dev
|
|
14
|
+
* assertion (`assertTokenMetaResolves`) fails the build/tests on any stale key
|
|
15
|
+
* that no longer maps to an IR node, so this map can never silently rot.
|
|
16
|
+
*
|
|
17
|
+
* **Deprecation values:** `true` is a bare DTCG deprecation flag; a string is a
|
|
18
|
+
* human-readable reason (DTCG allows either). Prefer the string form — it
|
|
19
|
+
* surfaces the migration path to consumers reading the JSON.
|
|
20
|
+
*
|
|
21
|
+
* @see ir.ts (`decorateMeta` post-pass + `assertTokenMetaResolves`)
|
|
22
|
+
* @see renderers/json.ts (`projectToken` emits `$description` / `$deprecated`)
|
|
23
|
+
* @see https://www.designtokens.org/TR/drafts/format/ §Description, §Deprecation
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
export interface TokenMetaEntry {
|
|
27
|
+
/** DTCG `$description` — human-readable intent of the token. */
|
|
28
|
+
readonly description?: string;
|
|
29
|
+
/** DTCG `$deprecated` — `true` (bare flag) or a string migration reason. */
|
|
30
|
+
readonly deprecated?: boolean | string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Editorial metadata keyed by kebab-normalized dotted IR path.
|
|
35
|
+
*
|
|
36
|
+
* Starter set covers the well-known foundation roles + a couple of scale
|
|
37
|
+
* anchors; extend as tokens gain documented intent. Every key MUST resolve to
|
|
38
|
+
* a live IR node (enforced by `assertTokenMetaResolves`).
|
|
39
|
+
*/
|
|
40
|
+
export const TOKEN_META: Readonly<Record<string, TokenMetaEntry>> = {
|
|
41
|
+
// ── Foundation backgrounds (semantic surface roles) ──────────────────────
|
|
42
|
+
'bg.primary': { description: 'Primary action surface — the brand’s dominant call-to-action background.' },
|
|
43
|
+
'bg.secondary': { description: 'Secondary action surface — supporting actions paired with the primary.' },
|
|
44
|
+
'bg.danger': { description: 'Destructive / error surface — irreversible or failing actions.' },
|
|
45
|
+
'bg.success': { description: 'Positive confirmation surface — completed or healthy states.' },
|
|
46
|
+
'bg.info': {
|
|
47
|
+
description: 'Informational surface — neutral, non-blocking notices.',
|
|
48
|
+
deprecated: 'Use bg.information once the foundation role is renamed (tracked for S6).',
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
// ── Foundation text-on roles ─────────────────────────────────────────────
|
|
52
|
+
'text.on-primary': { description: 'Text/icon colour with guaranteed contrast over bg.primary.' },
|
|
53
|
+
'text.on-danger': { description: 'Text/icon colour with guaranteed contrast over bg.danger.' },
|
|
54
|
+
|
|
55
|
+
// ── Spacing primitives (density-aware dimension scale) ────────────────────
|
|
56
|
+
'space.medium': { description: 'Base spacing step (1rem at normal density) — the layout rhythm unit.' },
|
|
57
|
+
'space.large': { description: 'Comfortable spacing step — section padding and card gutters.' },
|
|
58
|
+
|
|
59
|
+
// ── Spacing foundation aliases ───────────────────────────────────────────
|
|
60
|
+
'spacing.md': { description: 'Medium foundation spacing alias — references the space.medium primitive.' },
|
|
61
|
+
'spacing.lg': { description: 'Large foundation spacing alias — references the space.large primitive.' },
|
|
62
|
+
|
|
63
|
+
// ── Border scale ─────────────────────────────────────────────────────────
|
|
64
|
+
'border.radius.md': { description: 'Default card corner radius.' },
|
|
65
|
+
'border.width.thin': { description: 'Hairline border — dividers and default input outlines.' },
|
|
66
|
+
'border.width.regular': { description: 'Standard emphasis border — focused/active controls.' },
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Dev assertion: every {@link TOKEN_META} key must map to a live IR node.
|
|
71
|
+
*
|
|
72
|
+
* `resolvedPaths` is the set of kebab-normalized dotted paths the IR currently
|
|
73
|
+
* emits (built by `decorateMeta`'s walk). Throws listing every stale key so a
|
|
74
|
+
* rename in the foundation can never leave dangling documentation behind.
|
|
75
|
+
*
|
|
76
|
+
* @throws if any `TOKEN_META` key is absent from `resolvedPaths`.
|
|
77
|
+
*/
|
|
78
|
+
export function assertTokenMetaResolves(resolvedPaths: ReadonlySet<string>): void {
|
|
79
|
+
const stale = Object.keys(TOKEN_META).filter((key) => !resolvedPaths.has(key));
|
|
80
|
+
if (stale.length > 0) {
|
|
81
|
+
throw new Error(
|
|
82
|
+
`token-meta: ${stale.length} stale TOKEN_META key(s) resolve to no IR node: ${stale.join(', ')}. ` +
|
|
83
|
+
`Update src/token-meta.ts to match the current IR paths.`,
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
}
|