@commercetools/nimbus-design-token-ts-plugin 2.7.0 → 2.8.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.
@@ -37,7 +37,7 @@ function createPlugin(_ts, info, tokenData) {
37
37
  for (const entry of original.entries) {
38
38
  const cssValue = values[entry.name];
39
39
  if (cssValue !== undefined) {
40
- entry.labelDetails = { detail: ` = ${cssValue}` };
40
+ entry.labelDetails = { detail: ` ${cssValue}` };
41
41
  }
42
42
  }
43
43
  return original;
@@ -0,0 +1,43 @@
1
+ {
2
+ "semanticColors": {
3
+ "bg": "#fff / hsl(0, 0%, 7%) (d)",
4
+ "fg": "hsl(0, 0%, 13%) / hsl(0, 0%, 93%) (d)",
5
+ "border": "hsl(0, 0%, 85%) / hsl(0, 0%, 23%) (d)",
6
+ "border.muted": "hsl(0, 0%, 81%) / hsl(0, 0%, 28%) (d)",
7
+ "border.subtle": "hsl(0, 0%, 85%) / hsl(0, 0%, 23%) (d)",
8
+ "border.emphasized": "hsl(0, 0%, 73%) / hsl(0, 0%, 38%) (d)",
9
+ "border.inverted": "hsl(0, 0%, 55%) / hsl(0, 0%, 43%) (d)",
10
+ "border.critical": "hsl(359, 70%, 74%) / hsl(358, 45%, 49%) (d)",
11
+ "border.warning": "hsl(38, 75%, 55%) / hsl(36, 60%, 35%) (d)",
12
+ "border.positive": "hsl(131, 38%, 56%) / hsl(131, 32%, 36%) (d)",
13
+ "border.info": "hsl(206, 82%, 65%) / hsl(211, 65%, 45%) (d)"
14
+ },
15
+ "textStyles": {
16
+ "2xs": "fontSize: 10px, lineHeight: 14px",
17
+ "xs": "fontSize: 12px, lineHeight: 18px",
18
+ "sm": "fontSize: 14px, lineHeight: 22px",
19
+ "md": "fontSize: 16px, lineHeight: 22px",
20
+ "lg": "fontSize: 18px, lineHeight: 24px",
21
+ "xl": "fontSize: 20px, lineHeight: 28px",
22
+ "2xl": "fontSize: 24px, lineHeight: 28px",
23
+ "3xl": "fontSize: 30px, lineHeight: 36px",
24
+ "4xl": "fontSize: 36px, lineHeight: 44px",
25
+ "5xl": "fontSize: 48px, lineHeight: 60px",
26
+ "6xl": "fontSize: 60px, lineHeight: 72px",
27
+ "7xl": "fontSize: 72px, lineHeight: 92px",
28
+ "caption": "fontSize: 12px, lineHeight: 18px",
29
+ "detail": "fontSize: 14px, lineHeight: 22px",
30
+ "body": "fontSize: 16px, lineHeight: 26px"
31
+ },
32
+ "layerStyles": {
33
+ "disabled": "opacity: 0.5, cursor: not-allowed",
34
+ "focusRing": "outlineWidth: var(--focus-ring-width), outlineColor: var(--focus-ring-color), outlineStyle: var(--focus-ring-style), outlineOffset: var(--focus-ring-offset)"
35
+ },
36
+ "letterSpacing": {
37
+ "tighter": "-5%",
38
+ "tight": "-2.5%",
39
+ "wide": "2.5%",
40
+ "wider": "5%",
41
+ "widest": "10%"
42
+ }
43
+ }
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * TypeScript Language Service Plugin entry point.
3
3
  *
4
- * Shows design token CSS values (e.g., "= 24px") next to token names
4
+ * Shows design token CSS values (e.g., "24px") next to token names
5
5
  * in autocomplete when editing Chakra styled-system properties.
6
6
  */
7
7
  import type typescript from "typescript";
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  /**
3
3
  * TypeScript Language Service Plugin entry point.
4
4
  *
5
- * Shows design token CSS values (e.g., "= 24px") next to token names
5
+ * Shows design token CSS values (e.g., "24px") next to token names
6
6
  * in autocomplete when editing Chakra styled-system properties.
7
7
  */
8
8
  const token_data_1 = require("./token-data");
@@ -1,6 +1,9 @@
1
1
  /**
2
2
  * Loads design token data from @commercetools/nimbus-tokens and builds
3
3
  * lookup maps for enriching autocomplete with CSS values.
4
+ *
5
+ * Also loads a generated mapping for tokens that aren't simple key→string pairs:
6
+ * semantic colors (bg, fg, border.*), textStyles, layerStyles, letterSpacing.
4
7
  */
5
8
  /** category name → { token name → CSS value string } */
6
9
  export type CategoryValues = Record<string, Record<string, string>>;
@@ -2,6 +2,9 @@
2
2
  /**
3
3
  * Loads design token data from @commercetools/nimbus-tokens and builds
4
4
  * lookup maps for enriching autocomplete with CSS values.
5
+ *
6
+ * Also loads a generated mapping for tokens that aren't simple key→string pairs:
7
+ * semantic colors (bg, fg, border.*), textStyles, layerStyles, letterSpacing.
5
8
  */
6
9
  Object.defineProperty(exports, "__esModule", { value: true });
7
10
  exports.loadTokenData = loadTokenData;
@@ -42,6 +45,22 @@ function loadDesignTokens() {
42
45
  }
43
46
  return undefined;
44
47
  }
