@servicetitan/hammer-token 2.5.0 → 3.0.0

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