@massimo-cassandro/minimo 1.20.7 → 1.20.9

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 (47) hide show
  1. package/design-tokens/README.md +13 -12
  2. package/design-tokens/_src/typography.minimo.tokens.mjs +19 -13
  3. package/design-tokens/tokens-config-sample.mjs +19 -14
  4. package/design-tokens/utilities/build-tokens-src/config.mjs +11 -11
  5. package/design-tokens/utilities/build-tokens-src/formats/css.mjs +19 -2
  6. package/design-tokens/utilities/build-tokens-src/formats/{penpot.mjs → json.mjs} +61 -58
  7. package/design-tokens/utilities/build-tokens-src/platforms.mjs +22 -21
  8. package/design-tokens/utilities/build-tokens.mjs +37 -29
  9. package/index.js +6 -2
  10. package/package.json +1 -1
  11. package/src/css/buttons/buttons.css +1 -1
  12. package/src/custom-properties.css +9 -9
  13. package/src/utilities/dom-builder-helpers/form-tags.js +1 -1
  14. package/design-tokens/tokens/design-tokens/_src/alerts.minimo.tokens.jsonc +0 -28
  15. package/design-tokens/tokens/design-tokens/_src/badge.minimo.tokens.jsonc +0 -42
  16. package/design-tokens/tokens/design-tokens/_src/btn-close.minimo.tokens.jsonc +0 -102
  17. package/design-tokens/tokens/design-tokens/_src/btn-color-themes.minimo.tokens.jsonc +0 -182
  18. package/design-tokens/tokens/design-tokens/_src/btn.minimo.tokens.jsonc +0 -210
  19. package/design-tokens/tokens/design-tokens/_src/color-accent.minimo.tokens.jsonc +0 -16
  20. package/design-tokens/tokens/design-tokens/_src/color-neutral.minimo.tokens.jsonc +0 -18
  21. package/design-tokens/tokens/design-tokens/_src/color-primary.minimo.tokens.jsonc +0 -18
  22. package/design-tokens/tokens/design-tokens/_src/color-secondary.minimo.tokens.jsonc +0 -18
  23. package/design-tokens/tokens/design-tokens/_src/flex-grid.minimo.tokens.jsonc +0 -24
  24. package/design-tokens/tokens/design-tokens/_src/form-validation.minimo.tokens.jsonc +0 -22
  25. package/design-tokens/tokens/design-tokens/_src/form.minimo.tokens.jsonc +0 -366
  26. package/design-tokens/tokens/design-tokens/_src/headings.minimo.tokens.jsonc +0 -70
  27. package/design-tokens/tokens/design-tokens/_src/layout.minimo.tokens.jsonc +0 -28
  28. package/design-tokens/tokens/design-tokens/_src/link.minimo.tokens.jsonc +0 -44
  29. package/design-tokens/tokens/design-tokens/_src/radius.minimo.tokens.jsonc +0 -42
  30. package/design-tokens/tokens/design-tokens/_src/shadows.minimo.tokens.jsonc +0 -19
  31. package/design-tokens/tokens/design-tokens/_src/size.minimo.tokens.jsonc +0 -46
  32. package/design-tokens/tokens/design-tokens/_src/status.minimo.tokens.jsonc +0 -98
  33. package/design-tokens/tokens/design-tokens/_src/switch.minimo.tokens.jsonc +0 -32
  34. package/design-tokens/tokens/design-tokens/_src/table.minimo.tokens.jsonc +0 -84
  35. package/design-tokens/tokens/design-tokens/_src/text.minimo.tokens.jsonc +0 -38
  36. package/design-tokens/tokens/design-tokens/_src/typography.minimo.tokens.jsonc +0 -110
  37. package/design-tokens/tokens/design-tokens/_src/utility.minimo.tokens.jsonc +0 -14
  38. package/design-tokens/tokens/design-tokens/_src/z-index.minimo.tokens.jsonc +0 -18
  39. package/design-tokens/tokens/src/components/inner-nav/inner-nav.minimo.tokens.jsonc +0 -20
  40. package/design-tokens/tokens/src/components/modal-alert/modal-alert.minimo.tokens.jsonc +0 -246
  41. package/design-tokens/tokens/src/components/modal-popup/modal-popup.minimo.tokens.jsonc +0 -128
  42. package/design-tokens/tokens/src/components/sf-macro/sf-macro.minimo.tokens.jsonc +0 -84
  43. package/design-tokens/tokens/src/components/slide-up-down-toggle/slide-up-down-toggle.minimo.tokens.jsonc +0 -14
  44. package/design-tokens/tokens/src/components/snackbar/snackbar.minimo.tokens.jsonc +0 -146
  45. package/design-tokens/tokens/src/components/spinner/spinner.minimo.tokens.jsonc +0 -24
  46. package/design-tokens/tokens/src/components/unsplash-page/unsplash-page.minimo.tokens.jsonc +0 -170
  47. package/design-tokens/tokens/src/web-components/s-datatable-component/simple-datatable.minimo.tokens.jsonc +0 -51