48
+ /**
49
+ * Load the generated token mapping JSON from the same directory as the compiled plugin.
50
+ * Falls back to undefined if the file doesn't exist (graceful degradation).
51
+ */
52
+ function loadGeneratedMapping() {
53
+ try {
54
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
55
+ const path = require("path");
56
+ const mappingPath = path.join(__dirname, "generated-token-mapping.json");
57
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
58
+ return require(mappingPath);
59
+ }
60
+ catch {
61
+ return undefined;
62
+ }
63
+ }
45
64
  function loadTokenData() {
46
65
  try {
47
66
  const tokens = loadDesignTokens();
@@ -162,6 +181,34 @@ function loadTokenData() {
162
181
  categorySets["zIndex"] = new Set(Object.keys(values));
163
182
  }
164
183
  }
184
+ // Load generated token mapping for semantic colors, composite tokens, etc.
185
+ const mapping = loadGeneratedMapping();
186
+ if (mapping) {
187
+ // Merge semantic colors into existing colors category
188
+ if (mapping.semanticColors) {
189
+ if (!categoryValues["colors"]) {
190
+ categoryValues["colors"] = {};
191
+ }
192
+ Object.assign(categoryValues["colors"], mapping.semanticColors);
193
+ categorySets["colors"] = new Set(Object.keys(categoryValues["colors"]));
194
+ }
195
+ // Add textStyles category
196
+ if (mapping.textStyles && Object.keys(mapping.textStyles).length > 0) {
197
+ categoryValues["textStyles"] = mapping.textStyles;
198
+ categorySets["textStyles"] = new Set(Object.keys(mapping.textStyles));
199
+ }
200
+ // Add layerStyles category
201
+ if (mapping.layerStyles && Object.keys(mapping.layerStyles).length > 0) {
202
+ categoryValues["layerStyles"] = mapping.layerStyles;
203
+ categorySets["layerStyles"] = new Set(Object.keys(mapping.layerStyles));
204
+ }
205
+ // Add letterSpacing category
206
+ if (mapping.letterSpacing &&
207
+ Object.keys(mapping.letterSpacing).length > 0) {
208
+ categoryValues["letterSpacing"] = mapping.letterSpacing;
209
+ categorySets["letterSpacing"] = new Set(Object.keys(mapping.letterSpacing));
210
+ }
211
+ }
165
212
  return { categoryValues, categorySets };
166
213
  }
