@servicetitan/hammer-token 2.5.2 → 3.0.1
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/CHANGELOG.md +52 -2
- package/README.md +332 -0
- package/build/web/core/component-variables.scss +1088 -131
- package/build/web/core/component.d.ts +558 -0
- package/build/web/core/component.js +6685 -249
- package/build/web/core/component.scss +557 -69
- package/build/web/core/css-utils/a2-border.css +23 -51
- package/build/web/core/css-utils/a2-color.css +221 -233
- package/build/web/core/css-utils/a2-font.css +1 -29
- package/build/web/core/css-utils/a2-spacing.css +238 -483
- package/build/web/core/css-utils/a2-utils.css +496 -781
- package/build/web/core/css-utils/border.css +23 -51
- package/build/web/core/css-utils/color.css +221 -233
- package/build/web/core/css-utils/font.css +1 -29
- package/build/web/core/css-utils/spacing.css +238 -483
- package/build/web/core/css-utils/utils.css +496 -781
- package/build/web/core/index.d.ts +6 -0
- package/build/web/core/index.js +1 -1
- package/build/web/core/primitive-variables.scss +148 -65
- package/build/web/core/primitive.d.ts +209 -0
- package/build/web/core/primitive.js +779 -61
- package/build/web/core/primitive.scss +207 -124
- package/build/web/core/semantic-variables.scss +363 -245
- package/build/web/core/semantic.d.ts +221 -0
- package/build/web/core/semantic.js +1592 -347
- package/build/web/core/semantic.scss +219 -140
- package/build/web/index.d.ts +3 -4
- package/build/web/types.d.ts +17 -0
- package/config.js +121 -496
- package/eslint.config.mjs +11 -1
- package/package.json +15 -5
- package/src/global/primitive/breakpoint.tokens.json +54 -0
- package/src/global/primitive/color.tokens.json +1092 -0
- package/src/global/primitive/duration.tokens.json +44 -0
- package/src/global/primitive/font.tokens.json +151 -0
- package/src/global/primitive/radius.tokens.json +94 -0
- package/src/global/primitive/size.tokens.json +174 -0
- package/src/global/primitive/transition.tokens.json +32 -0
- package/src/theme/core/background.tokens.json +1312 -0
- package/src/theme/core/border.tokens.json +192 -0
- package/src/theme/core/chart.tokens.json +982 -0
- package/src/theme/core/component/ai-mark.tokens.json +20 -0
- package/src/theme/core/component/alert.tokens.json +261 -0
- package/src/theme/core/component/announcement.tokens.json +460 -0
- package/src/theme/core/component/avatar.tokens.json +137 -0
- package/src/theme/core/component/badge.tokens.json +42 -0
- package/src/theme/core/component/breadcrumb.tokens.json +42 -0
- package/src/theme/core/component/button-toggle.tokens.json +428 -0
- package/src/theme/core/component/button.tokens.json +941 -0
- package/src/theme/core/component/calendar.tokens.json +391 -0
- package/src/theme/core/component/card.tokens.json +107 -0
- package/src/theme/core/component/checkbox.tokens.json +631 -0
- package/src/theme/core/component/chip.tokens.json +169 -0
- package/src/theme/core/component/combobox.tokens.json +269 -0
- package/src/theme/core/component/details.tokens.json +152 -0
- package/src/theme/core/component/dialog.tokens.json +87 -0
- package/src/theme/core/component/divider.tokens.json +23 -0
- package/src/theme/core/component/dnd.tokens.json +208 -0
- package/src/theme/core/component/drawer.tokens.json +61 -0
- package/src/theme/core/component/drilldown.tokens.json +61 -0
- package/src/theme/core/component/edit-card.tokens.json +381 -0
- package/src/theme/core/component/field-label.tokens.json +42 -0
- package/src/theme/core/component/field-message.tokens.json +65 -0
- package/src/theme/core/component/icon.tokens.json +42 -0
- package/src/theme/core/component/link.tokens.json +108 -0
- package/src/theme/core/component/list-view.tokens.json +82 -0
- package/src/theme/core/component/listbox.tokens.json +283 -0
- package/src/theme/core/component/menu.tokens.json +230 -0
- package/src/theme/core/component/overflow.tokens.json +84 -0
- package/src/theme/core/component/page.tokens.json +377 -0
- package/src/theme/core/component/pagination.tokens.json +63 -0
- package/src/theme/core/component/popover.tokens.json +122 -0
- package/src/theme/core/component/progress-bar.tokens.json +133 -0
- package/src/theme/core/component/radio.tokens.json +631 -0
- package/src/theme/core/component/segmented-control.tokens.json +175 -0
- package/src/theme/core/component/select-card.tokens.json +943 -0
- package/src/theme/core/component/side-nav.tokens.json +349 -0
- package/src/theme/core/component/skeleton.tokens.json +42 -0
- package/src/theme/core/component/spinner.tokens.json +96 -0
- package/src/theme/core/component/status-icon.tokens.json +164 -0
- package/src/theme/core/component/stepper.tokens.json +484 -0
- package/src/theme/core/component/switch.tokens.json +285 -0
- package/src/theme/core/component/tab.tokens.json +192 -0
- package/src/theme/core/component/text-field.tokens.json +160 -0
- package/src/theme/core/component/text.tokens.json +59 -0
- package/src/theme/core/component/toast.tokens.json +343 -0
- package/src/theme/core/component/toolbar.tokens.json +114 -0
- package/src/theme/core/component/tooltip.tokens.json +61 -0
- package/src/theme/core/focus.tokens.json +56 -0
- package/src/theme/core/foreground.tokens.json +416 -0
- package/src/theme/core/gradient.tokens.json +41 -0
- package/src/theme/core/opacity.tokens.json +25 -0
- package/src/theme/core/shadow.tokens.json +81 -0
- package/src/theme/core/status.tokens.json +74 -0
- package/src/theme/core/typography.tokens.json +163 -0
- package/src/utils/__tests__/css-utils-format-utils.test.js +312 -0
- package/src/utils/__tests__/sd-build-configs.test.js +306 -0
- package/src/utils/__tests__/sd-formats.test.js +942 -0
- package/src/utils/__tests__/sd-transforms.test.js +336 -0
- package/src/utils/__tests__/token-helpers.test.js +1160 -0
- package/src/utils/copy-css-utils-cli.js +13 -1
- package/src/utils/css-utils-format-utils.js +105 -176
- package/src/utils/figma/__tests__/sync-gradient.test.js +561 -0
- package/src/utils/figma/__tests__/token-conversion.test.js +117 -0
- package/src/utils/figma/__tests__/token-resolution.test.js +231 -0
- package/src/utils/figma/auth.js +355 -0
- package/src/utils/figma/constants.js +22 -0
- package/src/utils/figma/errors.js +80 -0
- package/src/utils/figma/figma-api.js +1069 -0
- package/src/utils/figma/get-token.js +348 -0
- package/src/utils/figma/sync-components.js +909 -0
- package/src/utils/figma/sync-main.js +692 -0
- package/src/utils/figma/sync-orchestration.js +683 -0
- package/src/utils/figma/sync-primitives.js +230 -0
- package/src/utils/figma/sync-semantic.js +1056 -0
- package/src/utils/figma/token-conversion.js +340 -0
- package/src/utils/figma/token-parsing.js +186 -0
- package/src/utils/figma/token-resolution.js +569 -0
- package/src/utils/figma/utils.js +199 -0
- package/src/utils/sd-build-configs.js +305 -0
- package/src/utils/sd-formats.js +948 -0
- package/src/utils/sd-transforms.js +165 -0
- package/src/utils/token-helpers.js +848 -0
- package/tsconfig.json +18 -0
- package/vitest.config.js +17 -0
- package/.turbo/turbo-build.log +0 -37
- package/build/web/core/raw.js +0 -234
- package/src/global/primitive/breakpoint.js +0 -19
- package/src/global/primitive/color.js +0 -231
- package/src/global/primitive/duration.js +0 -16
- package/src/global/primitive/font.js +0 -60
- package/src/global/primitive/radius.js +0 -31
- package/src/global/primitive/size.js +0 -55
- package/src/global/primitive/transition.js +0 -16
- package/src/theme/core/background.js +0 -170
- package/src/theme/core/border.js +0 -103
- package/src/theme/core/charts.js +0 -464
- package/src/theme/core/component/button.js +0 -708
- package/src/theme/core/component/checkbox.js +0 -405
- package/src/theme/core/focus.js +0 -35
- package/src/theme/core/foreground.js +0 -148
- package/src/theme/core/overlay.js +0 -137
- package/src/theme/core/shadow.js +0 -29
- package/src/theme/core/status.js +0 -49
- package/src/theme/core/typography.js +0 -82
- package/type/types.ts +0 -344
|
@@ -0,0 +1,948 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Style Dictionary format registrations
|
|
3
|
+
* @module sd-formats
|
|
4
|
+
*/
|
|
5
|
+
const {
|
|
6
|
+
generateBorderClasses,
|
|
7
|
+
generateColorClasses,
|
|
8
|
+
generateFontClasses,
|
|
9
|
+
generateSpacingClasses,
|
|
10
|
+
} = require("./css-utils-format-utils");
|
|
11
|
+
const {
|
|
12
|
+
getTokenName,
|
|
13
|
+
getTokenType,
|
|
14
|
+
getDtcgValue,
|
|
15
|
+
getDtcgDescription,
|
|
16
|
+
hasDarkValue,
|
|
17
|
+
isCompositeColor,
|
|
18
|
+
getTokenValue,
|
|
19
|
+
buildTokenMap,
|
|
20
|
+
buildFallbackWithRefs,
|
|
21
|
+
convertToHex8,
|
|
22
|
+
isGradientValue,
|
|
23
|
+
buildGradientValue,
|
|
24
|
+
resolveGradientValue,
|
|
25
|
+
} = require("./token-helpers");
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Builds the CSS utils output string with optional `@supports` blocks for dark-aware utilities.
|
|
29
|
+
* @param {string} fallback - The fallback CSS classes (light mode only)
|
|
30
|
+
* @param {string} [darkAwareClasses=''] - The dark-aware CSS classes with light-dark()
|
|
31
|
+
* @returns {string} Flat CSS output (no cascade layers — Anvil 3.0 dropped layered component CSS).
|
|
32
|
+
*/
|
|
33
|
+
const buildCssUtilsOutput = (fallback, darkAwareClasses = "") => {
|
|
34
|
+
const supportsBlock = darkAwareClasses
|
|
35
|
+
? `\n@supports (color: light-dark(#fff, #000)) {\n ${darkAwareClasses.replaceAll("\n", "\n ")}\n}`
|
|
36
|
+
: "";
|
|
37
|
+
|
|
38
|
+
return `${fallback}${supportsBlock}`;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Registers CSS utility class formats for a given class prefix.
|
|
43
|
+
* Creates format registrations for: All utils, Borders, Colors, Fonts, and Spacing.
|
|
44
|
+
* Each format generates CSS utility classes with CSS variable fallbacks and light-dark() support.
|
|
45
|
+
* @param {import('style-dictionary').default} StyleDictionary - The Style Dictionary class instance
|
|
46
|
+
* @param {string} prefix - CSS class prefix (e.g., '' or 'a2-')
|
|
47
|
+
* @param {Function} usesReferences - Function to check if a value uses references
|
|
48
|
+
* @param {Function} resolveReferences - Function to resolve references
|
|
49
|
+
* @param {string} [cssVarPrefix=''] - Prefix for CSS variable names (e.g., 'a2-')
|
|
50
|
+
* @returns {void}
|
|
51
|
+
*/
|
|
52
|
+
const registerCssUtilsFormats = (
|
|
53
|
+
StyleDictionary,
|
|
54
|
+
prefix,
|
|
55
|
+
usesReferences,
|
|
56
|
+
resolveReferences,
|
|
57
|
+
cssVarPrefix = "",
|
|
58
|
+
) => {
|
|
59
|
+
/**
|
|
60
|
+
* Format: custom/CSSUtils/{prefix}All
|
|
61
|
+
* Generates all CSS utility classes including colors, borders, typography, and spacing.
|
|
62
|
+
*/
|
|
63
|
+
StyleDictionary.registerFormat({
|
|
64
|
+
name: `custom/CSSUtils/${prefix}All`,
|
|
65
|
+
format: ({ dictionary }) => {
|
|
66
|
+
const allTokens = dictionary.allTokens || [];
|
|
67
|
+
// Build token map once for O(1) lookups
|
|
68
|
+
const tokenMap = buildTokenMap(dictionary);
|
|
69
|
+
|
|
70
|
+
// Single pass: compute light fallback value once per token, bucket into output arrays
|
|
71
|
+
const nonColorBucket = [];
|
|
72
|
+
const colorFallbackBucket = [];
|
|
73
|
+
const colorDarkAwareBucket = [];
|
|
74
|
+
|
|
75
|
+
for (const token of allTokens) {
|
|
76
|
+
const name = getTokenName(token);
|
|
77
|
+
const lightValue = buildFallbackWithRefs(
|
|
78
|
+
token,
|
|
79
|
+
dictionary,
|
|
80
|
+
tokenMap,
|
|
81
|
+
{ isDark: false, cssVarPrefix },
|
|
82
|
+
usesReferences,
|
|
83
|
+
resolveReferences,
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
if (name.startsWith("border")) {
|
|
87
|
+
nonColorBucket.push(
|
|
88
|
+
...generateBorderClasses(name, lightValue, {
|
|
89
|
+
prefix,
|
|
90
|
+
cssVarPrefix,
|
|
91
|
+
}),
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
if (name.startsWith("typography")) {
|
|
95
|
+
nonColorBucket.push(
|
|
96
|
+
...generateFontClasses(name, lightValue, { prefix }),
|
|
97
|
+
);
|
|
98
|
+
} else if (name.startsWith("size")) {
|
|
99
|
+
nonColorBucket.push(
|
|
100
|
+
...generateSpacingClasses(name, lightValue, {
|
|
101
|
+
prefix,
|
|
102
|
+
cssVarPrefix,
|
|
103
|
+
}),
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const isColorToken =
|
|
108
|
+
name.startsWith("status-color") ||
|
|
109
|
+
name.startsWith("foreground-color") ||
|
|
110
|
+
name.startsWith("background-color") ||
|
|
111
|
+
name.startsWith("overlay-color") ||
|
|
112
|
+
name.startsWith("border-color");
|
|
113
|
+
|
|
114
|
+
if (isColorToken) {
|
|
115
|
+
const classes = name.startsWith("border-color")
|
|
116
|
+
? generateBorderClasses(name, lightValue, { prefix, cssVarPrefix })
|
|
117
|
+
: generateColorClasses(name, lightValue, { prefix, cssVarPrefix });
|
|
118
|
+
colorFallbackBucket.push(...classes);
|
|
119
|
+
|
|
120
|
+
if (hasDarkValue(token)) {
|
|
121
|
+
const darkAwareValue = buildFallbackWithRefs(
|
|
122
|
+
token,
|
|
123
|
+
dictionary,
|
|
124
|
+
tokenMap,
|
|
125
|
+
{ useLightDark: true, cssVarPrefix },
|
|
126
|
+
usesReferences,
|
|
127
|
+
resolveReferences,
|
|
128
|
+
);
|
|
129
|
+
const darkClasses = name.startsWith("border-color")
|
|
130
|
+
? generateBorderClasses(name, darkAwareValue, {
|
|
131
|
+
prefix,
|
|
132
|
+
cssVarPrefix,
|
|
133
|
+
})
|
|
134
|
+
: generateColorClasses(name, darkAwareValue, {
|
|
135
|
+
prefix,
|
|
136
|
+
cssVarPrefix,
|
|
137
|
+
});
|
|
138
|
+
colorDarkAwareBucket.push(...darkClasses);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const colorTokens = colorDarkAwareBucket
|
|
144
|
+
.filter((t) => t != null)
|
|
145
|
+
.sort((a, b) => a.localeCompare(b))
|
|
146
|
+
.join("\n");
|
|
147
|
+
const nonColorTokens = nonColorBucket
|
|
148
|
+
.filter((t) => t != null)
|
|
149
|
+
.sort((a, b) => a.localeCompare(b))
|
|
150
|
+
.join("\n");
|
|
151
|
+
const colorFallbackTokens = colorFallbackBucket
|
|
152
|
+
.filter((t) => t != null)
|
|
153
|
+
.sort((a, b) => a.localeCompare(b))
|
|
154
|
+
.join("\n");
|
|
155
|
+
|
|
156
|
+
const withSr = nonColorTokens.concat(
|
|
157
|
+
"\n",
|
|
158
|
+
".sr-only {border: 0; clip: rect(0, 0, 0, 0); height: 1px; margin: -1px; overflow: hidden; padding-block: 0; padding-inline: 0; position: absolute; white-space: nowrap; width: 1px;}",
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
return buildCssUtilsOutput(
|
|
162
|
+
`${withSr}\n${colorFallbackTokens}\n`,
|
|
163
|
+
colorTokens,
|
|
164
|
+
);
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Format: custom/CSSUtils/{prefix}Borders
|
|
170
|
+
* Generates CSS utility classes for border tokens (radius, width, color).
|
|
171
|
+
*/
|
|
172
|
+
StyleDictionary.registerFormat({
|
|
173
|
+
name: `custom/CSSUtils/${prefix}Borders`,
|
|
174
|
+
format: ({ dictionary }) => {
|
|
175
|
+
const allTokens = dictionary.allTokens || [];
|
|
176
|
+
// Build token map once for O(1) lookups
|
|
177
|
+
const tokenMap = buildTokenMap(dictionary);
|
|
178
|
+
|
|
179
|
+
// Dark-aware classes with light-dark() for @supports block
|
|
180
|
+
// Uses recursive var chain with light-dark at primitive level
|
|
181
|
+
const darkAwareClasses = allTokens
|
|
182
|
+
.filter((token) => {
|
|
183
|
+
const name = getTokenName(token);
|
|
184
|
+
return name.includes("color") && name.includes("border");
|
|
185
|
+
})
|
|
186
|
+
.filter(hasDarkValue) // Only tokens with dark values
|
|
187
|
+
.map((token) => {
|
|
188
|
+
// Use useLightDark to get: light-dark(var(--light-ref, #hex), var(--dark-ref, #hex))
|
|
189
|
+
const value = buildFallbackWithRefs(
|
|
190
|
+
token,
|
|
191
|
+
dictionary,
|
|
192
|
+
tokenMap,
|
|
193
|
+
{ useLightDark: true, cssVarPrefix },
|
|
194
|
+
usesReferences,
|
|
195
|
+
resolveReferences,
|
|
196
|
+
);
|
|
197
|
+
const name = getTokenName(token);
|
|
198
|
+
// Pass value directly - it already contains light-dark()
|
|
199
|
+
return generateBorderClasses(name, value, { prefix, cssVarPrefix });
|
|
200
|
+
})
|
|
201
|
+
.flat()
|
|
202
|
+
.sort((a, b) => a.localeCompare(b))
|
|
203
|
+
.join("\n");
|
|
204
|
+
|
|
205
|
+
// Fallback classes (light values only, with recursive var chain)
|
|
206
|
+
const fallback = allTokens
|
|
207
|
+
.filter((token) => getTokenName(token).startsWith("border"))
|
|
208
|
+
.map((token) => {
|
|
209
|
+
const value = buildFallbackWithRefs(
|
|
210
|
+
token,
|
|
211
|
+
dictionary,
|
|
212
|
+
tokenMap,
|
|
213
|
+
{ isDark: false, cssVarPrefix },
|
|
214
|
+
usesReferences,
|
|
215
|
+
resolveReferences,
|
|
216
|
+
);
|
|
217
|
+
const name = getTokenName(token);
|
|
218
|
+
return generateBorderClasses(name, value, { prefix, cssVarPrefix });
|
|
219
|
+
})
|
|
220
|
+
.flat()
|
|
221
|
+
.sort((a, b) => a.localeCompare(b))
|
|
222
|
+
.join("\n");
|
|
223
|
+
|
|
224
|
+
return buildCssUtilsOutput(fallback, darkAwareClasses);
|
|
225
|
+
},
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Format: custom/CSSUtils/{prefix}Colors
|
|
230
|
+
* Generates CSS utility classes for color tokens (background, foreground, status, overlay).
|
|
231
|
+
*/
|
|
232
|
+
StyleDictionary.registerFormat({
|
|
233
|
+
name: `custom/CSSUtils/${prefix}Colors`,
|
|
234
|
+
format: ({ dictionary }) => {
|
|
235
|
+
const allTokens = dictionary.allTokens || [];
|
|
236
|
+
// Build token map once for O(1) lookups
|
|
237
|
+
const tokenMap = buildTokenMap(dictionary);
|
|
238
|
+
|
|
239
|
+
// Dark-aware classes with recursive var chain and light-dark at primitive level
|
|
240
|
+
const result = allTokens
|
|
241
|
+
.filter((token) => getTokenName(token).includes("color"))
|
|
242
|
+
.filter(hasDarkValue) // Only tokens with dark values for @supports block
|
|
243
|
+
.map((token) => {
|
|
244
|
+
// Use useLightDark to get: light-dark(var(--light-ref, #hex), var(--dark-ref, #hex))
|
|
245
|
+
const value = buildFallbackWithRefs(
|
|
246
|
+
token,
|
|
247
|
+
dictionary,
|
|
248
|
+
tokenMap,
|
|
249
|
+
{ useLightDark: true, cssVarPrefix },
|
|
250
|
+
usesReferences,
|
|
251
|
+
resolveReferences,
|
|
252
|
+
);
|
|
253
|
+
const name = getTokenName(token);
|
|
254
|
+
// Pass value directly - it already contains light-dark()
|
|
255
|
+
return generateColorClasses(name, value, { prefix, cssVarPrefix });
|
|
256
|
+
})
|
|
257
|
+
.flat()
|
|
258
|
+
.filter(Boolean)
|
|
259
|
+
.sort((a, b) => a.localeCompare(b))
|
|
260
|
+
.join("\n");
|
|
261
|
+
|
|
262
|
+
// Fallback classes (light values only, with recursive var chain)
|
|
263
|
+
const fallback = allTokens
|
|
264
|
+
.map((token) => {
|
|
265
|
+
const value = buildFallbackWithRefs(
|
|
266
|
+
token,
|
|
267
|
+
dictionary,
|
|
268
|
+
tokenMap,
|
|
269
|
+
{ isDark: false, cssVarPrefix },
|
|
270
|
+
usesReferences,
|
|
271
|
+
resolveReferences,
|
|
272
|
+
);
|
|
273
|
+
const name = getTokenName(token);
|
|
274
|
+
return generateColorClasses(name, value, { prefix, cssVarPrefix });
|
|
275
|
+
})
|
|
276
|
+
.flat()
|
|
277
|
+
.filter(Boolean)
|
|
278
|
+
.sort((a, b) => a.localeCompare(b))
|
|
279
|
+
.join("\n");
|
|
280
|
+
|
|
281
|
+
return buildCssUtilsOutput(fallback, result);
|
|
282
|
+
},
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Format: custom/CSSUtils/{prefix}Fonts
|
|
287
|
+
* Generates CSS utility classes for typography tokens (font-family, font-weight, font-size).
|
|
288
|
+
*/
|
|
289
|
+
StyleDictionary.registerFormat({
|
|
290
|
+
name: `custom/CSSUtils/${prefix}Fonts`,
|
|
291
|
+
format: ({ dictionary }) => {
|
|
292
|
+
const fallback = (dictionary.allTokens || [])
|
|
293
|
+
.map((token) => {
|
|
294
|
+
const value = getTokenValue(token, dictionary, {}, resolveReferences);
|
|
295
|
+
const name = getTokenName(token);
|
|
296
|
+
return generateFontClasses(name, value, { prefix });
|
|
297
|
+
})
|
|
298
|
+
.flat()
|
|
299
|
+
.filter(Boolean)
|
|
300
|
+
.sort((a, b) => a.localeCompare(b))
|
|
301
|
+
.join("\n");
|
|
302
|
+
|
|
303
|
+
return buildCssUtilsOutput(fallback);
|
|
304
|
+
},
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Format: custom/CSSUtils/{prefix}Spacing
|
|
309
|
+
* Generates CSS utility classes for spacing/size tokens (margin, padding).
|
|
310
|
+
*/
|
|
311
|
+
StyleDictionary.registerFormat({
|
|
312
|
+
name: `custom/CSSUtils/${prefix}Spacing`,
|
|
313
|
+
format: ({ dictionary }) => {
|
|
314
|
+
const fallback = (dictionary.allTokens || [])
|
|
315
|
+
.filter((token) => getTokenName(token).startsWith("size"))
|
|
316
|
+
.map((token) => {
|
|
317
|
+
const value = getTokenValue(token, dictionary, {}, resolveReferences);
|
|
318
|
+
const name = getTokenName(token);
|
|
319
|
+
return generateSpacingClasses(name, value, { prefix, cssVarPrefix });
|
|
320
|
+
})
|
|
321
|
+
.flat()
|
|
322
|
+
.filter(Boolean)
|
|
323
|
+
.sort((a, b) => a.localeCompare(b))
|
|
324
|
+
.join("\n");
|
|
325
|
+
|
|
326
|
+
return buildCssUtilsOutput(fallback);
|
|
327
|
+
},
|
|
328
|
+
});
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Registers all custom Style Dictionary formats including SCSS variables, ES6 exports,
|
|
333
|
+
* CSS variables, and CSS utility classes.
|
|
334
|
+
* @param {import('style-dictionary').default} StyleDictionary - The Style Dictionary class instance
|
|
335
|
+
* @param {Function} usesReferences - Function to check if a value uses references
|
|
336
|
+
* @param {Function} resolveReferences - Function to resolve references
|
|
337
|
+
* @param {string} [cssVarPrefix=''] - Prefix for CSS variable names (e.g., 'a2-')
|
|
338
|
+
* @returns {void}
|
|
339
|
+
* @example
|
|
340
|
+
* const StyleDictionary = require('style-dictionary');
|
|
341
|
+
* registerFormats(StyleDictionary, usesReferences, resolveReferences, 'a2-');
|
|
342
|
+
*/
|
|
343
|
+
const registerFormats = (
|
|
344
|
+
StyleDictionary,
|
|
345
|
+
usesReferences,
|
|
346
|
+
resolveReferences,
|
|
347
|
+
cssVarPrefix = "",
|
|
348
|
+
) => {
|
|
349
|
+
/**
|
|
350
|
+
* Format: custom/scss-variables
|
|
351
|
+
* Generates SCSS variables with CSS var() fallbacks and light-dark() support.
|
|
352
|
+
* Output format: $var: var(--var, value);
|
|
353
|
+
*/
|
|
354
|
+
StyleDictionary.registerFormat({
|
|
355
|
+
name: "custom/scss-variables",
|
|
356
|
+
format: ({ dictionary }) => {
|
|
357
|
+
// Build token lookup map once for O(1) lookups
|
|
358
|
+
const tokensByName = buildTokenMap(dictionary);
|
|
359
|
+
|
|
360
|
+
return dictionary.allTokens
|
|
361
|
+
.map((token) => {
|
|
362
|
+
const name = getTokenName(token);
|
|
363
|
+
const description = getDtcgDescription(token);
|
|
364
|
+
const comment = description ? `// ${description}\n` : "";
|
|
365
|
+
|
|
366
|
+
// Special handling for overlay-color (no CSS variable)
|
|
367
|
+
if (name.startsWith("overlay-color")) {
|
|
368
|
+
return `${comment}$${name}: ${getTokenValue(token, dictionary, {}, resolveReferences)};`;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Gradient tokens: build linear-gradient() fallback with CSS var chains
|
|
372
|
+
if (getTokenType(token) === "gradient") {
|
|
373
|
+
const gradientValue = resolveGradientValue(token, tokensByName);
|
|
374
|
+
if (!isGradientValue(gradientValue)) return null;
|
|
375
|
+
const hasDark = gradientValue.stops.some(
|
|
376
|
+
(s) =>
|
|
377
|
+
s.color.$extensions?.appearance?.dark?.$value !== undefined,
|
|
378
|
+
);
|
|
379
|
+
const light = buildGradientValue(
|
|
380
|
+
gradientValue,
|
|
381
|
+
false,
|
|
382
|
+
tokensByName,
|
|
383
|
+
dictionary,
|
|
384
|
+
cssVarPrefix,
|
|
385
|
+
usesReferences,
|
|
386
|
+
resolveReferences,
|
|
387
|
+
);
|
|
388
|
+
const fallback = hasDark
|
|
389
|
+
? `light-dark(${light}, ${buildGradientValue(gradientValue, true, tokensByName, dictionary, cssVarPrefix, usesReferences, resolveReferences)})`
|
|
390
|
+
: light;
|
|
391
|
+
return `${comment}$${name}: var(--${cssVarPrefix}${name}, ${fallback});`;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Build fallback with light-dark() at the level where values differ
|
|
395
|
+
const fallback = buildFallbackWithRefs(
|
|
396
|
+
token,
|
|
397
|
+
dictionary,
|
|
398
|
+
tokensByName,
|
|
399
|
+
{ useLightDark: true, cssVarPrefix },
|
|
400
|
+
usesReferences,
|
|
401
|
+
resolveReferences,
|
|
402
|
+
);
|
|
403
|
+
return `${comment}$${name}: var(--${cssVarPrefix}${name}, ${fallback});`;
|
|
404
|
+
})
|
|
405
|
+
.filter(Boolean)
|
|
406
|
+
.join("\n");
|
|
407
|
+
},
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Format: custom/scss-variables-map
|
|
412
|
+
* Generates SCSS variable maps for both primitive and semantic tokens.
|
|
413
|
+
* Creates separate $light, $dark, and $nonColor maps for tokens with appearance variants.
|
|
414
|
+
*/
|
|
415
|
+
StyleDictionary.registerFormat({
|
|
416
|
+
name: "custom/scss-variables-map",
|
|
417
|
+
format: ({ dictionary, file }) => {
|
|
418
|
+
const allTokens = dictionary.allTokens || [];
|
|
419
|
+
const hasAppearanceVariants = allTokens.some(hasDarkValue);
|
|
420
|
+
|
|
421
|
+
// Check if this is for component tokens - they need only the final primitive reference
|
|
422
|
+
const isComponentBuild = file.destination?.includes("component");
|
|
423
|
+
|
|
424
|
+
// Build token lookup map once for O(1) lookups
|
|
425
|
+
const tokensByName = buildTokenMap(dictionary);
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Builds a nested CSS var chain, skipping tokens with $extensions.appearance or from component folder.
|
|
429
|
+
* @param {Object} token - The token object
|
|
430
|
+
* @param {boolean} [isDark=false] - Whether to get dark mode value
|
|
431
|
+
* @param {string} [prefix=cssVarPrefix] - CSS variable prefix
|
|
432
|
+
* @returns {string} The CSS var chain
|
|
433
|
+
* @example
|
|
434
|
+
* // Returns: var(--a2-border-radius-medium, var(--a2-radius-2, 0.375rem))
|
|
435
|
+
*/
|
|
436
|
+
const getNoLightDarkRef = (
|
|
437
|
+
token,
|
|
438
|
+
isDark = false,
|
|
439
|
+
prefix = cssVarPrefix,
|
|
440
|
+
) => {
|
|
441
|
+
/**
|
|
442
|
+
* Recursively builds the var chain for a token and its references.
|
|
443
|
+
* @param {Object} currentToken - The current token being processed
|
|
444
|
+
* @param {Set<string>} [visited=new Set()] - Set of visited token names to prevent cycles
|
|
445
|
+
* @param {boolean} [isFirst=true] - Whether this is the first token in the chain
|
|
446
|
+
* @returns {string} The CSS var chain
|
|
447
|
+
*/
|
|
448
|
+
const buildChain = (
|
|
449
|
+
currentToken,
|
|
450
|
+
visited = new Set(),
|
|
451
|
+
isFirst = true,
|
|
452
|
+
) => {
|
|
453
|
+
const tokenName = getTokenName(currentToken);
|
|
454
|
+
if (visited.has(tokenName)) {
|
|
455
|
+
return getTokenValue(
|
|
456
|
+
currentToken,
|
|
457
|
+
dictionary,
|
|
458
|
+
{ isDark, forScssMap: true },
|
|
459
|
+
resolveReferences,
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
visited.add(tokenName);
|
|
463
|
+
|
|
464
|
+
// Check if this token should be skipped in the chain
|
|
465
|
+
const hasAppearance =
|
|
466
|
+
currentToken.original?.$extensions?.appearance !== undefined ||
|
|
467
|
+
currentToken.$extensions?.appearance !== undefined;
|
|
468
|
+
const isComponentToken =
|
|
469
|
+
currentToken.filePath?.includes("/component/");
|
|
470
|
+
const shouldSkip = hasAppearance || (isFirst && isComponentToken);
|
|
471
|
+
|
|
472
|
+
// Get the value (considering light/dark mode)
|
|
473
|
+
const valueToFollow = isDark
|
|
474
|
+
? (currentToken.original?.$extensions?.appearance?.dark?.$value ??
|
|
475
|
+
currentToken.original?.$value ??
|
|
476
|
+
getDtcgValue(currentToken))
|
|
477
|
+
: (currentToken.original?.$extensions?.appearance?.light?.$value ??
|
|
478
|
+
currentToken.original?.$value ??
|
|
479
|
+
getDtcgValue(currentToken));
|
|
480
|
+
|
|
481
|
+
// If value is a reference, try to follow it
|
|
482
|
+
if (
|
|
483
|
+
typeof valueToFollow === "string" &&
|
|
484
|
+
valueToFollow.includes("{")
|
|
485
|
+
) {
|
|
486
|
+
const match = valueToFollow.match(/\{([^{}]+)\}/);
|
|
487
|
+
if (match) {
|
|
488
|
+
const refPath = match[1];
|
|
489
|
+
const refToken = tokensByName.get(refPath.replace(/\./g, "-"));
|
|
490
|
+
if (refToken) {
|
|
491
|
+
// Recursively build the chain for the referenced token
|
|
492
|
+
const innerChain = buildChain(refToken, visited, false);
|
|
493
|
+
|
|
494
|
+
// Skip this token in the chain if it has appearance or is component
|
|
495
|
+
if (shouldSkip) {
|
|
496
|
+
return innerChain;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// Include this token in the chain
|
|
500
|
+
return `var(--${prefix}${tokenName}, ${innerChain})`;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// Composite color: resolve inner reference via tokenMap and apply alpha
|
|
506
|
+
if (isCompositeColor(valueToFollow)) {
|
|
507
|
+
const refMatch =
|
|
508
|
+
typeof valueToFollow.color === "string" &&
|
|
509
|
+
valueToFollow.color.match(/\{([^{}]+)\}/);
|
|
510
|
+
if (refMatch) {
|
|
511
|
+
const refTokenName = refMatch[1].replace(/\./g, "-");
|
|
512
|
+
const refToken = tokensByName.get(refTokenName);
|
|
513
|
+
if (refToken) {
|
|
514
|
+
const resolved = getTokenValue(
|
|
515
|
+
refToken,
|
|
516
|
+
dictionary,
|
|
517
|
+
{ isDark, forScssMap: true },
|
|
518
|
+
resolveReferences,
|
|
519
|
+
);
|
|
520
|
+
if (typeof resolved === "string" && resolved.startsWith("#")) {
|
|
521
|
+
return convertToHex8(resolved, valueToFollow.alpha);
|
|
522
|
+
}
|
|
523
|
+
return resolved;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// No reference or can't follow - this is the final value
|
|
529
|
+
if (shouldSkip) {
|
|
530
|
+
return getTokenValue(
|
|
531
|
+
currentToken,
|
|
532
|
+
dictionary,
|
|
533
|
+
{ isDark, forScssMap: true },
|
|
534
|
+
resolveReferences,
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// Include in chain with final value
|
|
539
|
+
const finalValue = getTokenValue(
|
|
540
|
+
currentToken,
|
|
541
|
+
dictionary,
|
|
542
|
+
{ isDark, forScssMap: true },
|
|
543
|
+
resolveReferences,
|
|
544
|
+
);
|
|
545
|
+
return `var(--${prefix}${tokenName}, ${finalValue})`;
|
|
546
|
+
};
|
|
547
|
+
|
|
548
|
+
return buildChain(token);
|
|
549
|
+
};
|
|
550
|
+
|
|
551
|
+
if (hasAppearanceVariants) {
|
|
552
|
+
/**
|
|
553
|
+
* Checks if a token is a color type token.
|
|
554
|
+
* @param {Object} token - The token object
|
|
555
|
+
* @returns {boolean} True if the token is a color type
|
|
556
|
+
*/
|
|
557
|
+
const isColorToken = (token) => getTokenType(token) === "color";
|
|
558
|
+
|
|
559
|
+
const light = allTokens
|
|
560
|
+
.filter(isColorToken)
|
|
561
|
+
.map((token) => {
|
|
562
|
+
const name = getTokenName(token);
|
|
563
|
+
// For components, get innermost var; for semantic, get full fallback chain
|
|
564
|
+
const fallback = isComponentBuild
|
|
565
|
+
? getNoLightDarkRef(token, false)
|
|
566
|
+
: buildFallbackWithRefs(
|
|
567
|
+
token,
|
|
568
|
+
dictionary,
|
|
569
|
+
tokensByName,
|
|
570
|
+
{
|
|
571
|
+
useLightDark: false,
|
|
572
|
+
isDark: false,
|
|
573
|
+
cssVarPrefix,
|
|
574
|
+
forScssMap: true,
|
|
575
|
+
},
|
|
576
|
+
usesReferences,
|
|
577
|
+
resolveReferences,
|
|
578
|
+
);
|
|
579
|
+
return ` ${name}: ${fallback},`;
|
|
580
|
+
})
|
|
581
|
+
.join("\n");
|
|
582
|
+
|
|
583
|
+
const dark = allTokens
|
|
584
|
+
.filter(isColorToken)
|
|
585
|
+
.map((token) => {
|
|
586
|
+
const name = getTokenName(token);
|
|
587
|
+
const fallback = isComponentBuild
|
|
588
|
+
? getNoLightDarkRef(token, true)
|
|
589
|
+
: buildFallbackWithRefs(
|
|
590
|
+
token,
|
|
591
|
+
dictionary,
|
|
592
|
+
tokensByName,
|
|
593
|
+
{
|
|
594
|
+
useLightDark: false,
|
|
595
|
+
isDark: true,
|
|
596
|
+
cssVarPrefix,
|
|
597
|
+
forScssMap: true,
|
|
598
|
+
},
|
|
599
|
+
usesReferences,
|
|
600
|
+
resolveReferences,
|
|
601
|
+
);
|
|
602
|
+
return ` ${name}: ${fallback},`;
|
|
603
|
+
})
|
|
604
|
+
.join("\n");
|
|
605
|
+
|
|
606
|
+
const nonColor = allTokens
|
|
607
|
+
.filter((token) => !isColorToken(token))
|
|
608
|
+
.map((token) => {
|
|
609
|
+
const name = getTokenName(token);
|
|
610
|
+
if (getTokenType(token) === "gradient") {
|
|
611
|
+
const gradientValue = resolveGradientValue(token, tokensByName);
|
|
612
|
+
if (!isGradientValue(gradientValue)) return null;
|
|
613
|
+
const fallback = buildGradientValue(
|
|
614
|
+
gradientValue,
|
|
615
|
+
false,
|
|
616
|
+
tokensByName,
|
|
617
|
+
dictionary,
|
|
618
|
+
cssVarPrefix,
|
|
619
|
+
usesReferences,
|
|
620
|
+
resolveReferences,
|
|
621
|
+
);
|
|
622
|
+
return ` ${name}: "${fallback}",`;
|
|
623
|
+
}
|
|
624
|
+
const fallback = isComponentBuild
|
|
625
|
+
? getNoLightDarkRef(token, false)
|
|
626
|
+
: buildFallbackWithRefs(
|
|
627
|
+
token,
|
|
628
|
+
dictionary,
|
|
629
|
+
tokensByName,
|
|
630
|
+
{ useLightDark: false, cssVarPrefix, forScssMap: true },
|
|
631
|
+
usesReferences,
|
|
632
|
+
resolveReferences,
|
|
633
|
+
);
|
|
634
|
+
return ` ${name}: ${fallback},`;
|
|
635
|
+
})
|
|
636
|
+
.filter(Boolean)
|
|
637
|
+
.join("\n");
|
|
638
|
+
|
|
639
|
+
return `$light: (\n${light}\n);\n$dark: (\n${dark}\n);\n$nonColor: (\n${nonColor}\n);`;
|
|
640
|
+
} else {
|
|
641
|
+
const vars = allTokens
|
|
642
|
+
.map((token) => {
|
|
643
|
+
const name = getTokenName(token);
|
|
644
|
+
if (getTokenType(token) === "gradient") {
|
|
645
|
+
const gradientValue = resolveGradientValue(token, tokensByName);
|
|
646
|
+
if (!isGradientValue(gradientValue)) return null;
|
|
647
|
+
const fallback = buildGradientValue(
|
|
648
|
+
gradientValue,
|
|
649
|
+
false,
|
|
650
|
+
tokensByName,
|
|
651
|
+
dictionary,
|
|
652
|
+
cssVarPrefix,
|
|
653
|
+
usesReferences,
|
|
654
|
+
resolveReferences,
|
|
655
|
+
);
|
|
656
|
+
return ` ${name}: "${fallback}",`;
|
|
657
|
+
}
|
|
658
|
+
const fallback = isComponentBuild
|
|
659
|
+
? getNoLightDarkRef(token, false)
|
|
660
|
+
: buildFallbackWithRefs(
|
|
661
|
+
token,
|
|
662
|
+
dictionary,
|
|
663
|
+
tokensByName,
|
|
664
|
+
{ useLightDark: false, cssVarPrefix, forScssMap: true },
|
|
665
|
+
usesReferences,
|
|
666
|
+
resolveReferences,
|
|
667
|
+
);
|
|
668
|
+
return ` ${name}: ${fallback},`;
|
|
669
|
+
})
|
|
670
|
+
.filter(Boolean)
|
|
671
|
+
.join("\n");
|
|
672
|
+
|
|
673
|
+
return `$token: (\n${vars}\n);`;
|
|
674
|
+
}
|
|
675
|
+
},
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* Extracts terminal hex values for light and dark from a buildFallbackWithRefs result.
|
|
680
|
+
* Finds the light-dark() call in the chain (may be nested in var() fallbacks) and
|
|
681
|
+
* extracts the last hex from each arm. Returns { light, dark } where dark is null
|
|
682
|
+
* if no light-dark() was found (meaning light === dark).
|
|
683
|
+
* @param {string} chain - Result of buildFallbackWithRefs with useLightDark: true
|
|
684
|
+
* @returns {{ light: string, dark: string|null }}
|
|
685
|
+
*/
|
|
686
|
+
const extractLightDarkHex = (chain) => {
|
|
687
|
+
const ldIdx = chain.indexOf("light-dark(");
|
|
688
|
+
if (ldIdx === -1) {
|
|
689
|
+
const hexes = chain.match(/#[0-9a-fA-F]{3,8}/g);
|
|
690
|
+
return { light: hexes ? hexes[hexes.length - 1] : chain, dark: null };
|
|
691
|
+
}
|
|
692
|
+
// Find matching close paren for the light-dark( call
|
|
693
|
+
let depth = 0;
|
|
694
|
+
const start = ldIdx + "light-dark(".length;
|
|
695
|
+
let end = -1;
|
|
696
|
+
for (let i = ldIdx + "light-dark(".length - 1; i < chain.length; i++) {
|
|
697
|
+
if (chain[i] === "(") depth++;
|
|
698
|
+
else if (chain[i] === ")") {
|
|
699
|
+
depth--;
|
|
700
|
+
if (depth === 0) {
|
|
701
|
+
end = i;
|
|
702
|
+
break;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
if (end === -1) {
|
|
707
|
+
const hexes = chain.match(/#[0-9a-fA-F]{3,8}/g);
|
|
708
|
+
return { light: hexes ? hexes[hexes.length - 1] : chain, dark: null };
|
|
709
|
+
}
|
|
710
|
+
// Split the two top-level args of light-dark() by finding the comma at depth 0
|
|
711
|
+
const inner = chain.slice(start, end);
|
|
712
|
+
let splitIdx = -1;
|
|
713
|
+
let d = 0;
|
|
714
|
+
for (let i = 0; i < inner.length; i++) {
|
|
715
|
+
if (inner[i] === "(") d++;
|
|
716
|
+
else if (inner[i] === ")") d--;
|
|
717
|
+
else if (inner[i] === "," && d === 0) {
|
|
718
|
+
splitIdx = i;
|
|
719
|
+
break;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
if (splitIdx === -1) {
|
|
723
|
+
const hexes = chain.match(/#[0-9a-fA-F]{3,8}/g);
|
|
724
|
+
return { light: hexes ? hexes[hexes.length - 1] : chain, dark: null };
|
|
725
|
+
}
|
|
726
|
+
const lightArm = inner.slice(0, splitIdx);
|
|
727
|
+
const darkArm = inner.slice(splitIdx + 1);
|
|
728
|
+
const lastHex = (s) => {
|
|
729
|
+
const m = s.match(/#[0-9a-fA-F]{3,8}/g);
|
|
730
|
+
return m ? m[m.length - 1] : s.trim();
|
|
731
|
+
};
|
|
732
|
+
return { light: lastHex(lightArm), dark: lastHex(darkArm) };
|
|
733
|
+
};
|
|
734
|
+
|
|
735
|
+
/**
|
|
736
|
+
* Format: custom/es6-variable
|
|
737
|
+
* Generates ES6 module exports for tokens with TypeScript-compatible JSDoc types.
|
|
738
|
+
* Tokens with dark variants include an extensions.appearance.dark.value property.
|
|
739
|
+
* Uses buildFallbackWithRefs to walk the full reference chain so that multi-hop
|
|
740
|
+
* semantic references correctly resolve to their terminal dark hex values.
|
|
741
|
+
*/
|
|
742
|
+
StyleDictionary.registerFormat({
|
|
743
|
+
name: "custom/es6-variable",
|
|
744
|
+
format: ({ dictionary }) => {
|
|
745
|
+
const typeDefinitions = `/**
|
|
746
|
+
* @typedef {Object} TokenValue
|
|
747
|
+
* @property {string} value
|
|
748
|
+
*/
|
|
749
|
+
|
|
750
|
+
/**
|
|
751
|
+
* @typedef {Object} TokenWithAppearance
|
|
752
|
+
* @property {string} value
|
|
753
|
+
* @property {Object} extensions
|
|
754
|
+
* @property {Object} extensions.appearance
|
|
755
|
+
* @property {Object} extensions.appearance.dark
|
|
756
|
+
* @property {string} extensions.appearance.dark.value
|
|
757
|
+
*/
|
|
758
|
+
|
|
759
|
+
`;
|
|
760
|
+
const tokensByName = buildTokenMap(dictionary);
|
|
761
|
+
|
|
762
|
+
return (
|
|
763
|
+
typeDefinitions +
|
|
764
|
+
dictionary.allTokens
|
|
765
|
+
.map((token) => {
|
|
766
|
+
const name = getTokenName(token);
|
|
767
|
+
const pascalName = name
|
|
768
|
+
.split("-")
|
|
769
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
770
|
+
.join("");
|
|
771
|
+
|
|
772
|
+
const description = getDtcgDescription(token);
|
|
773
|
+
const descriptionTag = description
|
|
774
|
+
? ` * @description ${description}\n`
|
|
775
|
+
: "";
|
|
776
|
+
|
|
777
|
+
// Gradient tokens: build linear-gradient() strings directly (no hex extraction)
|
|
778
|
+
if (getTokenType(token) === "gradient") {
|
|
779
|
+
const gradientValue = resolveGradientValue(token, tokensByName);
|
|
780
|
+
if (!isGradientValue(gradientValue)) return null;
|
|
781
|
+
const hasDark = gradientValue.stops.some(
|
|
782
|
+
(s) =>
|
|
783
|
+
s.color.$extensions?.appearance?.dark?.$value !== undefined,
|
|
784
|
+
);
|
|
785
|
+
const lightValue = buildGradientValue(
|
|
786
|
+
gradientValue,
|
|
787
|
+
false,
|
|
788
|
+
tokensByName,
|
|
789
|
+
dictionary,
|
|
790
|
+
"",
|
|
791
|
+
usesReferences,
|
|
792
|
+
resolveReferences,
|
|
793
|
+
);
|
|
794
|
+
if (hasDark) {
|
|
795
|
+
const darkValue = buildGradientValue(
|
|
796
|
+
gradientValue,
|
|
797
|
+
true,
|
|
798
|
+
tokensByName,
|
|
799
|
+
dictionary,
|
|
800
|
+
"",
|
|
801
|
+
usesReferences,
|
|
802
|
+
resolveReferences,
|
|
803
|
+
);
|
|
804
|
+
return `/**\n * @type {TokenWithAppearance}\n${descriptionTag} */
|
|
805
|
+
export const ${pascalName} = {
|
|
806
|
+
value: ${JSON.stringify(lightValue)},
|
|
807
|
+
extensions: {
|
|
808
|
+
appearance: {
|
|
809
|
+
dark: {
|
|
810
|
+
value: ${JSON.stringify(darkValue)}
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
};`;
|
|
815
|
+
}
|
|
816
|
+
return `/**\n * @type {TokenValue}\n${descriptionTag} */
|
|
817
|
+
export const ${pascalName} = { value: ${JSON.stringify(lightValue)} };`;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
const chain = buildFallbackWithRefs(
|
|
821
|
+
token,
|
|
822
|
+
dictionary,
|
|
823
|
+
tokensByName,
|
|
824
|
+
{ useLightDark: true, cssVarPrefix: "" },
|
|
825
|
+
usesReferences,
|
|
826
|
+
resolveReferences,
|
|
827
|
+
);
|
|
828
|
+
const { light: lightValue, dark: darkValue } = chain.includes(
|
|
829
|
+
"light-dark(",
|
|
830
|
+
)
|
|
831
|
+
? extractLightDarkHex(chain)
|
|
832
|
+
: {
|
|
833
|
+
light: getTokenValue(
|
|
834
|
+
token,
|
|
835
|
+
dictionary,
|
|
836
|
+
{},
|
|
837
|
+
resolveReferences,
|
|
838
|
+
),
|
|
839
|
+
dark: null,
|
|
840
|
+
};
|
|
841
|
+
|
|
842
|
+
if (darkValue !== null || hasDarkValue(token)) {
|
|
843
|
+
const resolvedDark = darkValue !== null ? darkValue : lightValue;
|
|
844
|
+
return `/**\n * @type {TokenWithAppearance}\n${descriptionTag} */
|
|
845
|
+
export const ${pascalName} = {
|
|
846
|
+
value: ${JSON.stringify(lightValue)},
|
|
847
|
+
extensions: {
|
|
848
|
+
appearance: {
|
|
849
|
+
dark: {
|
|
850
|
+
value: ${JSON.stringify(resolvedDark)}
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
};`;
|
|
855
|
+
}
|
|
856
|
+
return `/**\n * @type {TokenValue}\n${descriptionTag} */
|
|
857
|
+
export const ${pascalName} = { value: ${JSON.stringify(lightValue)} };`;
|
|
858
|
+
})
|
|
859
|
+
.filter(Boolean)
|
|
860
|
+
.join("\n")
|
|
861
|
+
);
|
|
862
|
+
},
|
|
863
|
+
});
|
|
864
|
+
|
|
865
|
+
/**
|
|
866
|
+
* Format: custom/CSSVariables
|
|
867
|
+
* Generates CSS custom properties in a :root selector with light-dark() support.
|
|
868
|
+
*/
|
|
869
|
+
StyleDictionary.registerFormat({
|
|
870
|
+
name: "custom/CSSVariables",
|
|
871
|
+
format: ({ dictionary }) => {
|
|
872
|
+
const tokensByName = buildTokenMap(dictionary);
|
|
873
|
+
|
|
874
|
+
const cssVars = dictionary.allTokens
|
|
875
|
+
.map((token) => {
|
|
876
|
+
const name = getTokenName(token).replace("-default", "");
|
|
877
|
+
const description = getDtcgDescription(token);
|
|
878
|
+
const comment = description ? ` /* ${description} */\n` : "";
|
|
879
|
+
|
|
880
|
+
if (getTokenType(token) === "gradient") {
|
|
881
|
+
const gradientValue = resolveGradientValue(token, tokensByName);
|
|
882
|
+
if (!isGradientValue(gradientValue)) return null;
|
|
883
|
+
const hasDark = gradientValue.stops.some(
|
|
884
|
+
(s) =>
|
|
885
|
+
s.color.$extensions?.appearance?.dark?.$value !== undefined,
|
|
886
|
+
);
|
|
887
|
+
const light = buildGradientValue(
|
|
888
|
+
gradientValue,
|
|
889
|
+
false,
|
|
890
|
+
tokensByName,
|
|
891
|
+
dictionary,
|
|
892
|
+
cssVarPrefix,
|
|
893
|
+
usesReferences,
|
|
894
|
+
resolveReferences,
|
|
895
|
+
);
|
|
896
|
+
if (hasDark) {
|
|
897
|
+
const dark = buildGradientValue(
|
|
898
|
+
gradientValue,
|
|
899
|
+
true,
|
|
900
|
+
tokensByName,
|
|
901
|
+
dictionary,
|
|
902
|
+
cssVarPrefix,
|
|
903
|
+
usesReferences,
|
|
904
|
+
resolveReferences,
|
|
905
|
+
);
|
|
906
|
+
return `${comment} --${cssVarPrefix}${name}: light-dark(${light}, ${dark});`;
|
|
907
|
+
}
|
|
908
|
+
return `${comment} --${cssVarPrefix}${name}: ${light};`;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
const light = getTokenValue(token, dictionary, {}, resolveReferences);
|
|
912
|
+
const dark = hasDarkValue(token)
|
|
913
|
+
? getTokenValue(
|
|
914
|
+
token,
|
|
915
|
+
dictionary,
|
|
916
|
+
{ isDark: true },
|
|
917
|
+
resolveReferences,
|
|
918
|
+
)
|
|
919
|
+
: null;
|
|
920
|
+
|
|
921
|
+
if (dark) {
|
|
922
|
+
return `${comment} --${cssVarPrefix}${name}: light-dark(${light}, ${dark});`;
|
|
923
|
+
}
|
|
924
|
+
return `${comment} --${cssVarPrefix}${name}: ${light};`;
|
|
925
|
+
})
|
|
926
|
+
.filter(Boolean)
|
|
927
|
+
.join(`\n`);
|
|
928
|
+
|
|
929
|
+
return `:root {\n${cssVars}\n}`;
|
|
930
|
+
},
|
|
931
|
+
});
|
|
932
|
+
|
|
933
|
+
// Register CSS utils formats for each prefix
|
|
934
|
+
const CSS_UTILS_PREFIXES = ["", "a2-"];
|
|
935
|
+
CSS_UTILS_PREFIXES.forEach((prefix) => {
|
|
936
|
+
registerCssUtilsFormats(
|
|
937
|
+
StyleDictionary,
|
|
938
|
+
prefix,
|
|
939
|
+
usesReferences,
|
|
940
|
+
resolveReferences,
|
|
941
|
+
cssVarPrefix,
|
|
942
|
+
);
|
|
943
|
+
});
|
|
944
|
+
};
|
|
945
|
+
|
|
946
|
+
module.exports = {
|
|
947
|
+
registerFormats,
|
|
948
|
+
};
|