@apollion-dsi/tokens 5.6.0 → 5.7.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/src/ir.ts CHANGED
@@ -265,6 +265,11 @@ function buildColorPrimitives(colors: ColorsThemeInterface): IRGroup {
265
265
  }
266
266
  out.grayscale = recordColorGroup(colors.grayscale);
267
267
  out.neutral = recordColorGroup(colors.neutral);
268
+ // ADR-023 — surface ladder roles (mode-aware grounds / inks / edges / washes).
269
+ out.surface = recordColorGroup(colors.surface as unknown as Record<string, string>);
270
+ out.ink = recordColorGroup(colors.ink as unknown as Record<string, string>);
271
+ out.edge = recordColorGroup(colors.edge as unknown as Record<string, string>);
272
+ out.wash = recordColorGroup(colors.wash as unknown as Record<string, string>);
268
273
  out.baseDark = colorToken(colors.baseDark ?? '#000000');
269
274
  out.baseLight = colorToken(colors.baseLight ?? '#ffffff');
270
275
  out.deepDark = colorToken(colors.deepDark ?? '#000000');
@@ -317,6 +322,11 @@ const BG_REF: Readonly<Record<string, string>> = {
317
322
  danger: 'color.danger.base',
318
323
  warning: 'color.warning.base',
319
324
  info: 'color.information.base',
325
+ canvas: 'color.surface.canvas',
326
+ sunken: 'color.surface.sunken',
327
+ paper: 'color.surface.paper',
328
+ raised: 'color.surface.raised',
329
+ overlay: 'color.surface.overlay',
320
330
  };
321
331
 
