@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.
Files changed (146) hide show
  1. package/CHANGELOG.md +52 -2
  2. package/README.md +332 -0
  3. package/build/web/core/component-variables.scss +1088 -131
  4. package/build/web/core/component.d.ts +558 -0
  5. package/build/web/core/component.js +6685 -249
  6. package/build/web/core/component.scss +557 -69
  7. package/build/web/core/css-utils/a2-border.css +23 -51
  8. package/build/web/core/css-utils/a2-color.css +221 -233
  9. package/build/web/core/css-utils/a2-font.css +1 -29
  10. package/build/web/core/css-utils/a2-spacing.css +238 -483
  11. package/build/web/core/css-utils/a2-utils.css +496 -781
  12. package/build/web/core/css-utils/border.css +23 -51
  13. package/build/web/core/css-utils/color.css +221 -233
  14. package/build/web/core/css-utils/font.css +1 -29
  15. package/build/web/core/css-utils/spacing.css +238 -483
  16. package/build/web/core/css-utils/utils.css +496 -781
  17. package/build/web/core/index.d.ts +6 -0
  18. package/build/web/core/index.js +1 -1
  19. package/build/web/core/primitive-variables.scss +148 -65
  20. package/build/web/core/primitive.d.ts +209 -0
  21. package/build/web/core/primitive.js +779 -61
  22. package/build/web/core/primitive.scss +207 -124
  23. package/build/web/core/semantic-variables.scss +363 -245
  24. package/build/web/core/semantic.d.ts +221 -0
  25. package/build/web/core/semantic.js +1592 -347
  26. package/build/web/core/semantic.scss +219 -140
  27. package/build/web/index.d.ts +3 -4
  28. package/build/web/types.d.ts +17 -0
  29. package/config.js +121 -496
  30. package/eslint.config.mjs +11 -1
  31. package/package.json +15 -5
  32. package/src/global/primitive/breakpoint.tokens.json +54 -0
  33. package/src/global/primitive/color.tokens.json +1092 -0
  34. package/src/global/primitive/duration.tokens.json +44 -0
  35. package/src/global/primitive/font.tokens.json +151 -0
  36. package/src/global/primitive/radius.tokens.json +94 -0
  37. package/src/global/primitive/size.tokens.json +174 -0
  38. package/src/global/primitive/transition.tokens.json +32 -0
  39. package/src/theme/core/background.tokens.json +1312 -0
  40. package/src/theme/core/border.tokens.json +192 -0
  41. package/src/theme/core/chart.tokens.json +982 -0
  42. package/src/theme/core/component/ai-mark.tokens.json +20 -0
  43. package/src/theme/core/component/alert.tokens.json +261 -0
  44. package/src/theme/core/component/announcement.tokens.json +460 -0
  45. package/src/theme/core/component/avatar.tokens.json +137 -0
  46. package/src/theme/core/component/badge.tokens.json +42 -0
  47. package/src/theme/core/component/breadcrumb.tokens.json +42 -0
  48. package/src/theme/core/component/button-toggle.tokens.json +428 -0
  49. package/src/theme/core/component/button.tokens.json +941 -0
  50. package/src/theme/core/component/calendar.tokens.json +391 -0
  51. package/src/theme/core/component/card.tokens.json +107 -0
  52. package/src/theme/core/component/checkbox.tokens.json +631 -0
  53. package/src/theme/core/component/chip.tokens.json +169 -0
  54. package/src/theme/core/component/combobox.tokens.json +269 -0
  55. package/src/theme/core/component/details.tokens.json +152 -0
  56. package/src/theme/core/component/dialog.tokens.json +87 -0
  57. package/src/theme/core/component/divider.tokens.json +23 -0
  58. package/src/theme/core/component/dnd.tokens.json +208 -0
  59. package/src/theme/core/component/drawer.tokens.json +61 -0
  60. package/src/theme/core/component/drilldown.tokens.json +61 -0
  61. package/src/theme/core/component/edit-card.tokens.json +381 -0
  62. package/src/theme/core/component/field-label.tokens.json +42 -0
  63. package/src/theme/core/component/field-message.tokens.json +65 -0
  64. package/src/theme/core/component/icon.tokens.json +42 -0
  65. package/src/theme/core/component/link.tokens.json +108 -0
  66. package/src/theme/core/component/list-view.tokens.json +82 -0
  67. package/src/theme/core/component/listbox.tokens.json +283 -0
  68. package/src/theme/core/component/menu.tokens.json +230 -0
  69. package/src/theme/core/component/overflow.tokens.json +84 -0
  70. package/src/theme/core/component/page.tokens.json +377 -0
  71. package/src/theme/core/component/pagination.tokens.json +63 -0
  72. package/src/theme/core/component/popover.tokens.json +122 -0
  73. package/src/theme/core/component/progress-bar.tokens.json +133 -0
  74. package/src/theme/core/component/radio.tokens.json +631 -0
  75. package/src/theme/core/component/segmented-control.tokens.json +175 -0
  76. package/src/theme/core/component/select-card.tokens.json +943 -0
  77. package/src/theme/core/component/side-nav.tokens.json +349 -0
  78. package/src/theme/core/component/skeleton.tokens.json +42 -0
  79. package/src/theme/core/component/spinner.tokens.json +96 -0
  80. package/src/theme/core/component/status-icon.tokens.json +164 -0
  81. package/src/theme/core/component/stepper.tokens.json +484 -0
  82. package/src/theme/core/component/switch.tokens.json +285 -0
  83. package/src/theme/core/component/tab.tokens.json +192 -0
  84. package/src/theme/core/component/text-field.tokens.json +160 -0
  85. package/src/theme/core/component/text.tokens.json +59 -0
  86. package/src/theme/core/component/toast.tokens.json +343 -0
  87. package/src/theme/core/component/toolbar.tokens.json +114 -0
  88. package/src/theme/core/component/tooltip.tokens.json +61 -0
  89. package/src/theme/core/focus.tokens.json +56 -0
  90. package/src/theme/core/foreground.tokens.json +416 -0
  91. package/src/theme/core/gradient.tokens.json +41 -0
  92. package/src/theme/core/opacity.tokens.json +25 -0
  93. package/src/theme/core/shadow.tokens.json +81 -0
  94. package/src/theme/core/status.tokens.json +74 -0
  95. package/src/theme/core/typography.tokens.json +163 -0
  96. package/src/utils/__tests__/css-utils-format-utils.test.js +312 -0
  97. package/src/utils/__tests__/sd-build-configs.test.js +306 -0
  98. package/src/utils/__tests__/sd-formats.test.js +942 -0
  99. package/src/utils/__tests__/sd-transforms.test.js +336 -0
  100. package/src/utils/__tests__/token-helpers.test.js +1160 -0
  101. package/src/utils/copy-css-utils-cli.js +13 -1
  102. package/src/utils/css-utils-format-utils.js +105 -176
  103. package/src/utils/figma/__tests__/sync-gradient.test.js +561 -0
  104. package/src/utils/figma/__tests__/token-conversion.test.js +117 -0
  105. package/src/utils/figma/__tests__/token-resolution.test.js +231 -0
  106. package/src/utils/figma/auth.js +355 -0
  107. package/src/utils/figma/constants.js +22 -0
  108. package/src/utils/figma/errors.js +80 -0
  109. package/src/utils/figma/figma-api.js +1069 -0
  110. package/src/utils/figma/get-token.js +348 -0
  111. package/src/utils/figma/sync-components.js +909 -0
  112. package/src/utils/figma/sync-main.js +692 -0
  113. package/src/utils/figma/sync-orchestration.js +683 -0
  114. package/src/utils/figma/sync-primitives.js +230 -0
  115. package/src/utils/figma/sync-semantic.js +1056 -0
  116. package/src/utils/figma/token-conversion.js +340 -0
  117. package/src/utils/figma/token-parsing.js +186 -0
  118. package/src/utils/figma/token-resolution.js +569 -0
  119. package/src/utils/figma/utils.js +199 -0
  120. package/src/utils/sd-build-configs.js +305 -0
  121. package/src/utils/sd-formats.js +948 -0
  122. package/src/utils/sd-transforms.js +165 -0
  123. package/src/utils/token-helpers.js +848 -0
  124. package/tsconfig.json +18 -0
  125. package/vitest.config.js +17 -0
  126. package/.turbo/turbo-build.log +0 -37
  127. package/build/web/core/raw.js +0 -234
  128. package/src/global/primitive/breakpoint.js +0 -19
  129. package/src/global/primitive/color.js +0 -231
  130. package/src/global/primitive/duration.js +0 -16
  131. package/src/global/primitive/font.js +0 -60
  132. package/src/global/primitive/radius.js +0 -31
  133. package/src/global/primitive/size.js +0 -55
  134. package/src/global/primitive/transition.js +0 -16
  135. package/src/theme/core/background.js +0 -170
  136. package/src/theme/core/border.js +0 -103
  137. package/src/theme/core/charts.js +0 -464
  138. package/src/theme/core/component/button.js +0 -708
  139. package/src/theme/core/component/checkbox.js +0 -405
  140. package/src/theme/core/focus.js +0 -35
  141. package/src/theme/core/foreground.js +0 -148
  142. package/src/theme/core/overlay.js +0 -137
  143. package/src/theme/core/shadow.js +0 -29
  144. package/src/theme/core/status.js +0 -49
  145. package/src/theme/core/typography.js +0 -82
  146. 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
+ };