@baloise/ds-tokens 19.10.2 → 20.0.0-next.10

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.
Files changed (39) hide show
  1. package/LICENSE +11 -2
  2. package/dist/css/base.override.css +2351 -0
  3. package/dist/css/base.tokens.css +2354 -0
  4. package/dist/css/erv.override.css +2351 -0
  5. package/dist/css/erv.tokens.css +2351 -0
  6. package/dist/css/tcs.override.css +2351 -0
  7. package/dist/css/tcs.tokens.css +2351 -0
  8. package/dist/docs/base.tokens.json +80732 -0
  9. package/dist/js/base.tokens.js +1992 -0
  10. package/dist/out-tsc/config.base.js +210 -0
  11. package/dist/out-tsc/config.brand.js +92 -0
  12. package/dist/out-tsc/css-naming.js +26 -0
  13. package/dist/out-tsc/css-preview.js +8 -0
  14. package/dist/out-tsc/css-value.js +301 -0
  15. package/dist/out-tsc/formatter.js +517 -0
  16. package/dist/out-tsc/index.js +46 -0
  17. package/dist/out-tsc/transformers.js +196 -0
  18. package/dist/sass/base.tokens.scss +17 -0
  19. package/dist/web/base.tokens.json +1990 -0
  20. package/package.json +31 -9
  21. package/sbom.cdx.json +76462 -0
  22. package/README.md +0 -37
  23. package/dist/deprecated/tokens.css +0 -119
  24. package/dist/deprecated/tokens.css.scss +0 -119
  25. package/dist/deprecated/tokens.docs.json +0 -2764
  26. package/dist/deprecated/tokens.json +0 -114
  27. package/dist/deprecated/tokens.less +0 -116
  28. package/dist/deprecated/tokens.scss +0 -116
  29. package/dist/figma/color.json +0 -606
  30. package/dist/figma/size.json +0 -232
  31. package/dist/tokens.css +0 -270
  32. package/dist/tokens.css.scss +0 -270
  33. package/dist/tokens.docs.json +0 -5853
  34. package/dist/tokens.esm.js +0 -268
  35. package/dist/tokens.js +0 -270
  36. package/dist/tokens.json +0 -265
  37. package/dist/tokens.less +0 -267
  38. package/dist/tokens.scss +0 -267
  39. package/dist/types/tokens.d.ts +0 -350