167
214
  catch {
@@ -171,9 +218,10 @@ function loadTokenData() {
171
218
  }
172
219
  /**
173
220
  * Recursively flatten a color object into dot-notation keys.
174
- * For light/dark structures, use the light value.
175
- * E.g. { amber: { light: { "1": "hsl(...)" } } } "amber.1" = "hsl(...)"
176
- * E.g. { black: "hsl(...)" } → "black" = "hsl(...)"
221
+ * For light/dark structures, show both values as "<light> / <dark> (d)".
222
+ * E.g. { amber: { light: { "1": "hsl(a)" }, dark: { "1": "hsl(b)" } } }
223
+ * "amber.1" "hsl(a) / hsl(b) (d)"
224
+ * E.g. { black: "hsl(...)" } → "black" → "hsl(...)"
177
225
  */
178
226
  function flattenColors(obj, prefix, result) {
179
227
  for (const [key, val] of Object.entries(obj)) {
@@ -183,11 +231,24 @@ function flattenColors(obj, prefix, result) {
183
231
  }
184
232
  else if (val && typeof val === "object") {
185
233
  const record = val;
186
- // If this object has "light" and "dark" keys, use the light values
234
+ // If this object has "light" and "dark" keys, merge both
187
235
  if ("light" in record && "dark" in record) {
188
236
  const lightObj = record.light;
237
+ const darkObj = record.dark;
238
+ const colorPrefix = prefix ? `${prefix}.${key}` : key;
189
239
  if (lightObj && typeof lightObj === "object") {
190
- flattenColors(lightObj, prefix ? `${prefix}.${key}` : key, result);
240
+ for (const [step, lightVal] of Object.entries(lightObj)) {
241
+ if (typeof lightVal !== "string")
242
+ continue;
243
+ const darkVal = darkObj?.[step];
244
+ const stepKey = `${colorPrefix}.${step}`;
245
+ if (typeof darkVal === "string" && darkVal !== lightVal) {
246
+ result[stepKey] = `${lightVal} / ${darkVal} (d)`;
247
+ }
248
+ else {
249
+ result[stepKey] = lightVal;
250
+ }
251
+ }
191
252
  }
192
253
  }
193
254
  else {
@@ -1,8 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const vitest_1 = require("vitest");
4
+ const node_fs_1 = require("node:fs");
5
+ const node_path_1 = require("node:path");
4
6
  const token_data_1 = require("./token-data");
5
7
  const nimbus_tokens_1 = require("@commercetools/nimbus-tokens");
8
+ /** Load the generated mapping as the source of truth for generated categories */
9
+ const generatedMapping = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(__dirname, "generated-token-mapping.json"), "utf-8"));
6
10
  (0, vitest_1.describe)("loadTokenData", () => {
7
11
  const data = (0, token_data_1.loadTokenData)();
8
12
  (0, vitest_1.it)("loads token data successfully", () => {
@@ -19,6 +23,9 @@ const nimbus_tokens_1 = require("@commercetools/nimbus-tokens");
19
23
  (0, vitest_1.expect)(categories).toContain("shadows");
20
24
  (0, vitest_1.expect)(categories).toContain("opacity");
21
25
  (0, vitest_1.expect)(categories).toContain("zIndex");
26
+ (0, vitest_1.expect)(categories).toContain("textStyles");
27
+ (0, vitest_1.expect)(categories).toContain("layerStyles");
28
+ (0, vitest_1.expect)(categories).toContain("letterSpacing");
22
29
  });
23
30
  (0, vitest_1.describe)("spacing tokens", () => {
24
31
  (0, vitest_1.it)("maps token names to CSS values from source tokens", () => {
@@ -83,18 +90,22 @@ const nimbus_tokens_1 = require("@commercetools/nimbus-tokens");
83
90
  (0, vitest_1.expect)(colors[`blackAlpha.${name}`]).toBe(value);
84
91
  }
85
92
  });
86
- (0, vitest_1.it)("flattens palette colors using light values", () => {
93
+ (0, vitest_1.it)("flattens palette colors with light/dark values", () => {
87
94
  const colors = data.categoryValues["colors"];
88
95
  const amber = nimbus_tokens_1.designTokens.color["system-palettes"].amber;
89
- for (const [name, value] of Object.entries(amber.light)) {
90
- (0, vitest_1.expect)(colors[`amber.${name}`]).toBe(value);
96
+ for (const [name, lightVal] of Object.entries(amber.light)) {
97
+ const darkVal = amber.dark[name];
98
+ const expected = lightVal === darkVal ? lightVal : `${lightVal} / ${darkVal} (d)`;
99
+ (0, vitest_1.expect)(colors[`amber.${name}`]).toBe(expected);
91
100
  }
92
101
  });
93
- (0, vitest_1.it)("includes semantic palette colors", () => {
102
+ (0, vitest_1.it)("includes semantic palette colors with light/dark values", () => {
94
103
  const colors = data.categoryValues["colors"];
95
104
  const neutral = nimbus_tokens_1.designTokens.color["semantic-palettes"].neutral;
96
- for (const [name, value] of Object.entries(neutral.light)) {
97
- (0, vitest_1.expect)(colors[`neutral.${name}`]).toBe(value);
105
+ for (const [name, lightVal] of Object.entries(neutral.light)) {
106
+ const darkVal = neutral.dark[name];
107
+ const expected = lightVal === darkVal ? lightVal : `${lightVal} / ${darkVal} (d)`;
108
+ (0, vitest_1.expect)(colors[`neutral.${name}`]).toBe(expected);
98
109
  }
99
110
  });
100
111
  });
@@ -122,4 +133,103 @@ const nimbus_tokens_1 = require("@commercetools/nimbus-tokens");
122
133
  }
123
134
  });
124
135
  });
136
+ (0, vitest_1.describe)("semantic color tokens (from generated mapping)", () => {
137
+ (0, vitest_1.it)("includes all semantic colors from generated mapping", () => {
138
+ const colors = data.categoryValues["colors"];
139
+ for (const name of Object.keys(generatedMapping.semanticColors)) {
140
+ (0, vitest_1.expect)(colors[name]).toBeDefined();
141
+ }
142
+ });
143
+ (0, vitest_1.it)("matches resolved values from generated mapping", () => {
144
+ const colors = data.categoryValues["colors"];
145
+ for (const [name, expected] of Object.entries(generatedMapping.semanticColors)) {
146
+ (0, vitest_1.expect)(colors[name]).toBe(expected);
147
+ }
148
+ });
149
+ (0, vitest_1.it)("resolves semantic colors to CSS values (not token references)", () => {
150
+ const colors = data.categoryValues["colors"];
151
+ for (const name of Object.keys(generatedMapping.semanticColors)) {
152
+ (0, vitest_1.expect)(colors[name]).not.toMatch(/^\{/);
153
+ }
154
+ });
155
+ (0, vitest_1.it)("merges with existing palette colors", () => {
156
+ const colors = data.categoryValues["colors"];
157
+ // Semantic shortcuts should coexist with palette colors
158
+ (0, vitest_1.expect)(Object.keys(generatedMapping.semanticColors).length).toBeGreaterThan(0);
159
+ (0, vitest_1.expect)(colors["neutral.12"]).toBeDefined();
160
+ });
161
+ });
162
+ (0, vitest_1.describe)("textStyle tokens (from generated mapping)", () => {
163
+ (0, vitest_1.it)("contains all textStyle entries from designTokens", () => {
164
+ const textStyles = data.categoryValues["textStyles"];
165
+ for (const name of Object.keys(nimbus_tokens_1.designTokens.textStyle)) {
166
+ (0, vitest_1.expect)(textStyles[name]).toBeDefined();
167
+ }
168
+ });
169
+ (0, vitest_1.it)("formats composites as display strings derived from source", () => {
170
+ const textStyles = data.categoryValues["textStyles"];
171
+ for (const [name, value] of Object.entries(nimbus_tokens_1.designTokens.textStyle)) {
172
+ if (value && typeof value === "object") {
173
+ const expected = Object.entries(value)
174
+ .map(([prop, val]) => `${prop}: ${val}`)
175
+ .join(", ");
176
+ (0, vitest_1.expect)(textStyles[name]).toBe(expected);
177
+ }
178
+ }
179
+ });
180
+ (0, vitest_1.it)("has matching sets and values", () => {
181
+ const set = data.categorySets["textStyles"];
182
+ const values = data.categoryValues["textStyles"];
183
+ (0, vitest_1.expect)(set.size).toBe(Object.keys(values).length);
184
+ for (const name of set) {
185
+ (0, vitest_1.expect)(values[name]).toBeDefined();
186
+ }
187
+ });
188
+ });
189
+ (0, vitest_1.describe)("layerStyle tokens (from generated mapping)", () => {
190
+ (0, vitest_1.it)("contains all layerStyle entries from generated mapping", () => {
191
+ const layerStyles = data.categoryValues["layerStyles"];
192
+ for (const name of Object.keys(generatedMapping.layerStyles)) {
193
+ (0, vitest_1.expect)(layerStyles[name]).toBeDefined();
194
+ }
195
+ });
196
+ (0, vitest_1.it)("matches values from generated mapping", () => {
197
+ const layerStyles = data.categoryValues["layerStyles"];
198
+ for (const [name, expected] of Object.entries(generatedMapping.layerStyles)) {
199
+ (0, vitest_1.expect)(layerStyles[name]).toBe(expected);
200
+ }
201
+ });
202
+ (0, vitest_1.it)("has matching sets and values", () => {
203
+ const set = data.categorySets["layerStyles"];
204
+ const values = data.categoryValues["layerStyles"];
205
+ (0, vitest_1.expect)(set.size).toBe(Object.keys(values).length);
206
+ for (const name of set) {
207
+ (0, vitest_1.expect)(values[name]).toBeDefined();
208
+ }
209
+ });
210
+ });
211
+ (0, vitest_1.describe)("letterSpacing tokens (from generated mapping)", () => {
212
+ (0, vitest_1.it)("contains all letterSpacing entries from designTokens", () => {
213
+ const letterSpacing = data.categoryValues["letterSpacing"];
214
+ for (const name of Object.keys(nimbus_tokens_1.designTokens.letterSpacing)) {
215
+ (0, vitest_1.expect)(letterSpacing[name]).toBeDefined();
216
+ }
217
+ });
218
+ (0, vitest_1.it)("unwraps {value} objects to plain strings from source", () => {
219
+ const letterSpacing = data.categoryValues["letterSpacing"];
220
+ for (const [name, value] of Object.entries(nimbus_tokens_1.designTokens.letterSpacing)) {
221
+ if (value && typeof value === "object" && "value" in value) {
222
+ (0, vitest_1.expect)(letterSpacing[name]).toBe(value.value);
223
+ }
224
+ }
225
+ });
226
+ (0, vitest_1.it)("has matching sets and values", () => {
227
+ const set = data.categorySets["letterSpacing"];
228
+ const values = data.categoryValues["letterSpacing"];
229
+ (0, vitest_1.expect)(set.size).toBe(Object.keys(values).length);
230
+ for (const name of set) {
231
+ (0, vitest_1.expect)(values[name]).toBeDefined();
232
+ }
233
+ });
234
+ });
125
235
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@commercetools/nimbus-design-token-ts-plugin",
3
- "version": "2.7.0",
3
+ "version": "2.8.0",
4
4
  "description": "TypeScript language service plugin that shows design token CSS values in autocomplete",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -13,19 +13,20 @@
13
13
  "url": "https://github.com/commercetools/nimbus.git"
14
14
  },
15
15
  "peerDependencies": {
16
- "@commercetools/nimbus-tokens": "^2.7.0"
16
+ "@commercetools/nimbus-tokens": "^2.8.0"
17
17
  },
18
18
  "devDependencies": {
19
- "@types/node": "^24.10.1",
19
+ "@types/node": "^24.11.0",
20
20
  "typescript": "~5.9.3",
21
21
  "vitest": "^4.0.18",
22
- "@commercetools/nimbus-tokens": "^2.7.0"
22
+ "@commercetools/nimbus-tokens": "^2.8.0"
23
23
  },
24
24
  "files": [
25
25
  "dist"
26
26
  ],
27
27
  "scripts": {
28
- "build": "tsc && echo '🎉 Nimbus design token typescript plugin built'",
28
+ "generate:tokens": "node --disable-warning=MODULE_TYPELESS_PACKAGE_JSON scripts/generate-token-mapping.mjs",
29
+ "build": "pnpm generate:tokens && tsc && cp src/generated-token-mapping.json dist/ && echo '🎉 Nimbus design token typescript plugin built'",
29
30
  "typecheck": "tsc --noEmit",
30
31
  "test": "vitest run"
31
32
  }