@carbon/themes 12.0.0-alpha.1 → 12.0.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/es/index.js +5572 -0
- package/lib/__tests__/themes-test.d.ts +1 -0
- package/lib/__tests__/tools-test.d.ts +1 -0
- package/lib/tokens/__tests__/Token-test.d.ts +1 -0
- package/lib/tokens/__tests__/TokenFormat-test.d.ts +1 -0
- package/lib/tokens/__tests__/TokenGroup-test.d.ts +1 -0
- package/lib/tokens/__tests__/TokenSet-test.d.ts +1 -0
- package/lib/tokens/__tests__/metadata-test.d.ts +1 -0
- package/lib/tokens/__tests__/v11-test.d.ts +1 -0
- package/lib/v10/__tests__/themes-test.d.ts +1 -0
- package/lib/v10/__tests__/tokens-test.d.ts +1 -0
- package/package.json +13 -4
- package/umd/index.js +6322 -0
- package/docs/sass.md +0 -189
- package/src/dtcg/README.md +0 -336
- package/src/dtcg/color-palette.json +0 -4424
- package/src/dtcg/components/button.json +0 -193
- package/src/dtcg/components/content-switcher.json +0 -60
- package/src/dtcg/components/notification.json +0 -130
- package/src/dtcg/components/status.json +0 -122
- package/src/dtcg/components/tag.json +0 -486
- package/src/dtcg/themes.json +0 -3356
- package/src/index.ts +0 -129
- package/src/tokens/Token.ts +0 -41
- package/src/tokens/TokenFormat.ts +0 -93
- package/src/tokens/TokenGroup.ts +0 -185
- package/src/tokens/TokenSet.ts +0 -91
- package/src/tokens/components.ts +0 -120
- package/src/tokens/index.ts +0 -59
- package/src/tokens/layout.ts +0 -39
- package/src/tokens/type.ts +0 -49
- package/src/tokens/types.ts +0 -28
- package/src/tokens/v10.ts +0 -188
- package/src/tokens/v11TokenGroup.ts +0 -546
- package/src/tokens/v11TokenSet.ts +0 -93
- package/src/tools.ts +0 -69
- package/src/v10/g10.ts +0 -349
- package/src/v10/g100.ts +0 -348
- package/src/v10/g90.ts +0 -350
- package/src/v10/index.ts +0 -22
- package/src/v10/metadata.yml +0 -217
- package/src/v10/tokens.ts +0 -397
- package/src/v10/white.ts +0 -352
- package/style-dictionary/formats/js-component-tokens.js +0 -128
- package/style-dictionary/formats/js-themes.js +0 -84
- package/style-dictionary/formats/scss-component-tokens.js +0 -149
- package/style-dictionary/formats/scss-themes.js +0 -127
- package/style-dictionary/formats/scss-tokens.js +0 -91
- package/style-dictionary/preprocessors/component-tokens.js +0 -172
- package/style-dictionary/preprocessors/dual-role.js +0 -157
- package/style-dictionary/preprocessors/theme-metadata.js +0 -46
- package/style-dictionary/sd.config.js +0 -449
- package/style-dictionary/transforms/alpha-modifier.js +0 -68
- package/style-dictionary/transforms/color-flatten.js +0 -79
- package/test-dtcg.scss +0 -28
- package/tsconfig.json +0 -8
- package/tsconfig.types.json +0 -10
|
@@ -1,449 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Copyright IBM Corp. 2026
|
|
3
|
-
*
|
|
4
|
-
* This source code is licensed under the Apache-2.0 license found in the
|
|
5
|
-
* LICENSE file in the root directory of this source tree.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
'use strict';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Style Dictionary configuration for @carbon/themes.
|
|
12
|
-
*
|
|
13
|
-
* Wires together the custom plugins in this directory
|
|
14
|
-
*
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
const path = require('path');
|
|
18
|
-
const fs = require('fs-extra');
|
|
19
|
-
const { default: StyleDictionary } = require('style-dictionary');
|
|
20
|
-
|
|
21
|
-
// ── Custom plugins ────────────────────────────────────────────────────────────
|
|
22
|
-
const carbonColorFlatten = require('./transforms/color-flatten');
|
|
23
|
-
const carbonAlphaModifier = require('./transforms/alpha-modifier');
|
|
24
|
-
const carbonComponentTokensPreprocessorModule = require('./preprocessors/component-tokens');
|
|
25
|
-
const { preprocessor: carbonComponentTokensPreprocessor } =
|
|
26
|
-
carbonComponentTokensPreprocessorModule;
|
|
27
|
-
const carbonThemeMetadataPreprocessor = require('./preprocessors/theme-metadata');
|
|
28
|
-
const { preprocessor: flattenDualRole } = require('./preprocessors/dual-role');
|
|
29
|
-
const carbonScssThemes = require('./formats/scss-themes');
|
|
30
|
-
const carbonScssTokens = require('./formats/scss-tokens');
|
|
31
|
-
const carbonScssComponentTokens = require('./formats/scss-component-tokens');
|
|
32
|
-
const carbonJsThemes = require('./formats/js-themes');
|
|
33
|
-
const carbonJsComponentTokens = require('./formats/js-component-tokens');
|
|
34
|
-
|
|
35
|
-
// ── Paths ─────────────────────────────────────────────────────────────────────
|
|
36
|
-
const ROOT = path.resolve(__dirname, '..');
|
|
37
|
-
const DTCG_DIR = path.join(ROOT, 'src', 'dtcg');
|
|
38
|
-
const SCSS_GENERATED = path.join(ROOT, 'scss', 'generated');
|
|
39
|
-
const JS_GENERATED_THEMES = path.join(ROOT, 'js', 'generated', 'themes');
|
|
40
|
-
const JS_GENERATED_COMPONENTS = path.join(
|
|
41
|
-
ROOT,
|
|
42
|
-
'js',
|
|
43
|
-
'generated',
|
|
44
|
-
'component-tokens'
|
|
45
|
-
);
|
|
46
|
-
|
|
47
|
-
const THEME_NAMES = ['white', 'g10', 'g90', 'g100'];
|
|
48
|
-
|
|
49
|
-
// Light/dark scheme per theme — used to re-inject color-scheme metadata after
|
|
50
|
-
// extractThemeSlice strips the root $extensions from the unified file.
|
|
51
|
-
const THEME_COLOR_SCHEME = {
|
|
52
|
-
white: 'light',
|
|
53
|
-
g10: 'light',
|
|
54
|
-
g90: 'dark',
|
|
55
|
-
g100: 'dark',
|
|
56
|
-
};
|
|
57
|
-
const COMPONENT_NAMES = [
|
|
58
|
-
'button',
|
|
59
|
-
'tag',
|
|
60
|
-
'notification',
|
|
61
|
-
'status',
|
|
62
|
-
'content-switcher',
|
|
63
|
-
];
|
|
64
|
-
|
|
65
|
-
// ── extractThemeSlice ─────────────────────────────────────────────────────────
|
|
66
|
-
//
|
|
67
|
-
// After the component-tokens preprocessor runs on themes.json, every leaf token
|
|
68
|
-
// node becomes a group with a `_by_theme` child containing one synthetic leaf
|
|
69
|
-
// per theme. This function walks the expanded tree and replaces each
|
|
70
|
-
// `_by_theme` group with the single synthetic leaf for `themeName`, collapsing
|
|
71
|
-
// the tree back into a normal per-theme token structure that the rest of the
|
|
72
|
-
// pipeline (flattenDualRole, SD transforms, formatters) understands.
|
|
73
|
-
//
|
|
74
|
-
// Tokens that do NOT have a _by_theme group (palette reference tokens injected
|
|
75
|
-
// via `source:`) are passed through unchanged.
|
|
76
|
-
function extractThemeSlice(node, themeName) {
|
|
77
|
-
if (!node || typeof node !== 'object') return node;
|
|
78
|
-
|
|
79
|
-
const out = {};
|
|
80
|
-
for (const [key, value] of Object.entries(node)) {
|
|
81
|
-
if (key.startsWith('$')) {
|
|
82
|
-
out[key] = value;
|
|
83
|
-
continue;
|
|
84
|
-
}
|
|
85
|
-
if (!value || typeof value !== 'object') {
|
|
86
|
-
out[key] = value;
|
|
87
|
-
continue;
|
|
88
|
-
}
|
|
89
|
-
if (key === '_by_theme') {
|
|
90
|
-
// Skip — handled by the parent via the `value._by_theme` branch below.
|
|
91
|
-
continue;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Check if this child node has a _by_theme group (i.e. it was a leaf or
|
|
95
|
-
// dual-role node in themes.json that the preprocessor expanded).
|
|
96
|
-
if (value._by_theme) {
|
|
97
|
-
const leaf = value._by_theme[themeName];
|
|
98
|
-
if (leaf) {
|
|
99
|
-
// Start with the leaf's DTCG fields ($type, $value, $extensions).
|
|
100
|
-
const merged = { ...leaf };
|
|
101
|
-
// Preserve $description from the group wrapper (leaf synthetic nodes
|
|
102
|
-
// don't carry it, but the original themes.json node does).
|
|
103
|
-
if (!merged.$description && value.$description)
|
|
104
|
-
merged.$description = value.$description;
|
|
105
|
-
if (!merged.$type && value.$type) merged.$type = value.$type;
|
|
106
|
-
|
|
107
|
-
// Recurse into any group children of this node (dual-role case):
|
|
108
|
-
// e.g. `background` is both a token and the parent of `background-active`.
|
|
109
|
-
for (const [childKey, childValue] of Object.entries(value)) {
|
|
110
|
-
if (childKey.startsWith('$') || childKey === '_by_theme') continue;
|
|
111
|
-
if (!childValue || typeof childValue !== 'object') continue;
|
|
112
|
-
const sliced = extractThemeSlice(
|
|
113
|
-
{ [childKey]: childValue },
|
|
114
|
-
themeName
|
|
115
|
-
);
|
|
116
|
-
if (sliced[childKey] !== undefined)
|
|
117
|
-
merged[childKey] = sliced[childKey];
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
out[key] = merged;
|
|
121
|
-
}
|
|
122
|
-
// If the theme had no entry, skip the token.
|
|
123
|
-
} else {
|
|
124
|
-
const sliced = extractThemeSlice(value, themeName);
|
|
125
|
-
if (sliced !== null) out[key] = sliced;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
return out;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// ── Custom name transform ─────────────────────────────────────────────────────
|
|
132
|
-
//
|
|
133
|
-
// Builds a kebab-case name from the SD token path, with one special rule:
|
|
134
|
-
// a path segment equal to `_self` is stripped. This is used in conjunction
|
|
135
|
-
// with the carbon/dual-role preprocessor — dual-role parent token values are
|
|
136
|
-
// placed under a `_self` child key so that SD processes them as separate leaf
|
|
137
|
-
// tokens, but their output name omits the `_self` suffix.
|
|
138
|
-
//
|
|
139
|
-
// Examples:
|
|
140
|
-
// ['background'] → 'background'
|
|
141
|
-
// ['background', '_self'] → 'background' (parent of dual-role node)
|
|
142
|
-
// ['background', 'active'] → 'background-active'
|
|
143
|
-
// ['layer', '01'] → 'layer-01'
|
|
144
|
-
// ['layer', '_self'] → 'layer'
|
|
145
|
-
const carbonNameKebab = {
|
|
146
|
-
name: 'carbon/name-kebab',
|
|
147
|
-
type: 'name',
|
|
148
|
-
transform(token) {
|
|
149
|
-
return token.path
|
|
150
|
-
.filter((p) => p !== '_self')
|
|
151
|
-
.map((p) => p.replace(/^_/, '').replace(/_/g, '-'))
|
|
152
|
-
.join('-');
|
|
153
|
-
},
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
// ── Transform group ────────────────────────────────────────────────────────────
|
|
157
|
-
//
|
|
158
|
-
// Order matters: alpha-modifier must fire BEFORE color-flatten so the hex
|
|
159
|
-
// is already resolved when flatten runs.
|
|
160
|
-
// Both are `transitive: true` — SD re-applies them until the value stabilises.
|
|
161
|
-
const CARBON_TRANSFORMS = [
|
|
162
|
-
'attribute/cti',
|
|
163
|
-
'carbon/name-kebab', // custom: strips _self from dual-role parent paths
|
|
164
|
-
'carbon/alpha-modifier',
|
|
165
|
-
'carbon/color-flatten',
|
|
166
|
-
];
|
|
167
|
-
|
|
168
|
-
// ── Helper: build config for one theme ───────────────────────────────────────
|
|
169
|
-
//
|
|
170
|
-
// themes.json is the unified source — all four themes in one file, with each
|
|
171
|
-
// token carrying per-theme values under $extensions["carbon.themes"].
|
|
172
|
-
// The component-tokens preprocessor expands those into per-theme leaf tokens
|
|
173
|
-
// (identical to what it does for component files), and dual-role splitting
|
|
174
|
-
// then runs on the resulting tree so SD sees clean leaf+group pairs.
|
|
175
|
-
function themeConfig(themeName) {
|
|
176
|
-
// Read the unified themes.json. The component-tokens preprocessor first
|
|
177
|
-
// expands carbon.themes entries into _by_theme.<theme> leaf nodes, then
|
|
178
|
-
// flattenDualRole splits any dual-role nodes (value + children) so SD can
|
|
179
|
-
// process them without losing children.
|
|
180
|
-
const rawThemes = JSON.parse(
|
|
181
|
-
fs.readFileSync(path.join(DTCG_DIR, 'themes.json'), 'utf8')
|
|
182
|
-
);
|
|
183
|
-
// The component-tokens preprocessor understands the { value, alpha } entry
|
|
184
|
-
// shape from the unified file as well as the legacy bare-string shape from
|
|
185
|
-
// component files. Run it first so carbon.themes entries become $value leaves.
|
|
186
|
-
const expanded = carbonComponentTokensPreprocessor(rawThemes);
|
|
187
|
-
// Now extract only this theme's _by_theme.<themeName> leaf nodes, rewriting
|
|
188
|
-
// them back to plain $value tokens so the rest of the pipeline sees a normal
|
|
189
|
-
// single-theme token tree.
|
|
190
|
-
const rawSlice = extractThemeSlice(expanded, themeName);
|
|
191
|
-
|
|
192
|
-
// Re-attach the per-theme color-scheme to the root $extensions so that
|
|
193
|
-
// carbonThemeMetadataPreprocessor (which reads
|
|
194
|
-
// dictionary.$extensions["org.carbon"]["color-scheme"]) can inject the
|
|
195
|
-
// color-scheme synthetic token. extractThemeSlice does not carry the root
|
|
196
|
-
// $extensions through because they are global, not per-token.
|
|
197
|
-
const themeSlice = {
|
|
198
|
-
...rawSlice,
|
|
199
|
-
$extensions: {
|
|
200
|
-
...(rawSlice.$extensions ?? {}),
|
|
201
|
-
'org.carbon': {
|
|
202
|
-
...(rawSlice.$extensions?.['org.carbon'] ?? {}),
|
|
203
|
-
'color-scheme': THEME_COLOR_SCHEME[themeName],
|
|
204
|
-
},
|
|
205
|
-
},
|
|
206
|
-
};
|
|
207
|
-
|
|
208
|
-
const flatTokens = flattenDualRole(themeSlice);
|
|
209
|
-
|
|
210
|
-
return {
|
|
211
|
-
source: [path.join(DTCG_DIR, 'color-palette.json')],
|
|
212
|
-
tokens: flatTokens,
|
|
213
|
-
preprocessors: ['carbon/theme-metadata'],
|
|
214
|
-
platforms: {
|
|
215
|
-
// SCSS theme map
|
|
216
|
-
[`scss/themes/${themeName}`]: {
|
|
217
|
-
transformGroup: 'carbon',
|
|
218
|
-
buildPath: SCSS_GENERATED + '/',
|
|
219
|
-
files: [
|
|
220
|
-
{
|
|
221
|
-
// Each theme writes to its own temp file; a post-step
|
|
222
|
-
// (buildAllPlatforms action) concatenates them into _themes.scss.
|
|
223
|
-
destination: `_theme-${themeName}.scss`,
|
|
224
|
-
format: 'carbon/scss-themes',
|
|
225
|
-
options: { themeName },
|
|
226
|
-
// Exclude palette reference tokens from the theme map.
|
|
227
|
-
filter(token) {
|
|
228
|
-
const role =
|
|
229
|
-
token.$extensions?.['org.carbon']?.role ??
|
|
230
|
-
token.extensions?.['org.carbon']?.role;
|
|
231
|
-
return role !== 'reference';
|
|
232
|
-
},
|
|
233
|
-
},
|
|
234
|
-
],
|
|
235
|
-
},
|
|
236
|
-
// JS theme module
|
|
237
|
-
[`js/themes/${themeName}`]: {
|
|
238
|
-
transformGroup: 'carbon',
|
|
239
|
-
buildPath: JS_GENERATED_THEMES + '/',
|
|
240
|
-
files: [
|
|
241
|
-
{
|
|
242
|
-
destination: `${themeName}.js`,
|
|
243
|
-
format: 'carbon/js-themes',
|
|
244
|
-
options: { themeName, output: 'js' },
|
|
245
|
-
filter(token) {
|
|
246
|
-
const role =
|
|
247
|
-
token.$extensions?.['org.carbon']?.role ??
|
|
248
|
-
token.extensions?.['org.carbon']?.role;
|
|
249
|
-
return role !== 'reference';
|
|
250
|
-
},
|
|
251
|
-
},
|
|
252
|
-
{
|
|
253
|
-
destination: `${themeName}.d.ts`,
|
|
254
|
-
format: 'carbon/js-themes',
|
|
255
|
-
options: { themeName, output: 'dts' },
|
|
256
|
-
filter(token) {
|
|
257
|
-
const role =
|
|
258
|
-
token.$extensions?.['org.carbon']?.role ??
|
|
259
|
-
token.extensions?.['org.carbon']?.role;
|
|
260
|
-
return role !== 'reference';
|
|
261
|
-
},
|
|
262
|
-
},
|
|
263
|
-
],
|
|
264
|
-
},
|
|
265
|
-
},
|
|
266
|
-
};
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// ── Helper: build config for one component ───────────────────────────────────
|
|
270
|
-
function componentConfig(componentName) {
|
|
271
|
-
return {
|
|
272
|
-
source: [
|
|
273
|
-
path.join(DTCG_DIR, 'color-palette.json'),
|
|
274
|
-
path.join(DTCG_DIR, 'components', `${componentName}.json`),
|
|
275
|
-
],
|
|
276
|
-
preprocessors: ['carbon/component-tokens'],
|
|
277
|
-
platforms: {
|
|
278
|
-
// SCSS component token map
|
|
279
|
-
[`scss/component-tokens/${componentName}`]: {
|
|
280
|
-
transformGroup: 'carbon',
|
|
281
|
-
buildPath: SCSS_GENERATED + '/',
|
|
282
|
-
files: [
|
|
283
|
-
{
|
|
284
|
-
destination: `_${componentName}-tokens.scss`,
|
|
285
|
-
format: 'carbon/scss-component-tokens',
|
|
286
|
-
filter(token) {
|
|
287
|
-
// Only include expanded theme tokens (injected by preprocessor)
|
|
288
|
-
return token.name.includes('-by-theme-');
|
|
289
|
-
},
|
|
290
|
-
},
|
|
291
|
-
],
|
|
292
|
-
},
|
|
293
|
-
// JS component token module
|
|
294
|
-
[`js/component-tokens/${componentName}`]: {
|
|
295
|
-
transformGroup: 'carbon',
|
|
296
|
-
buildPath: JS_GENERATED_COMPONENTS + '/',
|
|
297
|
-
files: [
|
|
298
|
-
{
|
|
299
|
-
destination: `${componentName}.js`,
|
|
300
|
-
format: 'carbon/js-component-tokens',
|
|
301
|
-
options: { output: 'js' },
|
|
302
|
-
filter(token) {
|
|
303
|
-
return token.name.includes('-by-theme-');
|
|
304
|
-
},
|
|
305
|
-
},
|
|
306
|
-
{
|
|
307
|
-
destination: `${componentName}.d.ts`,
|
|
308
|
-
format: 'carbon/js-component-tokens',
|
|
309
|
-
options: { output: 'dts' },
|
|
310
|
-
filter(token) {
|
|
311
|
-
return token.name.includes('-by-theme-');
|
|
312
|
-
},
|
|
313
|
-
},
|
|
314
|
-
],
|
|
315
|
-
},
|
|
316
|
-
},
|
|
317
|
-
};
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
// ── _tokens.scss config ───────────────────────────────────────────────────────
|
|
321
|
-
//
|
|
322
|
-
// This file does not use token values at all — it reads from Carbon JS metadata
|
|
323
|
-
// directly. We still run it through SD so it participates in buildAllPlatforms
|
|
324
|
-
// and can be tested alongside the other outputs.
|
|
325
|
-
const tokensConfig = {
|
|
326
|
-
// No source files needed — format reads from src/tokens directly.
|
|
327
|
-
source: [],
|
|
328
|
-
platforms: {
|
|
329
|
-
'scss/tokens': {
|
|
330
|
-
transformGroup: 'carbon',
|
|
331
|
-
buildPath: SCSS_GENERATED + '/',
|
|
332
|
-
files: [
|
|
333
|
-
{
|
|
334
|
-
destination: '_tokens.scss',
|
|
335
|
-
format: 'carbon/scss-tokens',
|
|
336
|
-
},
|
|
337
|
-
],
|
|
338
|
-
},
|
|
339
|
-
},
|
|
340
|
-
};
|
|
341
|
-
|
|
342
|
-
// ── Build a registered SD instance ────────────────────────────────────────────
|
|
343
|
-
// In SD v5, register* methods live on the instance, not the class.
|
|
344
|
-
// We create one base instance with all plugins registered, then extend it
|
|
345
|
-
// per-config so each build inherits the registrations.
|
|
346
|
-
function createBase() {
|
|
347
|
-
const base = new StyleDictionary({});
|
|
348
|
-
// Register custom transforms BEFORE the transform group that references them.
|
|
349
|
-
base.registerTransform(carbonNameKebab);
|
|
350
|
-
base.registerTransform(carbonAlphaModifier);
|
|
351
|
-
base.registerTransform(carbonColorFlatten);
|
|
352
|
-
base.registerTransformGroup({
|
|
353
|
-
name: 'carbon',
|
|
354
|
-
transforms: CARBON_TRANSFORMS,
|
|
355
|
-
});
|
|
356
|
-
base.registerPreprocessor(carbonComponentTokensPreprocessorModule);
|
|
357
|
-
base.registerPreprocessor(carbonThemeMetadataPreprocessor);
|
|
358
|
-
base.registerFormat(carbonScssThemes);
|
|
359
|
-
base.registerFormat(carbonScssTokens);
|
|
360
|
-
base.registerFormat(carbonScssComponentTokens);
|
|
361
|
-
base.registerFormat(carbonJsThemes);
|
|
362
|
-
base.registerFormat(carbonJsComponentTokens);
|
|
363
|
-
return base;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
// ── SCSS build ────────────────────────────────────────────────────────────────
|
|
367
|
-
// Generates:
|
|
368
|
-
// scss/generated/_themes.scss
|
|
369
|
-
// scss/generated/_tokens.scss
|
|
370
|
-
// scss/generated/_button-tokens.scss (and tag, notification, status, content-switcher)
|
|
371
|
-
async function runScss() {
|
|
372
|
-
const base = createBase();
|
|
373
|
-
const scssTokensContent = await carbonScssTokens.format({
|
|
374
|
-
dictionary: { allTokens: [] },
|
|
375
|
-
options: {},
|
|
376
|
-
file: {},
|
|
377
|
-
});
|
|
378
|
-
fs.ensureDirSync(SCSS_GENERATED);
|
|
379
|
-
fs.writeFileSync(
|
|
380
|
-
path.join(SCSS_GENERATED, '_tokens.scss'),
|
|
381
|
-
scssTokensContent,
|
|
382
|
-
'utf8'
|
|
383
|
-
);
|
|
384
|
-
|
|
385
|
-
// Per-theme maps — write to temp files, then concatenate
|
|
386
|
-
for (const themeName of THEME_NAMES) {
|
|
387
|
-
await (await base.extend(themeConfig(themeName))).buildAllPlatforms();
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
// Concatenate per-theme temp files into a single _themes.scss
|
|
391
|
-
const themeParts = THEME_NAMES.map((name) => {
|
|
392
|
-
const src = path.join(SCSS_GENERATED, `_theme-${name}.scss`);
|
|
393
|
-
const content = fs.readFileSync(src, 'utf8');
|
|
394
|
-
return name === THEME_NAMES[0]
|
|
395
|
-
? content
|
|
396
|
-
: '\n' +
|
|
397
|
-
content
|
|
398
|
-
.replace(/^\/\/[^\n]*\n(?:\/\/[^\n]*\n)*/m, '') // strip banner
|
|
399
|
-
.replace(/^@use [^\n]+\n/gm, '') // strip duplicate @use
|
|
400
|
-
.replace(/^\n+/, ''); // collapse leading blanks
|
|
401
|
-
});
|
|
402
|
-
fs.writeFileSync(
|
|
403
|
-
path.join(SCSS_GENERATED, '_themes.scss'),
|
|
404
|
-
themeParts.join(''),
|
|
405
|
-
'utf8'
|
|
406
|
-
);
|
|
407
|
-
for (const name of THEME_NAMES) {
|
|
408
|
-
fs.removeSync(path.join(SCSS_GENERATED, `_theme-${name}.scss`));
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
// Component token maps
|
|
412
|
-
for (const componentName of COMPONENT_NAMES) {
|
|
413
|
-
await (
|
|
414
|
-
await base.extend(componentConfig(componentName))
|
|
415
|
-
).buildAllPlatforms();
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
// ── JS build ──────────────────────────────────────────────────────────────────
|
|
420
|
-
// Generates:
|
|
421
|
-
// js/generated/themes/{white,g10,g90,g100}.{js,d.ts}
|
|
422
|
-
// js/generated/component-tokens/{button,tag,…}.{js,d.ts}
|
|
423
|
-
async function runJs() {
|
|
424
|
-
const base = createBase();
|
|
425
|
-
|
|
426
|
-
for (const themeName of THEME_NAMES) {
|
|
427
|
-
await (await base.extend(themeConfig(themeName))).buildAllPlatforms();
|
|
428
|
-
}
|
|
429
|
-
for (const componentName of COMPONENT_NAMES) {
|
|
430
|
-
await (
|
|
431
|
-
await base.extend(componentConfig(componentName))
|
|
432
|
-
).buildAllPlatforms();
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
// ── Full build (SCSS + JS) ────────────────────────────────────────────────────
|
|
437
|
-
async function run() {
|
|
438
|
-
await runScss();
|
|
439
|
-
await runJs();
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
module.exports = {
|
|
443
|
-
run,
|
|
444
|
-
runScss,
|
|
445
|
-
runJs,
|
|
446
|
-
themeConfig,
|
|
447
|
-
componentConfig,
|
|
448
|
-
tokensConfig,
|
|
449
|
-
};
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Copyright IBM Corp. 2026
|
|
3
|
-
*
|
|
4
|
-
* This source code is licensed under the Apache-2.0 license found in the
|
|
5
|
-
* LICENSE file in the root directory of this source tree.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
'use strict';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
*
|
|
12
|
-
* Applies the `$extensions["org.carbon"].alphaModifier` field to theme tokens,
|
|
13
|
-
* producing an `rgba()` string.
|
|
14
|
-
*
|
|
15
|
-
* Must run AFTER alias resolution (transitive: true) so that by the time this
|
|
16
|
-
* transform fires, `token.value` is already the resolved hex string from the
|
|
17
|
-
* palette (e.g. '#8d8d8d' for gray.50), not the raw alias '{gray.50}'.
|
|
18
|
-
*
|
|
19
|
-
* If no alphaModifier extension is present the token is passed through
|
|
20
|
-
* unchanged (carbon/color-flatten will handle it next).
|
|
21
|
-
*
|
|
22
|
-
* Example in themes.json:
|
|
23
|
-
* "background-active": {
|
|
24
|
-
* "$type": "color",
|
|
25
|
-
* "$value": "{gray.50}", ← resolved by SD to '#8d8d8d'
|
|
26
|
-
* "$extensions": {
|
|
27
|
-
* "org.carbon": { "alphaModifier": 0.5 }
|
|
28
|
-
* }
|
|
29
|
-
* }
|
|
30
|
-
* → 'rgba(141, 141, 141, 0.5)'
|
|
31
|
-
*/
|
|
32
|
-
|
|
33
|
-
module.exports = {
|
|
34
|
-
name: 'carbon/alpha-modifier',
|
|
35
|
-
type: 'value',
|
|
36
|
-
transitive: true,
|
|
37
|
-
filter(token) {
|
|
38
|
-
const ext =
|
|
39
|
-
token.$extensions?.['org.carbon'] ?? token.extensions?.['org.carbon'];
|
|
40
|
-
return ext?.alphaModifier !== undefined;
|
|
41
|
-
},
|
|
42
|
-
transform(token) {
|
|
43
|
-
const ext =
|
|
44
|
-
token.$extensions?.['org.carbon'] ?? token.extensions?.['org.carbon'];
|
|
45
|
-
const alpha = ext.alphaModifier;
|
|
46
|
-
|
|
47
|
-
// SD v5: post-alias value is in token.$value (resolved object or hex string).
|
|
48
|
-
// In the golden-master test, token.value holds the pre-resolved hex string.
|
|
49
|
-
const resolved = token.value !== undefined ? token.value : token.$value;
|
|
50
|
-
const hex =
|
|
51
|
-
typeof resolved === 'string'
|
|
52
|
-
? resolved
|
|
53
|
-
: typeof resolved === 'object' && resolved?.hex
|
|
54
|
-
? resolved.hex
|
|
55
|
-
: null;
|
|
56
|
-
|
|
57
|
-
if (!hex || !hex.startsWith('#') || hex.length < 7) {
|
|
58
|
-
// Fallback: if resolution hasn't happened yet, return unchanged and let
|
|
59
|
-
// the transitive pass retry.
|
|
60
|
-
return token.value ?? token.$value;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const r = parseInt(hex.slice(1, 3), 16);
|
|
64
|
-
const g = parseInt(hex.slice(3, 5), 16);
|
|
65
|
-
const b = parseInt(hex.slice(5, 7), 16);
|
|
66
|
-
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
67
|
-
},
|
|
68
|
-
};
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Copyright IBM Corp. 2026
|
|
3
|
-
*
|
|
4
|
-
* This source code is licensed under the Apache-2.0 license found in the
|
|
5
|
-
* LICENSE file in the root directory of this source tree.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
'use strict';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
*
|
|
12
|
-
* Converts the DTCG color $value object shapes used by this codebase into
|
|
13
|
-
* plain CSS color strings, after Style Dictionary has already resolved any
|
|
14
|
-
* alias references.
|
|
15
|
-
*
|
|
16
|
-
* Input shapes (post-alias-resolution, i.e. $value is already an object):
|
|
17
|
-
*
|
|
18
|
-
* Shape A — solid inline object
|
|
19
|
-
* { colorSpace: 'srgb', components: [...], hex: '#rrggbb' }
|
|
20
|
-
* → '#rrggbb'
|
|
21
|
-
*
|
|
22
|
-
* Shape B — alpha inline object (components + alpha, no hex)
|
|
23
|
-
* { colorSpace: 'srgb', components: [r, g, b], alpha: 0.5 }
|
|
24
|
-
* → 'rgba(r, g, b, 0.5)'
|
|
25
|
-
*
|
|
26
|
-
* Plain strings and non-sRGB objects are passed through unchanged.
|
|
27
|
-
*
|
|
28
|
-
* NOTE: The alias+alphaModifier case (Shape C) is handled upstream by the
|
|
29
|
-
* carbon/alpha-modifier transform, which runs first (transitive: true ensures
|
|
30
|
-
* the resolved alias value is already in scope when this transform fires).
|
|
31
|
-
*/
|
|
32
|
-
|
|
33
|
-
module.exports = {
|
|
34
|
-
name: 'carbon/color-flatten',
|
|
35
|
-
type: 'value',
|
|
36
|
-
transitive: true,
|
|
37
|
-
filter(token) {
|
|
38
|
-
return token.$type === 'color' || token.type === 'color';
|
|
39
|
-
},
|
|
40
|
-
transform(token) {
|
|
41
|
-
// SD v5 stores the post-alias-resolution value in token.$value (the DTCG
|
|
42
|
-
// field), NOT in token.value. token.value is only set after transforms run.
|
|
43
|
-
// The golden-master test sets both; in that context token.value is preferred
|
|
44
|
-
// so the test can simulate resolution without running full SD.
|
|
45
|
-
const v = token.value !== undefined ? token.value : token.$value;
|
|
46
|
-
|
|
47
|
-
if (typeof v === 'string') {
|
|
48
|
-
// Already a plain string (e.g. '#0f62fe' or 'rgba(...)') — pass through.
|
|
49
|
-
return v;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (v === null || typeof v !== 'object') {
|
|
53
|
-
return v;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// SD v5 can resolve an alias to the full token node instead of its value
|
|
57
|
-
// when the alias points to a sibling token (e.g. {syntax.value} resolving
|
|
58
|
-
// to the syntax.value token object). Unwrap one level.
|
|
59
|
-
if ('$value' in v) {
|
|
60
|
-
const inner = v.$value;
|
|
61
|
-
if (typeof inner === 'string') return inner;
|
|
62
|
-
if (inner?.hex) return inner.hex;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (v.colorSpace !== 'srgb' || !Array.isArray(v.components)) {
|
|
66
|
-
return v;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Shape A — solid: hex present
|
|
70
|
-
if (typeof v.hex === 'string') {
|
|
71
|
-
return v.hex;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Shape B — alpha: derive rgba from 0–1 component floats
|
|
75
|
-
const [r, g, b] = v.components.map((c) => Math.round(c * 255));
|
|
76
|
-
const alpha = v.alpha !== undefined ? v.alpha : 1;
|
|
77
|
-
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
78
|
-
},
|
|
79
|
-
};
|
package/test-dtcg.scss
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
@use 'scss/themes' as *;
|
|
2
|
-
@use 'scss/component-tokens' as *;
|
|
3
|
-
|
|
4
|
-
// Test that all 4 themes are available
|
|
5
|
-
.test-white {
|
|
6
|
-
background: map-get($white, 'background');
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
.test-g10 {
|
|
10
|
-
background: map-get($g10, 'background');
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
.test-g90 {
|
|
14
|
-
background: map-get($g90, 'background');
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
.test-g100 {
|
|
18
|
-
background: map-get($g100, 'background');
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// Test component tokens
|
|
22
|
-
.test-button {
|
|
23
|
-
background: map-get($button-primary, 'white');
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
.test-tag {
|
|
27
|
-
background: map-get($tag-background-blue, 'white');
|
|
28
|
-
}
|
package/tsconfig.json
DELETED