@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.
- package/LICENSE +11 -2
- package/dist/css/base.override.css +2351 -0
- package/dist/css/base.tokens.css +2354 -0
- package/dist/css/erv.override.css +2351 -0
- package/dist/css/erv.tokens.css +2351 -0
- package/dist/css/tcs.override.css +2351 -0
- package/dist/css/tcs.tokens.css +2351 -0
- package/dist/docs/base.tokens.json +80732 -0
- package/dist/js/base.tokens.js +1992 -0
- package/dist/out-tsc/config.base.js +210 -0
- package/dist/out-tsc/config.brand.js +92 -0
- package/dist/out-tsc/css-naming.js +26 -0
- package/dist/out-tsc/css-preview.js +8 -0
- package/dist/out-tsc/css-value.js +301 -0
- package/dist/out-tsc/formatter.js +517 -0
- package/dist/out-tsc/index.js +46 -0
- package/dist/out-tsc/transformers.js +196 -0
- package/dist/sass/base.tokens.scss +17 -0
- package/dist/web/base.tokens.json +1990 -0
- package/package.json +31 -9
- package/sbom.cdx.json +76462 -0
- package/README.md +0 -37
- package/dist/deprecated/tokens.css +0 -119
- package/dist/deprecated/tokens.css.scss +0 -119
- package/dist/deprecated/tokens.docs.json +0 -2764
- package/dist/deprecated/tokens.json +0 -114
- package/dist/deprecated/tokens.less +0 -116
- package/dist/deprecated/tokens.scss +0 -116
- package/dist/figma/color.json +0 -606
- package/dist/figma/size.json +0 -232
- package/dist/tokens.css +0 -270
- package/dist/tokens.css.scss +0 -270
- package/dist/tokens.docs.json +0 -5853
- package/dist/tokens.esm.js +0 -268
- package/dist/tokens.js +0 -270
- package/dist/tokens.json +0 -265
- package/dist/tokens.less +0 -267
- package/dist/tokens.scss +0 -267
- 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
|
+
})();
|