@apollion-dsi/tokens 4.0.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,81 @@
1
+ /**
2
+ * JSON renderer — emit Foundation tokens as DTCG-compliant JSON.
3
+ *
4
+ * **Spec:** [Design Tokens Community Group Format Module](https://design-tokens.github.io/community-group/format/)
5
+ * (W3C draft track, mid-2024).
6
+ *
7
+ * Each token is a leaf with `$value` + `$type`. Groups nest naturally:
8
+ * ```json
9
+ * { "bg": { "primary": { "$value": "#003750", "$type": "color" } } }
10
+ * ```
11
+ *
12
+ * Enables interop with Tokens Studio (Figma), Style Dictionary downstream,
13
+ * Specify, and any tool consuming the DTCG spec.
14
+ *
15
+ * @see tech-radar R1 (DTCG compliance)
16
+ * @see ADR-006 §3.10 + PRD-002 §S5
17
+ */
18
+
19
+ import type { FoundationLayer } from '@apollion-dsi/core/themes/foundation';
20
+
21
+ import type { Variant } from '../config-schema';
22
+
23
+ interface DtcgToken<T extends string = string> {
24
+ $value: string;
25
+ $type: T;
26
+ }
27
+
28
+ interface DtcgGroup {
29
+ [key: string]: DtcgToken | DtcgGroup;
30
+ }
31
+
32
+ export interface DtcgDocument {
33
+ $description: string;
34
+ bg: DtcgGroup;
35
+ text: DtcgGroup;
36
+ spacing: DtcgGroup;
37
+ /** Variant metadata — non-standard extension under DTCG `$extensions` namespace. */
38
+ $extensions: {
39
+ 'com.apollion.variant': {
40
+ brand: string;
41
+ mode: string;
42
+ surface: string;
43
+ dimension: string;
44
+ };
45
+ };
46
+ }
47
+
48
+ function toColorGroup(layer: Record<string, string>): DtcgGroup {
49
+ const out: DtcgGroup = {};
50
+ for (const [key, value] of Object.entries(layer)) {
51
+ const kebab = key.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
52
+ out[kebab] = { $value: value, $type: 'color' };
53
+ }
54
+ return out;
55
+ }
56
+
57
+ function toDimensionGroup(layer: Record<string, string>): DtcgGroup {
58
+ const out: DtcgGroup = {};
59
+ for (const [key, value] of Object.entries(layer)) {
60
+ out[key] = { $value: value, $type: 'dimension' };
61
+ }
62
+ return out;
63
+ }
64
+
65
+ export function renderJson(foundation: FoundationLayer, variant: Variant): string {
66
+ const doc: DtcgDocument = {
67
+ $description: `Apollion DS tokens — DTCG v1. Variant: ${variant.brand}/${variant.mode}/${variant.surface}/${variant.dimension}.`,
68
+ bg: toColorGroup(foundation.bg as unknown as Record<string, string>),
69
+ text: toColorGroup(foundation.text as unknown as Record<string, string>),
70
+ spacing: toDimensionGroup(foundation.spacing as unknown as Record<string, string>),
71
+ $extensions: {
72
+ 'com.apollion.variant': {
73
+ brand: variant.brand,
74
+ mode: variant.mode,
75
+ surface: variant.surface,
76
+ dimension: variant.dimension,
77
+ },
78
+ },
79
+ };
80
+ return `${JSON.stringify(doc, null, 2)}\n`;
81
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * TypeScript renderer — emit Foundation tokens as `as const` literal.
3
+ *
4
+ * **tech-radar R4:** consumer importing the generated `.d.ts` gets literal
5
+ * types (e.g. `tokens.bg.primary: 'oklch(...)'` not `string`) — autocomplete
6
+ * + compile-time invariant checks.
7
+ *
8
+ * @see tech-radar R4
9
+ * @see ADR-006 §3.10 + PRD-002 §S5
10
+ */
11
+
12
+ import type { FoundationLayer } from '@apollion-dsi/core/themes/foundation';
13
+
14
+ import type { Variant } from '../config-schema';
15
+
16
+ const HEADER_LINE = '// Generated by apollion-tokens build — DO NOT EDIT. See apollion.config.mjs.';
17
+
18
+ function serializeRecord(record: Record<string, string>, indent: number): string {
19
+ const pad = ' '.repeat(indent);
20
+ const entries = Object.entries(record).map(
21
+ ([key, value]) => `${pad}${JSON.stringify(key)}: ${JSON.stringify(value)},`,
22
+ );
23
+ return entries.join('\n');
24
+ }
25
+
26
+ export function renderTs(foundation: FoundationLayer, variant: Variant): string {
27
+ return [
28
+ HEADER_LINE,
29
+ `// Variant: brand=${variant.brand} mode=${variant.mode} surface=${variant.surface} dimension=${variant.dimension}`,
30
+ '',
31
+ 'export const tokens = {',
32
+ ' bg: {',
33
+ serializeRecord(foundation.bg as unknown as Record<string, string>, 4),
34
+ ' },',
35
+ ' text: {',
36
+ serializeRecord(foundation.text as unknown as Record<string, string>, 4),
37
+ ' },',
38
+ ' spacing: {',
39
+ serializeRecord(foundation.spacing as unknown as Record<string, string>, 4),
40
+ ' },',
41
+ '} as const;',
42
+ '',
43
+ 'export type Tokens = typeof tokens;',
44
+ '',
45
+ ].join('\n');
46
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * `@apollion-dsi/tokens/runtime/v1` — Versioned API surface for v4+.
3
+ *
4
+ * **Status:** S4 Strangler Fig builders (ADR-006 §6 S4).
5
+ *
6
+ * This is the ONLY public entry of `@apollion-dsi/tokens`. Bumping to `/v2`
7
+ * adds new exports without breaking `/v1` (versioned API decoupling per
8
+ * ADR-006 §3.3). Consumers import:
9
+ *
10
+ * ```ts
11
+ * import {
12
+ * mountSingleColor, mountGreyscaleColors, mountNeutralColors, getOppositeColor,
13
+ * createSpacing,
14
+ * createFoundation, invertForSurface,
15
+ * TOKENS_RUNTIME_API_VERSION,
16
+ * } from '@apollion-dsi/tokens/runtime/v1';
17
+ * ```
18
+ *
19
+ * **Strangler Fig:** `colors` + `spacing` builders are DUPLICATES of core's
20
+ * implementation (validated bit-equivalent via parity tests). `foundation`
21
+ * + `surface` are re-exports from core via stable subpaths. Both classes
22
+ * coexist orthogonally — core remains SSOT until PRD-003 post-v4
23
+ * consolidation.
24
+ *
25
+ * @see ADR-006 §3.3 (versioned API + decoupling B2 mitigation)
26
+ * @see ADR-006 §6 S1 + §6 S4
27
+ * @see PRD-002 §S4
28
+ */
29
+
30
+ export const TOKENS_RUNTIME_API_VERSION = 'v1' as const;
31
+
32
+ // Strangler Fig duplicates (full implementation in tokens)
33
+ export { getOppositeColor, mountGreyscaleColors, mountNeutralColors, mountSingleColor } from '../builders/colors';
34
+ export { createSpacing } from '../builders/spacing';
35
+
36
+ // Interim re-exports from core (consolidated in PRD-003 if needed)
37
+ export { createFoundation } from '../builders/foundation';
38
+ export type { FoundationLayer } from '../builders/foundation';
39
+ export { invertForSurface } from '../builders/surface';
40
+ export type { SurfaceMode } from '../builders/surface';
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Build a `FoundationLayer` for a given `Variant` — the unit consumed by
3
+ * all renderers (CSS, JSON, TS).
4
+ *
5
+ * Pipeline:
6
+ * 1. Tokens builders → ColorsThemeInterface (OKLch lerp via culori).
7
+ * 2. Tokens builders → SpacingThemeInterface (Dimension override applied).
8
+ * 3. Core `createSemantic` (re-exported via tokens/builders/foundation
9
+ * indirectly).
10
+ * 4. Core `createFoundation` → FoundationLayer.
11
+ * 5. If surface === 'negative': core `invertForSurface` applied.
12
+ *
13
+ * **Mode handling (S5 limitation):** `mode` is currently a no-op placeholder
14
+ * — palette generation does not differ per mode. ADR-006 E6 (in S6) wires
15
+ * `themeMode` resolution into surface inversion. For S5, mode appears only
16
+ * in the variant filename so the output topology is ready when S6 plugs in
17
+ * real dark-mode logic.
18
+ *
19
+ * @see ADR-006 §3.6 (Surface-within-mode E6 — pending in S6)
20
+ * @see PRD-002 §S5
21
+ */
22
+
23
+ import type { FoundationLayer } from '@apollion-dsi/core/themes/foundation';
24
+ import { createFoundation } from '@apollion-dsi/core/themes/foundation';
25
+ import { createSemantic } from '@apollion-dsi/core/themes/semantic';
26
+ import type { SpacingInput } from '@apollion-dsi/core/themes/spacing';
27
+ import { defaultInputSpacing } from '@apollion-dsi/core/themes/spacing';
28
+ import { invertForSurface } from '@apollion-dsi/core/themes/surface';
29
+
30
+ import { mountGreyscaleColors, mountNeutralColors, mountSingleColor } from './builders/colors';
31
+ import { createSpacing } from './builders/spacing';
32
+ import type { Variant } from './config-schema';
33
+
34
+ type ColorsThemeInterface = ReturnType<typeof composeColors>;
35
+
36
+ function composeColors(input: Variant['colors']) {
37
+ return {
38
+ baseDark: input.baseDark,
39
+ baseLight: input.baseLight,
40
+ deepDark: input.deepDark,
41
+ deepLight: input.deepLight,
42
+ transparent: input.transparent ?? 'transparent',
43
+ neutral: mountNeutralColors(input),
44
+ grayscale: mountGreyscaleColors(input),
45
+ main: mountSingleColor(input.main!, input),
46
+ opposite: mountSingleColor(input.opposite!, input),
47
+ complementary: mountSingleColor(input.complementary!, input),
48
+ danger: mountSingleColor(input.danger!, input),
49
+ information: mountSingleColor(input.information!, input),
50
+ success: mountSingleColor(input.success!, input),
51
+ warning: mountSingleColor(input.warning!, input),
52
+ primary: mountSingleColor(input.primary!, input),
53
+ secondary: mountSingleColor(input.secondary!, input),
54
+ tertiary: mountSingleColor(input.tertiary!, input),
55
+ };
56
+ }
57
+
58
+ /**
59
+ * Build the Foundation layer for one variant. Pure function, deterministic.
60
+ */
61
+ export function buildFoundationForVariant(
62
+ variant: Variant,
63
+ spacingInput: SpacingInput = defaultInputSpacing,
64
+ ): FoundationLayer {
65
+ const themeColors = composeColors(variant.colors) as unknown as ColorsThemeInterface;
66
+ const themeSpacing = createSpacing(spacingInput, variant.dimension);
67
+ const semantic = createSemantic({ colors: themeColors as never, spacing: themeSpacing as never });
68
+
69
+ const baseFoundation = createFoundation(semantic);
70
+
71
+ if (variant.surface === 'negative') {
72
+ // invertForSurface acts on the full Theme — assemble a minimal shim.
73
+ // S6 will refactor to accept (semantic, surface) directly.
74
+ const fullTheme = { semantic, colors: themeColors as never } as never;
75
+ const inverted = invertForSurface(fullTheme, 'negative');
76
+ return createFoundation(inverted.semantic);
77
+ }
78
+
79
+ return baseFoundation;
80
+ }