@@ -0,0 +1,517 @@
1
+ import { fileHeader, formattedVariables } from 'style-dictionary/utils';
2
+ import { propertyFormatNames } from 'style-dictionary/enums';
3
+ import { RESPONSIVE_DIMENSION_EXTENSION_KEY, TYPOGRAPHY_CSS_SUFFIXES, dimensionValueToCss, } from './css-value.js';
4
+ import { tokenNameToCssVar } from './css-naming.js';
5
+ /**
6
+ * `formattedVariables` (used by both formatters below) renders exactly one CSS declaration per
7
+ * token in `dictionary.allTokens`, using that token's own `.value` as the declaration's value. A
8
+ * typography token's `.value` (post `ds/typography` transform) is a `{fontFamily, fontSize,
9
+ * fontWeight, lineHeight}` object, not a CSS-ready string — printed as-is it would render as
10
+ * `[object Object]`. This fans every `$type: "typography"` token out into 4 sibling tokens *before*
11
+ * the token list reaches `formattedVariables`, one per `TYPOGRAPHY_CSS_SUFFIXES` entry (e.g.
12
+ * `--ds-label-font-lg-font-family`, `-font-size`, `-font-weight`, `-line-height`) — matching the 4
13
+ * longhand custom properties decision 5 in docs/plans/typography-token-type-plan.md calls for, and
14
+ * the same split Toky's live preview already produces (`apps/toky/src/tokens/css-preview.ts`'s
15
+ * `pushTypographyPreviewTokens`), so the built CSS and the preview never disagree. A typography
16
+ * token whose transform produced `null` (malformed) is dropped silently rather than emitting a
17
+ * declaration with a blank value.
18
+ *
19
+ * Each field is resolved independently rather than trusting Style Dictionary's own
20
+ * `outputReferences` (which only rewrites a token's *entire* value against another token's, and
21
+ * can't rewrite one field inside a hand-built shorthand string). A field whose raw (pre-transform)
22
+ * value is a `{reference}` gets rendered as a `var(--...)`/`$...` reference via `wrapReference`;
23
+ * everything else falls back to its already-resolved literal from `css`.
24
+ *
25
+ * A field (in practice: `fontSize`) that references a *responsive* dimension token (one carrying
26
+ * `$extensions[RESPONSIVE_DIMENSION_EXTENSION_KEY]`, e.g. `Device.Text.Size.3XL`) is special-cased
27
+ * to reference that token's bare (auto-switching) var explicitly, by path, rather than trusting
28
+ * Style Dictionary's own `outputReferences` to do it — which only ever renders the referenced
29
+ * token's own bare name, but does so *before* this formatter's `-mobile`/`-tablet`/`-desktop`
30
+ * expansion runs, so nothing here can rely on it accidentally being right. The bare
31
+ * `--ds-device-text-size-3xl` var, is the auto-switching one (see `docs/plans/
32
+ * device-token-layer-plan.md` decision 5 — the layer itself being named "device" retired the old
33
+ * `-device` suffix), redefined inside this same formatter's `@media` blocks (see
34
+ * `ds/css/variables-responsive`/`-brand` below), so referencing it is what makes a typography
35
+ * token like `--ds-heading-bold-level1` responsive without itself needing a per-breakpoint
36
+ * redeclaration. CSS-only (`referenceSyntax === 'css'`): the SCSS platform never emits per-
37
+ * breakpoint vars (no media queries there), so its bare reference is left untouched.
38
+ */
39
+ function expandTypographyTokens(tokens, referenceSyntax) {
40
+ const responsiveDimensionPaths = computeResponsiveDimensionPaths(tokens);
41
+ const resolveField = (css, field, originalValue) => {
42
+ const rawField = originalValue?.[field];
43
+ const refMatch = typeof rawField === 'string' ? /^\{(.+)\}$/.exec(rawField) : null;
44
+ if (!referenceSyntax || !refMatch)
45
+ return css[field];
46
+ if (referenceSyntax === 'css' && responsiveDimensionPaths.has(refMatch[1])) {
47
+ return `var(--${tokenNameToCssVar(refMatch[1].split('.'))})`;
48
+ }
49
+ return wrapReference(refMatch[1], referenceSyntax);
50
+ };
51
+ // A whole-token reference (e.g. Modal.Heading: "{Component.Heading.Level.3}") resolves, by the
52
+ // time this runs, to a plain object copy of the target's own {fontFamily, fontSize, fontWeight,
53
+ // lineHeight} — Style Dictionary's reference resolution substitutes the target's raw $value
54
+ // before any transform sees it, so nothing here can tell "this came from aliasing a whole sibling
55
+ // typography token" apart from "this token was authored with 4 literal/referenced sub-fields"
56
+ // except by checking token.original.$value's own shape: a string (not an object) means the whole
57
+ // token was aliased. In that case each of the 4 longhand declarations should reference that
58
+ // sibling's own 4 longhands directly (e.g. `var(--ds-heading-level-3-font-family)`), not repeat
59
+ // its already-resolved literal/reference under this token's name — otherwise the alias silently
60
+ // degrades into a disconnected copy that drifts if the referenced token ever changes.
61
+ const wrapReferenceField = (path, suffix, syntax) => {
62
+ const name = `${tokenNameToCssVar(path.split('.'))}-${suffix}`;
63
+ return syntax === 'css' ? `var(--${name})` : `$${name}`;
64
+ };
65
+ const expanded = [];
66
+ for (const token of tokens) {
67
+ if (token.$type !== 'typography') {
68
+ expanded.push(token);
69
+ continue;
70
+ }
71
+ // Style Dictionary stores the post-transform result on `$value`, not `value`, once a token has
72
+ // passed through a `usesDtcg: true` pipeline (which every caller of formattedVariables below
73
+ // is) — `token.value` is `undefined` here even though `ds/typography`'s transform runs fine.
74
+ const css = (token.$value ?? token.value);
75
+ if (!css)
76
+ continue;
77
+ const rawValue = token.original?.$value;
78
+ const wholeTokenRefMatch = typeof rawValue === 'string' ? /^\{(.+)\}$/.exec(rawValue) : null;
79
+ const originalValue = rawValue;
80
+ for (const [field, suffix] of Object.entries(TYPOGRAPHY_CSS_SUFFIXES)) {
81
+ const fieldValue = wholeTokenRefMatch && referenceSyntax
82
+ ? wrapReferenceField(wholeTokenRefMatch[1], suffix, referenceSyntax)
83
+ : resolveField(css, field, originalValue);
84
+ expanded.push({
85
+ ...token,
86
+ name: `${token.name}-${suffix}`,
87
+ value: fieldValue,
88
+ $value: fieldValue,
89
+ original: { ...token.original, $value: fieldValue },
90
+ });
91
+ }
92
+ }
93
+ return expanded;
94
+ }
95
+ function isRawResponsiveDimensionValue(value) {
96
+ if (typeof value !== 'object' || value === null)
97
+ return false;
98
+ const v = value;
99
+ return 'mobile' in v && 'tablet' in v && 'desktop' in v;
100
+ }
101
+ /** DTCG paths (dot-joined, e.g. "🔗 Alias.📏 Space.MD") of every token carrying a responsive breakpoint map — shared by expandTypographyTokens (its fontSize field) and resolveResponsiveDimensionReferences (any other token's whole $value) so both agree on which reference targets need the `-device` treatment. */
102
+ function computeResponsiveDimensionPaths(tokens) {
103
+ const responsiveDimensionPaths = new Set();
104
+ for (const token of tokens) {
105
+ const extensions = token.original?.$extensions;
106
+ if (token.$type === 'dimension' &&
107
+ isRawResponsiveDimensionValue(extensions?.[RESPONSIVE_DIMENSION_EXTENSION_KEY])) {
108
+ responsiveDimensionPaths.add(token.path.join('.'));
109
+ }
110
+ }
111
+ return responsiveDimensionPaths;
112
+ }
113
+ /**
114
+ * Mirrors expandTypographyTokens's fontSize special-case (see its own comment) for every other
115
+ * token whose *whole* `$value` is a `{reference}` to a responsive dimension token — e.g. a plain
116
+ * Component.Popup.Space aliasing Device.Space.MD. Left to Style Dictionary's own
117
+ * `outputReferences`, such a token would render `var(--ds-device-space-md)` — which, post the
118
+ * device-token-layer-plan naming change, *is* already the auto-switching var, so this mainly
119
+ * exists to make that explicit and immune to `outputReferences` timing (see this function's twin
120
+ * above). Typography tokens are skipped — their 4 longhand fields already get this treatment
121
+ * per-field in expandTypographyTokens, and re-running it here on their (object-shaped) `$value`
122
+ * would be a no-op anyway. CSS-only, same reasoning as expandTypographyTokens: SCSS never emits
123
+ * per-breakpoint vars.
124
+ */
125
+ function resolveResponsiveDimensionReferences(tokens, referenceSyntax) {
126
+ if (referenceSyntax !== 'css')
127
+ return tokens;
128
+ const responsiveDimensionPaths = computeResponsiveDimensionPaths(tokens);
129
+ if (responsiveDimensionPaths.size === 0)
130
+ return tokens;
131
+ return tokens.map(token => {
132
+ if (token.$type === 'typography')
133
+ return token;
134
+ const rawValue = token.original?.$value;
135
+ const match = typeof rawValue === 'string' ? /^\{(.+)\}$/.exec(rawValue) : null;
136
+ if (!match || !responsiveDimensionPaths.has(match[1]))
137
+ return token;
138
+ const deviceValue = `var(--${tokenNameToCssVar(match[1].split('.'))})`;
139
+ return { ...token, value: deviceValue, $value: deviceValue, original: { ...token.original, $value: deviceValue } };
140
+ });
141
+ }
142
+ const wrapReference = (path, syntax) => {
143
+ const name = tokenNameToCssVar(path.split('.'));
144
+ return syntax === 'css' ? `var(--${name})` : `$${name}`;
145
+ };
146
+ /**
147
+ * Resolves one breakpoint value to a CSS/SCSS-ready string, or — on the javascript platform
148
+ * (`referenceSyntax === false`) — the referenced token's own already-transformed value as-is
149
+ * (a raw number for a unitless dimension like a Component size token, matching how that same
150
+ * token's non-responsive sibling is emitted). A literal {value, unit} converts directly
151
+ * (dimensionValueToCss); a `{reference}` string can't be resolved by Style Dictionary's
152
+ * own reference resolver — that only ever walks $value, never $extensions (see
153
+ * docs/plans/responsive-dimension-token-plan.md's "Open technical question") — so it's looked up
154
+ * here instead, against every other dimension token's own already-transformed (post `ds/dimension`)
155
+ * CSS value, keyed by its original DTCG path (e.g. "🌐 Global.📏 Dimension.Space16", matching the
156
+ * braces-stripped form of the `{reference}` string as authored in Base.tokens.json). When
157
+ * `referenceSyntax` is set (as it is on every platform that calls this, matching that platform's
158
+ * own `outputReferences`), a resolvable reference renders as a `var(--...)`/`$...` reference
159
+ * instead of its literal value — matching how Style Dictionary's own reference resolution already
160
+ * renders every *non*-responsive alias (e.g. `--ds-alias-background-color-white: var(--ds-global-
161
+ * color-white)` in CSS, `$ds-alias-background-color-white: $ds-global-color-white` in SCSS);
162
+ * `cssByPath` still gates this on the target actually existing as a real dimension declaration in
163
+ * this dictionary, so an unresolvable reference still degrades the same way it always has.
164
+ */
165
+ function resolveResponsiveBreakpointCss(raw, cssByPath, referenceSyntax) {
166
+ if (typeof raw === 'string') {
167
+ const match = /^\{(.+)\}$/.exec(raw);
168
+ if (!match)
169
+ return null;
170
+ if (!cssByPath.has(match[1]))
171
+ return null;
172
+ return referenceSyntax ? wrapReference(match[1], referenceSyntax) : (cssByPath.get(match[1]) ?? null);
173
+ }
174
+ return dimensionValueToCss(raw);
175
+ }
176
+ /**
177
+ * Mirrors expandTypographyTokens's technique (see its own comment above), fanning one responsive
178
+ * dimension token out into 3 clones (mobile/tablet/desktop) *before* the existing
179
+ * -mobile/-tablet/-desktop -> -device media-query splitting below. That splitting logic already
180
+ * operates purely on name suffixes, with no idea whether they came from the pre-existing
181
+ * sibling-token pattern or this expansion — so it needs zero code changes to also handle these
182
+ * (Phase 2's "key design win", see the plan). A malformed responsive token (an unresolvable
183
+ * breakpoint) degrades to its own plain $value/bare name instead of being dropped entirely —
184
+ * decision 4 already keeps $value mirroring mobile, so that's still a real, valid dimension value
185
+ * on its own; this is the DTCG-degradation behavior the whole $extensions design exists for.
186
+ */
187
+ function expandResponsiveDimensionTokens(tokens, referenceSyntax) {
188
+ const cssByPath = new Map();
189
+ for (const token of tokens) {
190
+ const value = token.$value ?? token.value;
191
+ if (token.$type === 'dimension' && (typeof value === 'string' || typeof value === 'number')) {
192
+ cssByPath.set(token.path.join('.'), value);
193
+ }
194
+ }
195
+ const expanded = [];
196
+ for (const token of tokens) {
197
+ const extensions = token.original?.$extensions;
198
+ const responsive = token.$type === 'dimension' ? extensions?.[RESPONSIVE_DIMENSION_EXTENSION_KEY] : undefined;
199
+ if (!isRawResponsiveDimensionValue(responsive)) {
200
+ expanded.push(token);
201
+ continue;
202
+ }
203
+ const mobile = resolveResponsiveBreakpointCss(responsive.mobile, cssByPath, referenceSyntax);
204
+ const tablet = resolveResponsiveBreakpointCss(responsive.tablet, cssByPath, referenceSyntax);
205
+ const desktop = resolveResponsiveBreakpointCss(responsive.desktop, cssByPath, referenceSyntax);
206
+ if (mobile === null || tablet === null || desktop === null) {
207
+ expanded.push(token);
208
+ continue;
209
+ }
210
+ for (const [breakpoint, cssValue] of [
211
+ ['mobile', mobile],
212
+ ['tablet', tablet],
213
+ ['desktop', desktop],
214
+ ]) {
215
+ // Each clone gets its own `original.$value: undefined` — same "don't leave a
216
+ // reference-shaped original.$value shared across clones" defensiveness expandTypographyTokens
217
+ // needed for outputReferences (see its own comment) — there's no meaningful per-breakpoint
218
+ // original sub-value to narrow to here the way typography's per-field narrowing has, so this
219
+ // just removes it instead.
220
+ expanded.push({
221
+ ...token,
222
+ name: `${token.name}-${breakpoint}`,
223
+ value: cssValue,
224
+ $value: cssValue,
225
+ original: { ...token.original, $value: undefined },
226
+ });
227
+ }
228
+ }
229
+ return expanded;
230
+ }
231
+ // Every token's `path[0]` is one of these four top-level source-tree groups, in the order Style
232
+ // Dictionary is fed the DTCG source (see `tokens/Base.tokens.json`'s top-level keys). Untouched by
233
+ // `name/kebab` or any other transform — those only ever rewrite `.name`/`.value`, never `.path` —
234
+ // so grouping by `path[0]` stays reliable no matter which format/transform pipeline ran first.
235
+ const TOKEN_ORIGIN_GROUPS = [
236
+ { pathSegment: '🌐 Global', label: 'Global tokens' },
237
+ { pathSegment: '🔗 Alias', label: 'Alias tokens' },
238
+ { pathSegment: '📱 Device', label: 'Device tokens' },
239
+ { pathSegment: '🧩 Component', label: 'Component tokens' },
240
+ ];
241
+ /**
242
+ * Thin wrapper around `formattedVariables` that partitions `dictionary.allTokens` into
243
+ * Global/Alias/Device/Component groups (see TOKEN_ORIGIN_GROUPS) and renders each through
244
+ * `formattedVariables` under its own `/* ... *\/` comment header, in that fixed order — so
245
+ * generated output always reads primitives first, then their semantic aliases, then
246
+ * component-level overrides, regardless of source JSON key order or any prior name-based
247
+ * splitting (e.g. the -mobile/-tablet/-desktop dictionaries built above). A group with no tokens
248
+ * is omitted entirely rather than emitting an empty header.
249
+ */
250
+ function formattedVariablesByOrigin(params) {
251
+ const { dictionary, indent = ' ', ...rest } = params;
252
+ return TOKEN_ORIGIN_GROUPS.map(({ pathSegment, label }) => {
253
+ const allTokens = dictionary.allTokens.filter(token => token.path[0] === pathSegment);
254
+ if (allTokens.length === 0)
255
+ return null;
256
+ const groupDictionary = { ...dictionary, allTokens };
257
+ return `${indent}/* ${label} */\n` + formattedVariables({ ...rest, dictionary: groupDictionary });
258
+ })
259
+ .filter((group) => group !== null)
260
+ .join('\n\n');
261
+ }
262
+ export const registerCustomFormatters = (sd) => {
263
+ /**
264
+ * CSS Responsive Formatter
265
+ * ------------------------------------------------------
266
+ */
267
+ sd.registerFormat({
268
+ name: 'ds/css/variables-responsive',
269
+ format: async ({ dictionary: rawDictionary, file, options }) => {
270
+ const { outputReferences } = options;
271
+ const header = await fileHeader({ file });
272
+ const dictionary = {
273
+ ...rawDictionary,
274
+ allTokens: expandResponsiveDimensionTokens(resolveResponsiveDimensionReferences(expandTypographyTokens(rawDictionary.allTokens, outputReferences ? 'css' : false), outputReferences ? 'css' : false), outputReferences ? 'css' : false),
275
+ };
276
+ // find responsive tokens in dictionary which end with -mobile, -tablet or -desktop
277
+ const baseTokensOriginal = dictionary.allTokens.filter(token => token.name.endsWith('-mobile'));
278
+ //
279
+ // Base tokens (bare name = auto-switching, per docs/plans/device-token-layer-plan.md
280
+ // decision 5 — the mobile clone becomes the unsuffixed declaration directly, there's no
281
+ // longer a separate mobile-mirrored fallback var distinct from the auto-switching one)
282
+ // ------------------------------------------------------
283
+ const baseTokens = JSON.parse(JSON.stringify(baseTokensOriginal));
284
+ baseTokens.forEach(token => {
285
+ token.name = token.name.replace('-mobile', '');
286
+ });
287
+ const baseDictionary = {
288
+ ...dictionary,
289
+ allTokens: baseTokens,
290
+ };
291
+ const tabletTokensOriginal = dictionary.allTokens.filter(token => token.name.endsWith('-tablet'));
292
+ const deviceTabletTokens = JSON.parse(JSON.stringify(tabletTokensOriginal));
293
+ deviceTabletTokens.forEach(token => {
294
+ token.name = token.name.replace('-tablet', '');
295
+ });
296
+ const deviceTabletDictionary = {
297
+ ...dictionary,
298
+ allTokens: deviceTabletTokens,
299
+ };
300
+ const desktopTokensOriginal = dictionary.allTokens.filter(token => token.name.endsWith('-desktop'));
301
+ const deviceDesktopTokens = JSON.parse(JSON.stringify(desktopTokensOriginal));
302
+ deviceDesktopTokens.forEach(token => {
303
+ token.name = token.name.replace('-desktop', '');
304
+ });
305
+ const deviceDesktopDictionary = {
306
+ ...dictionary,
307
+ allTokens: deviceDesktopTokens,
308
+ };
309
+ return (header +
310
+ ':host,\n:root {\n' +
311
+ formattedVariablesByOrigin({
312
+ format: propertyFormatNames.css,
313
+ dictionary,
314
+ outputReferences,
315
+ usesDtcg: true,
316
+ }) +
317
+ '\n\n' +
318
+ ' /* Device tokens */\n' +
319
+ formattedVariablesByOrigin({
320
+ format: propertyFormatNames.css,
321
+ dictionary: baseDictionary,
322
+ outputReferences,
323
+ usesDtcg: true,
324
+ }) +
325
+ '\n}\n\n' +
326
+ '/* Device tokens: Tablet */\n' +
327
+ `\n@media (min-width: 769px) {\n` +
328
+ ':host,\n:root {\n' +
329
+ formattedVariablesByOrigin({
330
+ format: propertyFormatNames.css,
331
+ dictionary: deviceTabletDictionary,
332
+ outputReferences,
333
+ usesDtcg: true,
334
+ }) +
335
+ `\n}` +
336
+ `\n}\n\n` +
337
+ '/* Device tokens: Desktop */\n' +
338
+ `\n@media (min-width: 1024px) {\n` +
339
+ ':host,\n:root {\n' +
340
+ formattedVariablesByOrigin({
341
+ format: propertyFormatNames.css,
342
+ dictionary: deviceDesktopDictionary,
343
+ outputReferences,
344
+ usesDtcg: true,
345
+ }) +
346
+ `\n}` +
347
+ `\n}\n` +
348
+ '\n');
349
+ },
350
+ });
351
+ /**
352
+ * CSS Brand Formatter
353
+ * ------------------------------------------------------
354
+ * Renders the FULL token set behind a caller-supplied `options.selector` — used for a brand's
355
+ * `<brand>.tokens.css` (`:host, :root`) and `<brand>.override.css`
356
+ * (`[data-theme]`/`:host([data-theme])`) files, and for Base's own `base.override.css`
357
+ * (see config.base.ts). Unlike its diff-based predecessor, the caller is expected to hand this
358
+ * a dictionary whose `allTokens` is already the complete, self-sufficient set to render — a
359
+ * brand's config.brand.ts merges the brand onto Base before this ever runs (mergeTokenTree), so
360
+ * there's no `include`/`isSource` split to filter here anymore. See
361
+ * docs/adr/0030-full-merge-brand-token-css.md.
362
+ */
363
+ sd.registerFormat({
364
+ name: 'ds/css/variables-brand',
365
+ format: async ({ dictionary: rawDictionary, file, options }) => {
366
+ const { outputReferences } = options;
367
+ const selector = options.selector ?? ':host, :root';
368
+ const header = await fileHeader({ file });
369
+ const dictionary = {
370
+ ...rawDictionary,
371
+ allTokens: expandResponsiveDimensionTokens(resolveResponsiveDimensionReferences(expandTypographyTokens(rawDictionary.allTokens, outputReferences ? 'css' : false), outputReferences ? 'css' : false), outputReferences ? 'css' : false),
372
+ };
373
+ const sourceTokens = dictionary.allTokens;
374
+ const baseTokensOriginal = sourceTokens.filter(token => token.name.endsWith('-mobile'));
375
+ const baseTokens = JSON.parse(JSON.stringify(baseTokensOriginal));
376
+ baseTokens.forEach(token => {
377
+ token.name = token.name.replace('-mobile', '');
378
+ });
379
+ const baseDictionary = { ...dictionary, allTokens: baseTokens };
380
+ const tabletTokensOriginal = sourceTokens.filter(token => token.name.endsWith('-tablet'));
381
+ const deviceTabletTokens = JSON.parse(JSON.stringify(tabletTokensOriginal));
382
+ deviceTabletTokens.forEach(token => {
383
+ token.name = token.name.replace('-tablet', '');
384
+ });
385
+ const deviceTabletDictionary = { ...dictionary, allTokens: deviceTabletTokens };
386
+ const desktopTokensOriginal = sourceTokens.filter(token => token.name.endsWith('-desktop'));
387
+ const deviceDesktopTokens = JSON.parse(JSON.stringify(desktopTokensOriginal));
388
+ deviceDesktopTokens.forEach(token => {
389
+ token.name = token.name.replace('-desktop', '');
390
+ });
391
+ const deviceDesktopDictionary = { ...dictionary, allTokens: deviceDesktopTokens };
392
+ return (header +
393
+ `${selector} {\n` +
394
+ formattedVariablesByOrigin({
395
+ format: propertyFormatNames.css,
396
+ dictionary,
397
+ outputReferences,
398
+ usesDtcg: true,
399
+ }) +
400
+ '\n\n /* Device tokens */\n' +
401
+ formattedVariablesByOrigin({
402
+ format: propertyFormatNames.css,
403
+ dictionary: baseDictionary,
404
+ outputReferences,
405
+ usesDtcg: true,
406
+ }) +
407
+ '\n}\n\n' +
408
+ '/* Device tokens: Tablet */\n' +
409
+ `\n@media (min-width: 769px) {\n` +
410
+ `${selector} {\n` +
411
+ formattedVariablesByOrigin({
412
+ format: propertyFormatNames.css,
413
+ dictionary: deviceTabletDictionary,
414
+ outputReferences,
415
+ usesDtcg: true,
416
+ }) +
417
+ `\n}` +
418
+ `\n}\n\n` +
419
+ '/* Device tokens: Desktop */\n' +
420
+ `\n@media (min-width: 1024px) {\n` +
421
+ `${selector} {\n` +
422
+ formattedVariablesByOrigin({
423
+ format: propertyFormatNames.css,
424
+ dictionary: deviceDesktopDictionary,
425
+ outputReferences,
426
+ usesDtcg: true,
427
+ }) +
428
+ `\n}` +
429
+ `\n}\n` +
430
+ '\n');
431
+ },
432
+ });
433
+ /**
434
+ * SCSS Variables Formatter
435
+ * ------------------------------------------------------
436
+ * A thin wrapper around Style Dictionary's own stock 'scss/variables' format (reproduced here,
437
+ * not imported — SD doesn't expose the stock format bodies for reuse), needed only because the
438
+ * stock format has no hook to run expandTypographyTokens first. Every other type's scss output
439
+ * is unaffected — the expand pass is a no-op for any token that isn't $type: "typography".
440
+ */
441
+ sd.registerFormat({
442
+ name: 'ds/scss/variables',
443
+ format: async ({ dictionary: rawDictionary, options, file }) => {
444
+ const { outputReferences, themeable = false, formatting, sort } = options;
445
+ const dictionary = {
446
+ ...rawDictionary,
447
+ allTokens: expandResponsiveDimensionTokens(expandTypographyTokens(rawDictionary.allTokens, outputReferences ? 'sass' : false), outputReferences ? 'sass' : false),
448
+ };
449
+ const header = await fileHeader({ file, options });
450
+ return (header +
451
+ formattedVariablesByOrigin({
452
+ format: propertyFormatNames.sass,
453
+ dictionary,
454
+ outputReferences,
455
+ themeable,
456
+ formatting,
457
+ usesDtcg: true,
458
+ sort,
459
+ indent: '',
460
+ }) +
461
+ '\n');
462
+ },
463
+ });
464
+ /**
465
+ * JSON Flat Formatter
466
+ * ------------------------------------------------------
467
+ * A thin wrapper around Style Dictionary's own stock 'json/flat' format (reproduced here, not
468
+ * imported — SD doesn't expose the stock format bodies for reuse), needed only so a responsive
469
+ * dimension token's mobile/tablet/desktop breakpoints — otherwise invisible past its
470
+ * mobile-mirrored `$value` (decision 4) — still reach this flat platform, mirroring the
471
+ * `-mobile`/`-tablet`/`-desktop` expansion the CSS/SCSS formatters above already do.
472
+ */
473
+ sd.registerFormat({
474
+ name: 'ds/json/flat',
475
+ format: async ({ dictionary: rawDictionary, options }) => {
476
+ const dictionary = {
477
+ ...rawDictionary,
478
+ allTokens: expandResponsiveDimensionTokens(rawDictionary.allTokens, false),
479
+ };
480
+ return ('{\n' +
481
+ dictionary.allTokens
482
+ .map(token => ` "${token.name}": ${JSON.stringify(options.usesDtcg ? token.$value : token.value)}`)
483
+ .join(',\n') +
484
+ '\n}\n');
485
+ },
486
+ });
487
+ /**
488
+ * JavaScript ES6 Formatter
489
+ * ------------------------------------------------------
490
+ * Same rationale as 'ds/json/flat' above, for the 'javascript/es6' stock format. Unlike the
491
+ * kebab-case names 'ds/json/flat' (and the CSS/SCSS formatters above) see, this platform's
492
+ * `ds/js/name` transform has already produced PascalCase identifiers (e.g.
493
+ * `DsAliasContainerSpace`) by the time this formatter runs — expandResponsiveDimensionTokens's
494
+ * `-mobile`/`-tablet`/`-desktop` suffix would otherwise land as an invalid `Name-mobile`
495
+ * identifier, so it's re-cased to `NameMobile` here before emitting.
496
+ */
497
+ sd.registerFormat({
498
+ name: 'ds/javascript/es6',
499
+ format: async ({ dictionary: rawDictionary, file, options }) => {
500
+ const header = await fileHeader({ file, options });
501
+ const dictionary = {
502
+ ...rawDictionary,
503
+ allTokens: expandResponsiveDimensionTokens(rawDictionary.allTokens, false).map(token => {
504
+ const match = /^(.+)-(mobile|tablet|desktop)$/.exec(token.name);
505
+ if (!match)
506
+ return token;
507
+ const [, base, breakpoint] = match;
508
+ return { ...token, name: `${base}${breakpoint[0].toUpperCase()}${breakpoint.slice(1)}` };
509
+ }),
510
+ };
511
+ const body = dictionary.allTokens
512
+ .map(token => `export const ${token.name} = ${JSON.stringify(options.usesDtcg ? token.$value : token.value)};`)
513
+ .join('\n');
514
+ return `${header}${body}\n`;
515
+ },
516
+ });
517
+ };
@@ -0,0 +1,46 @@
1
+ import StyleDictionary from 'style-dictionary';
2
+ import { copy, ensureDir, pathExists } from 'fs-extra';
3
+ import { resolve } from 'path';
4
+ console.log(`
5
+ \x1b[35m┃\x1b[0m
6
+ \x1b[35m┃\x1b[0m \x1b[1;37m🧩 Helvetia Design System\x1b[0m
7
+ \x1b[35m┃\x1b[0m \x1b[90m🎨 Building Tokens Package\x1b[0m
8
+ \x1b[35m┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m
9
+ `);
10
+ import { registerCustomTransformers } from './transformers.js';
11
+ import { registerCustomFormatters } from './formatter.js';
12
+ registerCustomTransformers(StyleDictionary);
13
+ registerCustomFormatters(StyleDictionary);
14
+ import ConfigBase from './config.base.js';
15
+ import { createBrandConfig } from './config.brand.js';
16
+ // Base build
17
+ const StyleDictionaryBase = new StyleDictionary(ConfigBase);
18
+ StyleDictionaryBase.buildAllPlatforms();
19
+ // Brand builds — add new brand names here (must match tokens/<Name>.tokens.json)
20
+ const brands = ['Tcs', 'ERV'];
21
+ for (const brand of brands) {
22
+ const { config, cleanup } = createBrandConfig(brand);
23
+ try {
24
+ const sd = new StyleDictionary(config, { verbosity: 'verbose' });
25
+ await sd.buildAllPlatforms();
26
+ }
27
+ finally {
28
+ cleanup();
29
+ }
30
+ }
31
+ // copy generated files to css folder in core assets
32
+ const projectRoot = process.cwd();
33
+ const sourceDir = resolve(projectRoot, 'dist', 'css');
34
+ const targetDir = resolve(projectRoot, '..', 'core', 'www', 'assets', 'tokens');
35
+ const storybookTargetDir = resolve(projectRoot, '..', '..', 'apps', 'storybook', 'public', 'assets', 'css');
36
+ (async () => {
37
+ await ensureDir(targetDir);
38
+ await ensureDir(storybookTargetDir);
39
+ if (await pathExists(sourceDir)) {
40
+ await copy(sourceDir, targetDir, { overwrite: true });
41
+ await copy(sourceDir, storybookTargetDir, { overwrite: true });
42
+ }
43
+ else {
44
+ console.warn(`Tokens CSS directory not found at: ${sourceDir}`);
45
+ }
46
+ })();