@@ -56,7 +56,7 @@ Read comments inside the sample file for more info.
56
56
 
57
57
  Reads design token source files and generates:
58
58
  - A **CSS file** containing all tokens as custom properties inside `:root { ... }`
59
- - Optionally, one or more **W3C DTCG JSON/JSONC** files to import into Penpot/Figma (use in Figma requires a plugin).
59
+ - Optionally, one or more **W3C DTCG JSON/JSONC** files, independent of any specific consuming tool (usable in Penpot, Figma via plugin, Token Studio, ...).
60
60
 
61
61
  #### Usage
62
62
 
@@ -129,32 +129,33 @@ Typography tokens are expanded into multiple properties:
129
129
 
130
130
  The generated file is linted and auto-fixed with stylelint using the config specified in `stylelintConfigPath`.
131
131
 
132
- #### Penpot output
132
+ #### JSON output
133
133
 
134
- When `penpotBuildPath` is set, the script also generates W3C DTCG token files for import into Penpot via the [Design Tokens plugin](https://penpot.app/penpot-files/design-tokens).
134
+ When `jsonBuildPath` is set, the script also generates W3C DTCG token files, independent of any specific consuming tool (e.g. importable into Penpot via the [Design Tokens plugin](https://penpot.app/penpot-files/design-tokens), into Figma via a compatible plugin, or into Token Studio).
135
135
 
136
- **Single file** (`penpotDestFile` is a string):
136
+ **Single file** (`jsonDestFile` is a string):
137
137
  ```
138
- penpotBuildPath/tokens.jsonc ← all tokens in one file
138
+ jsonBuildPath/tokens.jsonc ← all tokens in one file
139
139
  ```
140
140
 
141
- **One file per source** (`penpotDestFile` is `null` or omitted):
141
+ **One file per source** (`jsonDestFile` is `null` or omitted):
142
142
  ```
143
- penpotBuildPath/colors.tokens.jsonc
144
- penpotBuildPath/spacing.tokens.jsonc
145
- penpotBuildPath/typography.tokens.jsonc
143
+ jsonBuildPath/colors.tokens.jsonc
144
+ jsonBuildPath/spacing.tokens.jsonc
145
+ jsonBuildPath/typography.tokens.jsonc
146
146
  ```
147
+ All files are written flat in `jsonBuildPath`, regardless of the subdirectory structure of the source files — no subdirectories are created. If two source files with different paths would produce the same output filename, the build fails with an error listing the colliding files instead of silently overwriting one with the other.
147
148
 
148
- The `penpotFormat` option controls the output:
149
+ The `jsonFormat` option controls the output:
149
150
 
150
151
  | Value | Extension | Disclaimer header |
151
152
  |---|---|---|
152
153
  | `'jsonc'` | `.jsonc` | Yes |
153
154
  | `'json'` | `.json` | No |
154
155
 
155
- Alias references (`{color.brand.primary}`) are preserved in the output so Penpot maintains token links between primitive and semantic tokens.
156
+ Alias references (`{color.brand.primary}`) are preserved in the output so consuming tools maintain token links between primitive and semantic tokens.
156
157
 
157
- > Penpot token files intentionally use a minimal set of transforms (no px→rem conversion, no shorthand expansion) because Penpot expects original values and structured objects, not resolved CSS strings.
158
+ > JSON token files intentionally use a minimal set of transforms (no px→rem conversion, no shorthand expansion) because consuming tools generally expect original values and structured objects, not resolved CSS strings.
158
159
 
159
160
  ---
160
161
 
@@ -12,43 +12,49 @@ export default {
12
12
  size: {
13
13
  base: {
14
14
  $value: 'clamp(.9rem, .6538rem + .3846vw, 1rem)',
15
- $type: 'string'
15
+ $type: 'string',
16
+ $description: '64 to 90 rem: https://clamp.font-size.app/?config=eyJyb290IjoiMTYiLCJtaW5XaWR0aCI6IjY0cmVtIiwibWF4V2lkdGgiOiI5MHJlbSIsIm1pbkZvbnRTaXplIjoiLjlyZW0iLCJtYXhGb250U2l6ZSI6IjFyZW0ifQ%3D%3D'
16
17
  },
17
18
  xxs: {
18
19
  $value: 'clamp(.6rem, .3538rem + .3846vw, .7rem)',
19
- $type: 'string'
20
+ $type: 'string',
21
+ $description: '64-90rem: https://clamp.font-size.app/?config=eyJyb290IjoiMTYiLCJtaW5XaWR0aCI6IjY0cmVtIiwibWF4V2lkdGgiOiI5MHJlbSIsIm1pbkZvbnRTaXplIjoiLjZyZW0iLCJtYXhGb250U2l6ZSI6Ii43cmVtIn0%3D'
20
22
  },
21
23
  xs: {
22
24
  $value: 'clamp(.7rem, .4538rem + .3846vw, .8rem)',
23
- $type: 'string'
25
+ $type: 'string',
26
+ $description: '64-90rem: https://clamp.font-size.app/?config=eyJyb290IjoiMTYiLCJtaW5XaWR0aCI6IjY0cmVtIiwibWF4V2lkdGgiOiI5MHJlbSIsIm1pbkZvbnRTaXplIjoiLjdyZW0iLCJtYXhGb250U2l6ZSI6Ii44cmVtIn0%3D'
24
27
  },
25
28
  sm: {
26
29
  $value: 'clamp(.8rem, .5538rem + .3846vw, .9rem)',
27
- $type: 'string'
30
+ $type: 'string',
31
+ $description: '64-90rem: https://clamp.font-size.app/?config=eyJyb290IjoiMTYiLCJtaW5XaWR0aCI6IjY0cmVtIiwibWF4V2lkdGgiOiI5MHJlbSIsIm1pbkZvbnRTaXplIjoiLjhyZW0iLCJtYXhGb250U2l6ZSI6Ii45cmVtIn0%3D'
28
32
  },
29
- // lead: {
30
- // $value: 'clamp(1rem, 0.6923rem + 0.4808vw, 1.125rem)',
31
- // $type: 'string'
32
- // },
33
+
33
34
  md: {
34
35
  $value: 'clamp(1.05rem, .6808rem + .5769vw, 1.2rem)',
35
- $type: 'string'
36
+ $type: 'string',
37
+ $description: '64-90rem: https://clamp.font-size.app/?config=eyJyb290IjoiMTYiLCJtaW5XaWR0aCI6IjY0cmVtIiwibWF4V2lkdGgiOiI5MHJlbSIsIm1pbkZvbnRTaXplIjoiMS4wNXJlbSIsIm1heEZvbnRTaXplIjoiMS4ycmVtIn0%3D'
36
38
  },
37
39
  lg: {
38
40
  $value: 'clamp(1.2rem, .7077rem + .7692vw, 1.4rem)',
39
- $type: 'string'
41
+ $type: 'string',
42
+ $description: '64-90rem: https://clamp.font-size.app/?config=eyJyb290IjoiMTYiLCJtaW5XaWR0aCI6IjY0cmVtIiwibWF4V2lkdGgiOiI5MHJlbSIsIm1pbkZvbnRTaXplIjoiMS4ycmVtIiwibWF4Rm9udFNpemUiOiIxLjRyZW0ifQ%3D%3D'
40
43
  },
41
44
  xl: {
42
45
  $value: 'clamp(1.6rem, 1.1077rem + .7692vw, 1.8rem)',
43
- $type: 'string'
46
+ $type: 'string',
47
+ $description: '64-90rem: https://clamp.font-size.app/?config=eyJyb290IjoiMTYiLCJtaW5XaWR0aCI6IjY0cmVtIiwibWF4V2lkdGgiOiI5MHJlbSIsIm1pbkZvbnRTaXplIjoiMS42cmVtIiwibWF4Rm9udFNpemUiOiIxLjhyZW0ifQ%3D%3D'
44
48
  },
45
49
  '2xl': {
46
50
  $value: 'clamp(2rem, .7692rem + 1.9231vw, 2.5rem)',
47
- $type: 'string'
51
+ $type: 'string',
52
+ $description: '64-90rem: https://clamp.font-size.app/?config=eyJyb290IjoiMTYiLCJtaW5XaWR0aCI6IjY0cmVtIiwibWF4V2lkdGgiOiI5MHJlbSIsIm1pbkZvbnRTaXplIjoiMnJlbSIsIm1heEZvbnRTaXplIjoiMi41cmVtIn0%3D'
48
53
  },
49
54
  '3xl': {
50
55
  $value: 'clamp(2.2rem, .2308rem + 3.0769vw, 3rem)',
51
- $type: 'string'
56
+ $type: 'string',
57
+ $description: '64-90rem: https://clamp.font-size.app/?config=eyJyb290IjoiMTYiLCJtaW5XaWR0aCI6IjY0cmVtIiwibWF4V2lkdGgiOiI5MHJlbSIsIm1pbkZvbnRTaXplIjoiMi4ycmVtIiwibWF4Rm9udFNpemUiOiIzcmVtIn0%3D'
52
58
  }
53
59
  },
54
60
 
@@ -80,11 +80,12 @@ const config = {
80
80
 
81
81
 
82
82
  // ---------------------------------------------------------------------------
83
- // PENPOT / FIGMA
83
+ // JSON TOKENS OUTPUT
84
84
  // ---------------------------------------------------------------------------
85
- // Optional design tokens output
86
- // Generates one or more json(c) files in addition to the CSS output file, for use
87
- // in Penpot or Figma projects or as an alternative to the .mjs source files
85
+ // Optional design tokens output, independent of any specific consuming tool
86
+ // Generates one or more json(c) files in addition to the CSS output file —
87
+ // usable in Penpot, Figma (via plugin), Token Studio, or as an alternative
88
+ // to the .mjs source files
88
89
  // ---------------------------------------------------------------------------
89
90
 
90
91
  // How to handle math expressions in dimension token $values (e.g. "{size.base} * .25"):
@@ -92,21 +93,25 @@ const config = {
92
93
  // 'calc' — wrap in CSS calc(): "calc({size.base} * .25)"
93
94
  // 'resolve' — (default) evaluate numerically, inheriting the unit from the first referenced token
94
95
  // e.g. {size.base} = 16px, "{size.base} * .25" -> "4px"
95
- penpotExpressions: 'resolve',
96
+ jsonExpression: 'resolve',
96
97
 
97
- // Output format for Penpot token files: 'jsonc' or 'json'
98
+ // Output format for the JSON token files: 'jsonc' or 'json'
98
99
  // 'jsonc' -> adds a generated-file disclaimer header, uses .jsonc extension
99
100
  // 'json' -> plain JSON, no disclaimer
100
- penpotFormat: 'jsonc',
101
+ jsonFormat: 'jsonc',
101
102
 
102
- // Output directory for the Penpot design token files.
103
+ // Output directory for the JSON design token files.
103
104
  // Set to null (or omit) to disable json output entirely.
104
- penpotBuildPath: null, // or './path/to/generated/design-tokens-dir',
105
-
106
- // Base name (without extension) for the aggregated Penpot output file.
107
- // The extension is added automatically based on penpotFormat.
108
- // Set to null (or omit) to generate one file per .mjs source file instead.
109
- penpotDestFile: null, // or 'tokens' (or else),
105
+ // In multi-file mode (jsonDestFile: null), all files are written flat in
106
+ // this directory (no subdirectories mirroring the source tree); the build
107
+ // fails with an error if two source files would produce the same filename.
108
+ jsonBuildPath: null, // or './path/to/generated/design-tokens-dir',
109
+
110
+ // Base name (without extension) for the aggregated JSON output file.
111
+ // The extension is added automatically based on jsonFormat.
112
+ // Set to null (or omit) to generate one file per .mjs source file instead
113
+ // (all placed flat in jsonBuildPath — see jsonBuildPath above).
114
+ jsonDestFile: null, // or 'tokens' (or else),
110
115
 
111
116
 
112
117
  // ---------------------------------------------------------------------------
@@ -51,29 +51,29 @@ export const destFile = buildConfig.destFile;
51
51
  // Stylelint
52
52
  export const stylelintConfigPath = resolveFromConfig(buildConfig.stylelintConfigPath);
53
53
 
54
- // Penpot — platform is active only if penpotBuildPath is set in the config
55
- const penpotBuildPathRaw = buildConfig.penpotBuildPath ?? null;
56
- export const penpotBuildPath = penpotBuildPathRaw ? resolveFromConfig(penpotBuildPathRaw) : null;
54
+ // JSON — platform is active only if jsonBuildPath is set in the config
55
+ const jsonBuildPathRaw = buildConfig.jsonBuildPath ?? null;
56
+ export const jsonBuildPath = jsonBuildPathRaw ? resolveFromConfig(jsonBuildPathRaw) : null;
57
57
 
58
- // penpotDestFile: base name (no extension) for the aggregated output file.
58
+ // jsonDestFile: base name (no extension) for the aggregated output file.
59
59
  // null → one file per source (mirrors the source structure)
60
60
  // string → single aggregated file (e.g. 'tokens' → 'tokens.jsonc')
61
- // The extension is added automatically based on penpotFormat.
62
- export const penpotDestFile = buildConfig.penpotDestFile ?? null;
61
+ // The extension is added automatically based on jsonFormat.
62
+ export const jsonDestFile = buildConfig.jsonDestFile ?? null;
63
63
 
64
- // penpotFormat: controls output format for Penpot token files.
64
+ // jsonFormat: controls output format for the generated JSON token files.
65
65
  // 'jsonc' → .jsonc extension + generated-file disclaimer header
66
66
  // 'json' → plain .json, no disclaimer
67
67
  // Any value other than 'jsonc' is normalised to 'json'.
68
- export const penpotFormat = buildConfig.penpotFormat === 'jsonc' ? 'jsonc' : 'json';
68
+ export const jsonFormat = buildConfig.jsonFormat === 'jsonc' ? 'jsonc' : 'json';
69
69
 
70
- // penpotExpressions: controls how math expressions in dimension token values are handled.
70
+ // jsonExpression: controls how math expressions in dimension token values are handled.
71
71
  // 'keep' (default) — write the expression as-is
72
72
  // 'calc' — wrap in CSS calc()
73
73
  // 'resolve' — evaluate numerically; unit inherited from the first referenced token
74
74
  const VALID_EXPR_MODES = ['keep', 'calc', 'resolve'];
75
- export const penpotExpressions = VALID_EXPR_MODES.includes(buildConfig.penpotExpressions)
76
- ? buildConfig.penpotExpressions
75
+ export const jsonExpression = VALID_EXPR_MODES.includes(buildConfig.jsonExpression)
76
+ ? buildConfig.jsonExpression
77
77
  : 'resolve';
78
78
 
79
79
  // pxToRem: if true (default), dimension token values expressed in px are
@@ -131,6 +131,17 @@ const buildTypography = (tokenName, original, resolveRefs) => {
131
131
  return lines.join('\n');
132
132
  };
133
133
 
134
+ // Builds a trailing ` /* ... */` comment from a token's $description, to be
135
+ // appended at the end of its generated custom prop declaration (same line).
136
+ // Newlines are collapsed (declarations are parsed one line at a time, see
137
+ // parseCustomProps in ../merge-css.mjs) and `*/` is escaped so the comment
138
+ // can't be closed early by its own content.
139
+ const buildDescriptionComment = (description) => {
140
+ if (!description) return '';
141
+ const flat = String(description).replace(/\s*\n\s*/g, ' ').trim().replace(/\*\//g, '* /');
142
+ return flat ? ` /* ${flat} */` : '';
143
+ };
144
+
134
145
  // Builds a customPropsGroups title comment, e.g.:
135
146
  // /* ****** LAYOUT ****** */
136
147
  // Dashes fill out a fixed total width so titles line up regardless of name length.
@@ -185,9 +196,15 @@ StyleDictionary.registerFormat({
185
196
  .flatMap((token) => {
186
197
  const type = token.$type ?? token.type;
187
198
  const orig = token.original?.$value ?? token.original?.value;
199
+ const description = token.$description ?? token.description ?? token.comment;
200
+ const commentSuffix = buildDescriptionComment(description);
188
201
 
189
202
  if (type === 'typography') {
190
- return [buildTypography(token.name, orig ?? {}, resolveRefs)];
203
+ const typographyLines = buildTypography(token.name, orig ?? {}, resolveRefs)
204
+ .split('\n')
205
+ .map((line) => `${line}${commentSuffix}`)
206
+ .join('\n');
207
+ return [typographyLines];
191
208
  }
192
209
 
193
210
  let value;
@@ -230,7 +247,7 @@ StyleDictionary.registerFormat({
230
247
  value = String(token.$value ?? token.value);
231
248
  }
232
249
 
233
- return [` --${token.name}: ${value};`];
250
+ return [` --${token.name}: ${value};${commentSuffix}`];
234
251
  });
235
252
 
236
253
  // Build a name → declaration-tail map from the declarations generated
@@ -1,30 +1,33 @@
1
- // build-tokens-src/formats/penpot.mjs
2
- // Registers the 'json/penpot' format and exports buildPenpotFiles() and
1
+ // build-tokens-src/formats/json.mjs
2
+ // Registers the 'json/tokens' format and exports buildJsonFiles() and
3
3
  // collectConcreteFilePaths().
4
4
  //
5
- // The format produces a W3C DTCG nested file importable into Penpot via the
6
- // "Design Tokens" plugin (also compatible with Token Studio and Supernova).
5
+ // The format produces a W3C DTCG nested JSON(C) file, independent of any
6
+ // specific consuming tool (usable in Penpot, Figma via plugin, Token Studio,
7
+ // Supernova, or any other DTCG-compatible tool).
7
8
  //
8
- // Output format is controlled by the penpotFormat option in the project config:
9
+ // Output format is controlled by the jsonFormat option in the project config:
9
10
  // 'jsonc' -> .jsonc extension + generated-file disclaimer header
10
11
  // 'json' -> plain .json, no disclaimer
11
12
  //
12
- // buildPenpotFiles() builds the `files` array for the penpot platform:
13
- // penpotDestFile set -> single aggregated file
14
- // penpotDestFile null -> one file per concrete source file, mirroring the
15
- // subdirectory structure of the source tree
13
+ // buildJsonFiles() builds the `files` array for the json platform:
14
+ // jsonDestFile set -> single aggregated file
15
+ // jsonDestFile null -> one file per concrete source file, all placed flat
16
+ // in jsonBuildPath (no subdirectories) — see the
17
+ // collision check in buildJsonFiles()
16
18
  //
17
- // ── Expression handling (penpotExpressions option) ───────────────────────────
19
+ // ── Expression handling (jsonExpression option) ─────────────────────────────
18
20
  //
19
21
  // Dimension tokens may carry math expressions in their $value, e.g.:
20
22
  // { $type: "dimension", $value: "{size.base} * .25" }
21
23
  //
22
- // The penpotExpressions config option controls how these are handled:
24
+ // The jsonExpression config option controls how these are handled:
23
25
  //
24
- // 'keep' (default) — write the expression as-is; Penpot or the consuming
25
- // tool is expected to evaluate it.
26
+ // 'keep' (default) — write the expression as-is; the consuming tool is
27
+ // expected to evaluate it.
26
28
  // 'calc' — wrap in a CSS calc(): "calc({size.base} * .25)"
27
- // aliases are left as {references} for Penpot to resolve.
29
+ // aliases are left as {references} for the consuming
30
+ // tool to resolve.
28
31
  // 'resolve' — evaluate the expression numerically and write a concrete value.
29
32
  // The unit is inherited from the first dimension token
30
33
  // referenced in the expression (e.g. {size.base} = 16px
@@ -34,7 +37,7 @@
34
37
  import StyleDictionary from 'style-dictionary';
35
38
  import path from 'node:path';
36
39
 
37
- // Disclaimer prepended to every generated file when penpotFormat is 'jsonc'
40
+ // Disclaimer prepended to every generated file when jsonFormat is 'jsonc'
38
41
  const DISCLAIMER = [
39
42
  '// -----------------------------------------------------------------------',
40
43
  '// Generated file — do not edit manually.',
@@ -126,10 +129,10 @@ const toCalc = (orig) => `calc(${orig})`;
126
129
  // ── Format ───────────────────────────────────────────────────────────────────
127
130
 
128
131
  StyleDictionary.registerFormat({
129
- name: 'json/penpot',
132
+ name: 'json/tokens',
130
133
  format: ({ dictionary, options }) => {
131
134
  const root = {};
132
- const expressionMode = options.penpotExpressions ?? 'keep'; // 'keep' | 'calc' | 'resolve'
135
+ const expressionMode = options.jsonExpression ?? 'keep'; // 'keep' | 'calc' | 'resolve'
133
136
  const tokenMap = (expressionMode === 'resolve') ? makeTokenMap(dictionary) : null;
134
137
 
135
138
  for (const token of dictionary.allTokens) {
@@ -161,7 +164,7 @@ StyleDictionary.registerFormat({
161
164
  } else {
162
165
  // Resolution failed — keep the original and emit a warning
163
166
  // eslint-disable-next-line no-console
164
- console.warn(`[build-tokens] penpot: could not resolve expression "${orig}" for token "${token.path.join('.')}". Keeping original.`);
167
+ console.warn(`[build-tokens] json: could not resolve expression "${orig}" for token "${token.path.join('.')}". Keeping original.`);
165
168
  value = orig;
166
169
  }
167
170
  } else if (expressionMode === 'calc') {
@@ -173,7 +176,7 @@ StyleDictionary.registerFormat({
173
176
  }
174
177
  } else {
175
178
  // Non-expression value: preserve {references} wherever they appear.
176
- // - Pure alias: "{some.token}" → kept as-is for Penpot token links
179
+ // - Pure alias: "{some.token}" → kept as-is to preserve token links
177
180
  // - CSS function: "color-mix(in srgb, {some.token} 60%, #000)" → kept as-is
178
181
  // - Plain value: "#ff0000", "16px", … → use the resolved $value
179
182
  const containsRef = typeof orig === 'string' && orig.includes('{');
@@ -193,23 +196,6 @@ StyleDictionary.registerFormat({
193
196
  },
194
197
  });
195
198
 
196
- // ── commonDir ────────────────────────────────────────────────────────────────
197
- // Returns the longest common directory prefix shared by all given file paths.
198
- // Used to compute relative paths that mirror the source subdirectory structure.
199
-
200
- const commonDir = (filePaths) => {
201
- if (filePaths.length === 0) return '';
202
- if (filePaths.length === 1) return path.dirname(filePaths[0]);
203
-
204
- const dirs = filePaths.map(fp => path.dirname(path.resolve(fp)));
205
- const parts = dirs[0].split(path.sep);
206
-
207
- let i = 0;
208
- while (i < parts.length && dirs.every(d => d.split(path.sep)[i] === parts[i])) i++;
209
-
210
- return parts.slice(0, i).join(path.sep);
211
- };
212
-
213
199
  // ── collectConcreteFilePaths ──────────────────────────────────────────────────
214
200
  //
215
201
  // Extracts the unique concrete file paths from a Style Dictionary instance.
@@ -224,42 +210,59 @@ export const collectConcreteFilePaths = async (sd) => {
224
210
  return [...paths].sort();
225
211
  };
226
212
 
227
- // ── buildPenpotFiles ──────────────────────────────────────────────────────────
213
+ // ── buildJsonFiles ──────────────────────────────────────────────────────────
228
214
  //
229
215
  // @param {string[]} concreteFilePaths From collectConcreteFilePaths()
230
- // @param {string|null} penpotDestFile Aggregated file base name, or null
231
- // @param {'json'|'jsonc'} penpotFormat Output format
232
- // @param {'keep'|'calc'|'resolve'} penpotExpressions Expression handling mode
216
+ // @param {string|null} jsonDestFile Aggregated file base name, or null
217
+ // @param {'json'|'jsonc'} jsonFormat Output format
218
+ // @param {'keep'|'calc'|'resolve'} jsonExpression Expression handling mode
233
219
  // @returns {object[]} File descriptors for the Style Dictionary platform
234
220
 
235
- export const buildPenpotFiles = (
221
+ export const buildJsonFiles = (
236
222
  concreteFilePaths,
237
- penpotDestFile,
238
- penpotFormat = 'json',
239
- penpotExpressions = 'keep'
223
+ jsonDestFile,
224
+ jsonFormat = 'json',
225
+ jsonExpression = 'keep'
240
226
  ) => {
241
- const ext = penpotFormat === 'jsonc' ? '.jsonc' : '.json';
242
- const jsonc = penpotFormat === 'jsonc';
227
+ const ext = jsonFormat === 'jsonc' ? '.jsonc' : '.json';
228
+ const jsonc = jsonFormat === 'jsonc';
243
229
 
244
- if (penpotDestFile) {
230
+ if (jsonDestFile) {
245
231
  return [{
246
- destination: penpotDestFile + ext,
247
- format: 'json/penpot',
248
- options: { jsonc, penpotExpressions },
232
+ destination: jsonDestFile + ext,
233
+ format: 'json/tokens',
234
+ options: { jsonc, jsonExpression },
249
235
  }];
250
236
  }
251
237
 
252
- const sourceRoot = commonDir(concreteFilePaths);
238
+ // One file per source, all placed flat in jsonBuildPath — no subdirectories
239
+ // mirroring the source tree. Since source files can share a basename across
240
+ // different directories (e.g. two components each with their own
241
+ // <name>.minimo.tokens.mjs), fail loudly on collisions instead of letting
242
+ // one silently overwrite the other.
243
+ const pathsByDestName = new Map();
244
+ for (const filePath of concreteFilePaths) {
245
+ const destName = path.basename(filePath).replace(/\.[^.]+$/, '') + ext;
246
+ if (!pathsByDestName.has(destName)) pathsByDestName.set(destName, []);
247
+ pathsByDestName.get(destName).push(filePath);
248
+ }
253
249
 
254
- return concreteFilePaths.map((filePath) => {
255
- const absPath = path.resolve(filePath);
256
- const relPath = path.relative(sourceRoot, absPath);
257
- const destName = relPath.replace(/\.[^.]+$/, '') + ext;
250
+ const collisions = [...pathsByDestName.entries()].filter(([, paths]) => paths.length > 1);
251
+ if (collisions.length > 0) {
252
+ const details = collisions
253
+ .map(([destName, paths]) => ` "${destName}" <- ${paths.join(', ')}`)
254
+ .join('\n');
255
+ throw new Error(
256
+ `[build-tokens] json: output filename collision — these source files would overwrite each other (json output files are flattened into a single directory):\n${details}`
257
+ );
258
+ }
258
259
 
260
+ return concreteFilePaths.map((filePath) => {
261
+ const absPath = path.resolve(filePath);
259
262
  return {
260
- destination: destName,
261
- format: 'json/penpot',
262
- options: { jsonc, penpotExpressions },
263
+ destination: path.basename(filePath).replace(/\.[^.]+$/, '') + ext,
264
+ format: 'json/tokens',
265
+ options: { jsonc, jsonExpression },
263
266
  filter: (token) => {
264
267
  const fp = token.filePath ? path.resolve(token.filePath) : '';
265
268
  return fp === absPath;
@@ -1,10 +1,10 @@
1
1
  // build-tokens-src/platforms.mjs
2
2
  // Builds and returns the `platforms` object for the Style Dictionary config.
3
3
  //
4
- // The `penpot` platform is included only if penpotBuildPath is set.
5
- // Output format (json/jsonc) and file splitting are delegated to buildPenpotFiles().
4
+ // The `json` platform is included only if jsonBuildPath is set.
5
+ // Output format (json/jsonc) and file splitting are delegated to buildJsonFiles().
6
6
 
7
- import { buildPenpotFiles } from './formats/penpot.mjs';
7
+ import { buildJsonFiles } from './formats/json.mjs';
8
8
 
9
9
  /** @typedef {import('style-dictionary/types').PlatformConfig} PlatformConfig */
10
10
 
@@ -24,10 +24,11 @@ export const CSS_TRANSFORMS = [
24
24
  'typography/css',
25
25
  ];
26
26
 
27
- // Transforms applied to the Penpot platform.
28
- // Kept minimal on purpose: Penpot expects original values (e.g. "16px" not "1rem")
29
- // and alias references ({...}) must be preserved to maintain token links.
30
- const PENPOT_TRANSFORMS = [
27
+ // Transforms applied to the JSON platform.
28
+ // Kept minimal on purpose: consuming tools expect original values (e.g. "16px"
29
+ // not "1rem") and alias references ({...}) must be preserved to maintain
30
+ // token links.
31
+ const JSON_TRANSFORMS = [
31
32
  'attribute/cti',
32
33
  'name/kebab',
33
34
  'color/css-modern',
@@ -37,13 +38,13 @@ const PENPOT_TRANSFORMS = [
37
38
  * @param {object} opts
38
39
  * @param {string} opts.buildPath Absolute path for CSS output directory
39
40
  * @param {string} opts.destFile CSS output filename (e.g. "tokens.css")
40
- * @param {string|null} opts.penpotBuildPath Absolute path for Penpot output (null = disabled)
41
- * @param {string|null} opts.penpotDestFile Base name for aggregated file; null = one file per source
42
- * @param {'json'|'jsonc'} opts.penpotFormat Output format for Penpot files
41
+ * @param {string|null} opts.jsonBuildPath Absolute path for JSON output (null = disabled)
42
+ * @param {string|null} opts.jsonDestFile Base name for aggregated file; null = one file per source
43
+ * @param {'json'|'jsonc'} opts.jsonFormat Output format for JSON files
43
44
  * @param {string[]} opts.concreteFilePaths Concrete (expanded) source file paths for multi-file mode.
44
45
  * Collected from sd.allTokens after SD initialisation.
45
- * Ignored when penpotDestFile is set.
46
- * @param {'keep'|'calc'|'resolve'} opts.penpotExpressions How to handle math expressions in dimension tokens.
46
+ * Ignored when jsonDestFile is set.
47
+ * @param {'keep'|'calc'|'resolve'} opts.jsonExpression How to handle math expressions in dimension tokens.
47
48
  * @param {{name: string, prefixes: string[]}[]} opts.customPropsGroups Named groups of name prefixes, moved to the top of the CSS output in list order.
48
49
  * @param {boolean} opts.pxToRem If false, skips the px→rem transform on the CSS platform. Default: true.
49
50
  * @returns {Record<string, PlatformConfig>} platforms object ready for Style Dictionary config
@@ -51,10 +52,10 @@ const PENPOT_TRANSFORMS = [
51
52
  export const buildPlatforms = ({
52
53
  buildPath,
53
54
  destFile,
54
- penpotBuildPath,
55
- penpotDestFile,
56
- penpotFormat,
57
- penpotExpressions = 'keep',
55
+ jsonBuildPath,
56
+ jsonDestFile,
57
+ jsonFormat,
58
+ jsonExpression = 'keep',
58
59
  concreteFilePaths = [],
59
60
  customPropsGroups = [],
60
61
  pxToRem = true,
@@ -82,11 +83,11 @@ export const buildPlatforms = ({
82
83
  },
83
84
  };
84
85
 
85
- if (penpotBuildPath) {
86
- platforms.penpot = {
87
- buildPath: penpotBuildPath + '/',
88
- transforms: PENPOT_TRANSFORMS,
89
- files: buildPenpotFiles(concreteFilePaths, penpotDestFile, penpotFormat, penpotExpressions),
86
+ if (jsonBuildPath) {
87
+ platforms.json = {
88
+ buildPath: jsonBuildPath + '/',
89
+ transforms: JSON_TRANSFORMS,
90
+ files: buildJsonFiles(concreteFilePaths, jsonDestFile, jsonFormat, jsonExpression),
90
91
  };
91
92
  }
92
93