322
332
  const TEXT_REF: Readonly<Record<string, string>> = {
@@ -327,6 +337,17 @@ const TEXT_REF: Readonly<Record<string, string>> = {
327
337
  onDanger: 'color.baseLight',
328
338
  onWarning: 'color.baseDark',
329
339
  onInfo: 'color.baseLight',
340
+ primary: 'color.ink.primary',
341
+ secondary: 'color.ink.secondary',
342
+ muted: 'color.ink.muted',
343
+ disabled: 'color.ink.disabled',
344
+ };
345
+
346
+ /** Foundation `edge.*` → `color.edge.*` primitive (ADR-023 ink-alpha lines). */
347
+ const EDGE_REF: Readonly<Record<string, string>> = {
348
+ hairline: 'color.edge.hairline',
349
+ border: 'color.edge.border',
350
+ strong: 'color.edge.strong',
330
351
  };
331
352
 
332
353
  /** Navigate the structural palette by IR path (`color.x.y`). */
@@ -691,6 +712,7 @@ export function buildColorIR(foundation: FoundationLayer, colors: ColorsThemeInt
691
712
  color,
692
713
  bg: colorRefGroup(foundation.bg as unknown as Record<string, string>, colors, BG_REF),
693
714
  text: colorRefGroup(foundation.text as unknown as Record<string, string>, colors, TEXT_REF),
715
+ edge: colorRefGroup(foundation.edge as unknown as Record<string, string>, colors, EDGE_REF),
694
716
  tx: buildReadableTextGroup(colors),
695
717
  shadow: buildShadow(colors),
696
718
  },
@@ -749,6 +771,7 @@ export function buildIR(foundation: FoundationLayer, colors: ColorsThemeInterfac
749
771
  groups: {
750
772
  bg: colorRefGroup(foundation.bg as unknown as Record<string, string>, colors, BG_REF),
751
773
  text: colorRefGroup(foundation.text as unknown as Record<string, string>, colors, TEXT_REF),
774
+ edge: colorRefGroup(foundation.edge as unknown as Record<string, string>, colors, EDGE_REF),
752
775
  tx: buildReadableTextGroup(colors),
753
776
  spacing: dimensionRefGroup(foundation.spacing as unknown as Record<string, string>, primitives, SPACE_REF),
754
777
  border: border.group,
@@ -90,3 +90,51 @@ export function renderCss(ir: IRDocument, options: RenderCssOptions = {}): strin
90
90
 
91
91
  return lines.join('\n');
92
92
  }
93
+
94
+ /** Options of {@link renderCssModes}. */
95
+ export interface RenderCssModesOptions extends RenderCssOptions {
96
+ /** `<html>` attribute carrying the resolved mode (same default as the core mode engine). */
97
+ attribute?: string;
98
+ }
99
+
100
+ /**
101
+ * Mode-scoped CSS (APOLLION-DARK-MODE-ENGINE, ADR-024 — `output.cssModes`):
102
+ * ONE file per (brand × surface × dimension) carrying BOTH modes, scoped the
103
+ * way the core mode engine scopes them — `:root`/`[attr="light"]` for light,
104
+ * `[attr="dark"]` for dark, plus a `prefers-color-scheme` fallback for pages
105
+ * that never set the attribute. Consumers without React link this file and
106
+ * flip the mode with the attribute (`getModeScript` / `syncDocumentMode`)
107
+ * — the "CSS-only switch" of the docs, made real.
108
+ */
109
+ export function renderCssModes(light: IRDocument, dark: IRDocument, options: RenderCssModesOptions = {}): string {
110
+ const { attribute = 'data-apollion-mode', rgbCompanions } = options;
111
+ const { brand, surface, dimension } = light.meta;
112
+ const lines: string[] = [HEADER_LINE, ''];
113
+
114
+ const block = (selector: string, ir: IRDocument, scheme: 'light' | 'dark'): void => {
115
+ lines.push(`${selector} {`, ` color-scheme: ${scheme};`);
116
+ emitVars(ir.groups, [], lines);
117
+ if (rgbCompanions) emitRgbVars(ir.groups, [], lines);
118
+ lines.push('}', '');
119
+ };
120
+
121
+ lines.push(
122
+ `/* Variant: brand=${brand} surface=${surface} dimension=${dimension} modes=light+dark (attribute ${attribute}) */`,
123
+ '',
124
+ );
125
+ block(`:root, html[${attribute}="light"]`, light, 'light');
126
+ block(`html[${attribute}="dark"]`, dark, 'dark');
127
+ lines.push('@media (prefers-color-scheme: dark) {');
128
+ lines.push(` html:not([${attribute}]) {`, ' color-scheme: dark;');
129
+ const inner: string[] = [];
130
+ emitVars(dark.groups, [], inner);
131
+ if (rgbCompanions) emitRgbVars(dark.groups, [], inner);
132
+ for (const line of inner) lines.push(` ${line}`);
133
+ lines.push(' }', '}', '');
134
+
135
+ lines.push('@supports (background: paint(squircle)) or (color: oklch(0% 0 0)) {');
136
+ emitProperties(light.groups, [], lines);
137
+ lines.push('}', '');
138
+
139
+ return lines.join('\n');
140
+ }
@@ -91,13 +91,13 @@ interface ColorEntry {
91
91
  }
92
92
 
93
93
  /**
94
- * Colour entries for the preset's `colors` scale: the `bg`/`text`/`tx`
94
+ * Colour entries for the preset's `colors` scale: the `bg`/`text`/`edge`/`tx`
95
95
  * foundation groups, keyed `<group>-<path>` (the CSS var name minus the
96
96
  * `--apollion-` prefix — one mental model across surfaces).
97
97
  */
98
98
  function colorEntries(ir: IRDocument): ColorEntry[] {
99
99
  const out: ColorEntry[] = [];
100
- for (const groupName of ['bg', 'text', 'tx'] as const) {
100
+ for (const groupName of ['bg', 'text', 'edge', 'tx'] as const) {
101
101
  for (const [key, token] of flatTokens(groupAt(ir.groups, groupName), [groupName])) {
102
102
  const color = colorPayload(token);
103
103
  if (!color) continue;
@@ -32,6 +32,7 @@ import { defaultInputSpacing } from '@apollion-dsi/core/themes/spacing';
32
32
  import { invertForSurface } from '@apollion-dsi/core/themes/surface';
33
33
 
34
34
  import { mountGreyscaleColors, mountNeutralColors, mountSingleColor } from './builders/colors';
35
+ import { resolveSurfaceLadder } from './builders/ladder';
35
36
  import { createSpacing } from './builders/spacing';
36
37
  import type { Variant } from './config-schema';
37
38
 
@@ -45,14 +46,17 @@ export interface VariantThemes {
45
46
  }
46
47
 
47
48
  function composeColors(input: Variant['colors']) {
49
+ const neutral = mountNeutralColors(input);
48
50
  return {
49
51
  baseDark: input.baseDark,
50
52
  baseLight: input.baseLight,
51
53
  deepDark: input.deepDark,
52
54
  deepLight: input.deepLight,
53
55
  transparent: input.transparent ?? 'transparent',
54
- neutral: mountNeutralColors(input),
56
+ neutral,
55
57
  grayscale: mountGreyscaleColors(input),
58
+ // ADR-023 — surface ladder (mirror of core's createColors composition).
59
+ ...resolveSurfaceLadder({ ...input, neutral }, input.ladder),
56
60
  main: mountSingleColor(input.main!, input),
57
61
  opposite: mountSingleColor(input.opposite!, input),
58
62
  complementary: mountSingleColor(input.complementary!, input),
package/src/token-meta.ts CHANGED
@@ -48,6 +48,15 @@ export const TOKEN_META: Readonly<Record<string, TokenMetaEntry>> = {
48
48
  deprecated: 'Use bg.information once the foundation role is renamed (tracked for S6).',
49
49
  },
50
50
 
51
+ // ── Surface ladder (ADR-023 — mode-aware grounds, inks, edges) ───────────
52
+ 'bg.canvas': { description: 'Page ground — the theme canvas (never pure black in dark).' },
53
+ 'bg.paper': { description: 'Component ground (cards, inputs, tables) — lighter than the canvas in both modes.' },
54
+ 'bg.overlay': { description: 'Top of the elevation ladder — modal / dialog panels.' },
55
+ 'text.primary': { description: 'Primary neutral ink — AA + APCA Lc ≥ 75 over bg.paper and bg.canvas.' },
56
+ 'text.muted': { description: 'Muted neutral ink — AA + APCA Lc ≥ 45 over bg.paper.' },
57
+ 'edge.border': { description: 'Default control outline — primary ink at 20 % alpha, legible on any ground.' },
58
+ 'edge.hairline': { description: 'Dividers and card outlines — primary ink at 10 % alpha.' },
59
+
51
60
  // ── Foundation text-on roles ─────────────────────────────────────────────
52
61
  'text.on-primary': { description: 'Text/icon colour with guaranteed contrast over bg.primary.' },
53
62
  'text.on-danger': { description: 'Text/icon colour with guaranteed contrast over bg.danger.' },