@carbon/themes 11.78.0 → 11.79.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.
- package/es/index.js +7 -7
- package/js/generated/themes/g10.d.ts +2 -2
- package/js/generated/themes/g10.js +2 -2
- package/js/generated/themes/g100.d.ts +2 -2
- package/js/generated/themes/g100.js +2 -2
- package/js/generated/themes/g90.d.ts +2 -2
- package/js/generated/themes/g90.js +2 -2
- package/js/generated/themes/white.d.ts +1 -1
- package/js/generated/themes/white.js +1 -1
- package/lib/index.js +7 -7
- package/package.json +11 -7
- package/src/dtcg/color-palette.json +1464 -244
- package/style-dictionary/formats/js-component-tokens.js +128 -0
- package/style-dictionary/formats/js-themes.js +84 -0
- package/style-dictionary/formats/scss-component-tokens.js +149 -0
- package/style-dictionary/formats/scss-themes.js +127 -0
- package/style-dictionary/formats/scss-tokens.js +91 -0
- package/style-dictionary/preprocessors/component-tokens.js +119 -0
- package/style-dictionary/preprocessors/dual-role.js +157 -0
- package/style-dictionary/preprocessors/theme-metadata.js +46 -0
- package/style-dictionary/sd.config.js +342 -0
- package/style-dictionary/transforms/alpha-modifier.js +68 -0
- package/style-dictionary/transforms/color-flatten.js +79 -0
- package/umd/index.js +7 -7
|
@@ -0,0 +1,128 @@
|
|
|
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
|
+
* Receives tokens for ONE component (pre-processed by
|
|
12
|
+
* `component-tokens`) and emits two files:
|
|
13
|
+
* - `<componentName>.js` — named ES exports, one per token
|
|
14
|
+
* - `<componentName>.d.ts` — companion TypeScript declarations
|
|
15
|
+
*
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const FILE_BANNER = `// Code generated by @carbon/themes. DO NOT EDIT.
|
|
19
|
+
//
|
|
20
|
+
// Copyright IBM Corp. 2018, 2025
|
|
21
|
+
//
|
|
22
|
+
// This source code is licensed under the Apache-2.0 license found in the
|
|
23
|
+
// LICENSE file in the root directory of this source tree.
|
|
24
|
+
`;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Convert kebab-case to camelCase.
|
|
28
|
+
* @param {string} str
|
|
29
|
+
* @returns {string}
|
|
30
|
+
*/
|
|
31
|
+
function kebabToCamel(str) {
|
|
32
|
+
return str.replace(/-([a-z0-9])/g, (_, ch) => ch.toUpperCase());
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Normalize theme key to the JS API convention.
|
|
37
|
+
* 'white' → 'whiteTheme', 'g10' → 'g10' (no dash, unlike SCSS convention)
|
|
38
|
+
* @param {string} theme
|
|
39
|
+
* @returns {string}
|
|
40
|
+
*/
|
|
41
|
+
function normalizeThemeKey(theme) {
|
|
42
|
+
if (theme === 'white') return 'whiteTheme';
|
|
43
|
+
return theme; // 'g10', 'g90', 'g100' — unchanged
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Serialize a JS value to source-code string.
|
|
48
|
+
* @param {*} value
|
|
49
|
+
* @returns {string}
|
|
50
|
+
*/
|
|
51
|
+
function toSource(value) {
|
|
52
|
+
if (typeof value === 'string') {
|
|
53
|
+
return `'${value.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
|
|
54
|
+
}
|
|
55
|
+
return JSON.stringify(value);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Group SD tokens by component-token name and theme.
|
|
60
|
+
* Mirrors the logic in carbon-scss-component-tokens.js.
|
|
61
|
+
*
|
|
62
|
+
* @param {import('style-dictionary').Token[]} allTokens
|
|
63
|
+
* @returns {Map<string, Record<string, string>>}
|
|
64
|
+
*/
|
|
65
|
+
function groupByTokenAndTheme(allTokens) {
|
|
66
|
+
const result = new Map();
|
|
67
|
+
const BY_THEME_MARKER = '-by-theme-';
|
|
68
|
+
|
|
69
|
+
for (const token of allTokens) {
|
|
70
|
+
const idx = token.name.indexOf(BY_THEME_MARKER);
|
|
71
|
+
if (idx === -1) continue;
|
|
72
|
+
|
|
73
|
+
const tokenName = token.name.slice(0, idx);
|
|
74
|
+
const theme = token.name.slice(idx + BY_THEME_MARKER.length);
|
|
75
|
+
|
|
76
|
+
if (!result.has(tokenName)) {
|
|
77
|
+
result.set(tokenName, {});
|
|
78
|
+
}
|
|
79
|
+
result.get(tokenName)[theme] = token.value ?? token.$value;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* @param {{ dictionary: import('style-dictionary').Dictionary, options: object }} args
|
|
87
|
+
* @returns {string}
|
|
88
|
+
*/
|
|
89
|
+
function carbonJsComponentTokensFormat({ dictionary, options }) {
|
|
90
|
+
const outputMode = options?.output ?? 'js'; // 'js' | 'dts'
|
|
91
|
+
|
|
92
|
+
const grouped = groupByTokenAndTheme(dictionary.allTokens);
|
|
93
|
+
|
|
94
|
+
// Preserve insertion order from the DTCG JSON walk.
|
|
95
|
+
const sortedNames = [...grouped.keys()];
|
|
96
|
+
|
|
97
|
+
const lines = [FILE_BANNER];
|
|
98
|
+
|
|
99
|
+
for (const tokenName of sortedNames) {
|
|
100
|
+
const themeValues = grouped.get(tokenName);
|
|
101
|
+
const camelName = kebabToCamel(tokenName);
|
|
102
|
+
|
|
103
|
+
// Preserve JSON source order exactly.
|
|
104
|
+
const themes = Object.keys(themeValues);
|
|
105
|
+
|
|
106
|
+
if (outputMode === 'js') {
|
|
107
|
+
const props = themes
|
|
108
|
+
.map((theme) => {
|
|
109
|
+
const key = normalizeThemeKey(theme);
|
|
110
|
+
return ` ${key}: ${toSource(themeValues[theme])}`;
|
|
111
|
+
})
|
|
112
|
+
.join(',\n');
|
|
113
|
+
lines.push(`export const ${camelName} = {\n${props},\n};`);
|
|
114
|
+
} else {
|
|
115
|
+
const props = themes
|
|
116
|
+
.map((theme) => ` ${normalizeThemeKey(theme)}: string`)
|
|
117
|
+
.join(';\n');
|
|
118
|
+
lines.push(`export declare const ${camelName}: {\n${props};\n};`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return lines.join('\n') + '\n';
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
module.exports = {
|
|
126
|
+
name: 'carbon/js-component-tokens',
|
|
127
|
+
format: carbonJsComponentTokensFormat,
|
|
128
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
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
|
+
* Receives all tokens for ONE theme and emits two files (via SD's
|
|
13
|
+
* `files` array):
|
|
14
|
+
* - `<themeName>.js` — ES module with `export const camelName = value;`
|
|
15
|
+
* - `<themeName>.d.ts` — companion TypeScript declarations
|
|
16
|
+
*
|
|
17
|
+
* Token names arrive as kebab-case from SD and are converted to camelCase.
|
|
18
|
+
*
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const FILE_BANNER = `// Code generated by @carbon/themes. DO NOT EDIT.
|
|
22
|
+
//
|
|
23
|
+
// Copyright IBM Corp. 2018, 2025
|
|
24
|
+
//
|
|
25
|
+
// This source code is licensed under the Apache-2.0 license found in the
|
|
26
|
+
// LICENSE file in the root directory of this source tree.
|
|
27
|
+
`;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Convert kebab-case to camelCase.
|
|
31
|
+
* @param {string} str
|
|
32
|
+
* @returns {string}
|
|
33
|
+
*/
|
|
34
|
+
function kebabToCamel(str) {
|
|
35
|
+
return str.replace(/-([a-z0-9])/g, (_, ch) => ch.toUpperCase());
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Serialize a JS value to a source-code string.
|
|
40
|
+
* Strings get single-quoted; everything else uses JSON.
|
|
41
|
+
* @param {*} value
|
|
42
|
+
* @returns {string}
|
|
43
|
+
*/
|
|
44
|
+
function toSource(value) {
|
|
45
|
+
if (typeof value === 'string') {
|
|
46
|
+
return `'${value.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
|
|
47
|
+
}
|
|
48
|
+
return JSON.stringify(value);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @param {{ dictionary: import('style-dictionary').Dictionary, options: object }} args
|
|
53
|
+
* @returns {string}
|
|
54
|
+
*/
|
|
55
|
+
function carbonJsThemesFormat({ dictionary, options }) {
|
|
56
|
+
const outputMode = options?.output ?? 'js'; // 'js' | 'dts'
|
|
57
|
+
|
|
58
|
+
const lines = [FILE_BANNER];
|
|
59
|
+
const sorted = dictionary.allTokens;
|
|
60
|
+
|
|
61
|
+
for (const token of sorted) {
|
|
62
|
+
const camelName = kebabToCamel(token.name);
|
|
63
|
+
// SD v5 may store the resolved alias as a full token node object in
|
|
64
|
+
// token.value when the alias points to a sibling token. Unwrap it.
|
|
65
|
+
let value = token.value ?? token.$value;
|
|
66
|
+
if (value !== null && typeof value === 'object' && '$value' in value) {
|
|
67
|
+
value = value.$value;
|
|
68
|
+
}
|
|
69
|
+
const tsType = typeof value === 'string' ? 'string' : 'number';
|
|
70
|
+
|
|
71
|
+
if (outputMode === 'js') {
|
|
72
|
+
lines.push(`export const ${camelName} = ${toSource(value)};`);
|
|
73
|
+
} else {
|
|
74
|
+
lines.push(`export declare const ${camelName}: ${tsType};`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return lines.join('\n') + '\n';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
module.exports = {
|
|
82
|
+
name: 'carbon/js-themes',
|
|
83
|
+
format: carbonJsThemesFormat,
|
|
84
|
+
};
|
|
@@ -0,0 +1,149 @@
|
|
|
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
|
+
* Receives the tokens for ONE component (already expanded from the
|
|
12
|
+
* carbon.themes multi-theme shape by the `component-tokens`
|
|
13
|
+
* preprocessor). Emits a Sass map variable per token, keyed by normalised
|
|
14
|
+
* theme name:
|
|
15
|
+
*
|
|
16
|
+
* $button-danger-active: (
|
|
17
|
+
* 'white-theme': #a2191f,
|
|
18
|
+
* 'g-10': #a2191f,
|
|
19
|
+
* 'g-90': #a2191f,
|
|
20
|
+
* 'g-100': #a2191f,
|
|
21
|
+
* ) !default;
|
|
22
|
+
*
|
|
23
|
+
*
|
|
24
|
+
* The token tree must have been pre-processed by `carbon/component-tokens`
|
|
25
|
+
* so that each token has a `_by_theme` child group containing one resolved
|
|
26
|
+
* token per theme.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
const FILE_BANNER = `// Code generated by @carbon/themes. DO NOT EDIT.
|
|
30
|
+
//
|
|
31
|
+
// Copyright IBM Corp. 2018, 2023
|
|
32
|
+
//
|
|
33
|
+
// This source code is licensed under the Apache-2.0 license found in the
|
|
34
|
+
// LICENSE file in the root directory of this source tree.
|
|
35
|
+
//
|
|
36
|
+
`;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Normalize a raw theme key to the Carbon SCSS convention.
|
|
40
|
+
* @param {string} theme
|
|
41
|
+
* @returns {string}
|
|
42
|
+
*/
|
|
43
|
+
function normalizeThemeName(theme) {
|
|
44
|
+
if (theme === 'white') return 'white-theme';
|
|
45
|
+
if (/^g\d+$/.test(theme)) return theme.replace(/^g(\d+)$/, 'g-$1');
|
|
46
|
+
return theme;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Serialize a CSS value to a Sass literal (same logic as carbon-scss-themes).
|
|
51
|
+
* @param {string|number} value
|
|
52
|
+
* @returns {string}
|
|
53
|
+
*/
|
|
54
|
+
function toSassLiteral(value) {
|
|
55
|
+
if (typeof value === 'number') return String(value);
|
|
56
|
+
if (typeof value !== 'string') return JSON.stringify(value);
|
|
57
|
+
if (value.startsWith('#') || value.startsWith('rgb')) return value;
|
|
58
|
+
if (
|
|
59
|
+
value.endsWith('px') ||
|
|
60
|
+
value.endsWith('em') ||
|
|
61
|
+
value.endsWith('%') ||
|
|
62
|
+
value === '0'
|
|
63
|
+
)
|
|
64
|
+
return value;
|
|
65
|
+
return `string.unquote("${value}")`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Group SD tokens by their component-token name and theme.
|
|
70
|
+
*
|
|
71
|
+
* After the carbon/component-tokens preprocessor runs, tokens in the
|
|
72
|
+
* dictionary look like:
|
|
73
|
+
*
|
|
74
|
+
* button-danger-active-by-theme-white (name after flattening)
|
|
75
|
+
* button-danger-active-by-theme-g10
|
|
76
|
+
* ...
|
|
77
|
+
*
|
|
78
|
+
* We strip the `_by_theme-` infix and reconstruct the map.
|
|
79
|
+
*
|
|
80
|
+
* @param {import('style-dictionary').Token[]} allTokens
|
|
81
|
+
* @returns {Map<string, Record<string, string>>} tokenName → { theme: value }
|
|
82
|
+
*/
|
|
83
|
+
function groupByTokenAndTheme(allTokens) {
|
|
84
|
+
const result = new Map();
|
|
85
|
+
|
|
86
|
+
for (const token of allTokens) {
|
|
87
|
+
// Token names contain '_by_theme' as an injected path segment.
|
|
88
|
+
// e.g. 'button-danger-active-by-theme-white' — but SD uses whatever
|
|
89
|
+
// separator the platform config specifies (usually '-').
|
|
90
|
+
// Match the segment just before a known theme key.
|
|
91
|
+
const BY_THEME_MARKER = '-by-theme-';
|
|
92
|
+
const idx = token.name.indexOf(BY_THEME_MARKER);
|
|
93
|
+
if (idx === -1) continue;
|
|
94
|
+
|
|
95
|
+
const tokenName = token.name.slice(0, idx);
|
|
96
|
+
const theme = token.name.slice(idx + BY_THEME_MARKER.length);
|
|
97
|
+
|
|
98
|
+
if (!result.has(tokenName)) {
|
|
99
|
+
result.set(tokenName, {});
|
|
100
|
+
}
|
|
101
|
+
result.get(tokenName)[theme] = token.value ?? token.$value;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return result;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* @param {{ dictionary: import('style-dictionary').Dictionary }} args
|
|
109
|
+
* @returns {string}
|
|
110
|
+
*/
|
|
111
|
+
function carbonScssComponentTokensFormat({ dictionary }) {
|
|
112
|
+
const grouped = groupByTokenAndTheme(dictionary.allTokens);
|
|
113
|
+
|
|
114
|
+
// Preserve insertion order from the DTCG JSON walk — do NOT sort here.
|
|
115
|
+
// The old builder also sorted alphabetically, but the compare script passes
|
|
116
|
+
// tokens in JSON order so we honour that to get a byte-for-byte match.
|
|
117
|
+
const sortedNames = [...grouped.keys()];
|
|
118
|
+
|
|
119
|
+
const variableBlocks = sortedNames.map((tokenName) => {
|
|
120
|
+
const themeValues = grouped.get(tokenName);
|
|
121
|
+
|
|
122
|
+
// Preserve JSON source order exactly.
|
|
123
|
+
const themes = Object.keys(themeValues);
|
|
124
|
+
|
|
125
|
+
const entries = themes
|
|
126
|
+
.map((theme) => {
|
|
127
|
+
const normalized = normalizeThemeName(theme);
|
|
128
|
+
const value = themeValues[theme];
|
|
129
|
+
// Unquoted key — matches t.SassMapProperty(t.Identifier(theme), ...) in old builder
|
|
130
|
+
return ` ${normalized}: ${toSassLiteral(value)},`;
|
|
131
|
+
})
|
|
132
|
+
.join('\n');
|
|
133
|
+
|
|
134
|
+
return `\n$${tokenName}: (\n${entries}\n) !default;`;
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
return (
|
|
138
|
+
FILE_BANNER +
|
|
139
|
+
`\n@use 'sass:map';\n` +
|
|
140
|
+
`@use 'sass:string';\n` +
|
|
141
|
+
variableBlocks.join('\n') +
|
|
142
|
+
'\n'
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
module.exports = {
|
|
147
|
+
name: 'carbon/scss-component-tokens',
|
|
148
|
+
format: carbonScssComponentTokensFormat,
|
|
149
|
+
};
|
|
@@ -0,0 +1,127 @@
|
|
|
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
|
+
*
|
|
13
|
+
* Each theme (white, g10, g90, g100) is expected to be a separate SD
|
|
14
|
+
* *platform* build run. This format receives all tokens for ONE theme and
|
|
15
|
+
* emits the Sass map variable for that theme plus the utilities.merge call
|
|
16
|
+
* that folds in layout spacing and type tokens.
|
|
17
|
+
*
|
|
18
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
19
|
+
* NOTE ON MULTI-THEME FILES
|
|
20
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
21
|
+
* The recommended approach: run all four platforms, collect their individual
|
|
22
|
+
* strings, and concatenate them in sd.config.js using a custom action.
|
|
23
|
+
* A simpler alternative (and the one wired up in sd.config.js) is to use SD's
|
|
24
|
+
* `buildAllPlatforms()` with four platforms that each write to a *separate*
|
|
25
|
+
* file, then a post-build step (`buildAllPlatforms` custom action) concatenates
|
|
26
|
+
* them. Both patterns are documented in style-dictionary/README.md.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
const FILE_BANNER = `// Code generated by @carbon/themes. DO NOT EDIT.
|
|
30
|
+
//
|
|
31
|
+
// Copyright IBM Corp. 2018, 2023
|
|
32
|
+
//
|
|
33
|
+
// This source code is licensed under the Apache-2.0 license found in the
|
|
34
|
+
// LICENSE file in the root directory of this source tree.
|
|
35
|
+
//
|
|
36
|
+
`;
|
|
37
|
+
|
|
38
|
+
const SCSS_IMPORTS = `@use 'sass:map';
|
|
39
|
+
@use 'sass:string';
|
|
40
|
+
@use '@carbon/layout';
|
|
41
|
+
@use '@carbon/type';
|
|
42
|
+
@use '../utilities';
|
|
43
|
+
`;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Serialize a CSS color value to a Sass literal.
|
|
47
|
+
*
|
|
48
|
+
* - Hex colors → bare hex (e.g. #ffffff)
|
|
49
|
+
* - rgba() → bare function (e.g. rgba(141, 141, 141, 0.5))
|
|
50
|
+
* - Strings → string.unquote("…")
|
|
51
|
+
* - Numbers → bare number
|
|
52
|
+
*
|
|
53
|
+
* @param {string|number} value
|
|
54
|
+
* @returns {string} Sass source fragment
|
|
55
|
+
*/
|
|
56
|
+
function toSassLiteral(value) {
|
|
57
|
+
if (typeof value === 'number') {
|
|
58
|
+
return String(value);
|
|
59
|
+
}
|
|
60
|
+
if (typeof value !== 'string') {
|
|
61
|
+
return JSON.stringify(value);
|
|
62
|
+
}
|
|
63
|
+
if (value.startsWith('#')) {
|
|
64
|
+
return value;
|
|
65
|
+
}
|
|
66
|
+
if (
|
|
67
|
+
value.startsWith('rgb') ||
|
|
68
|
+
value.endsWith('px') ||
|
|
69
|
+
value.endsWith('em') ||
|
|
70
|
+
value.endsWith('%') ||
|
|
71
|
+
value.endsWith('vw') ||
|
|
72
|
+
value.endsWith('vh') ||
|
|
73
|
+
value === '0'
|
|
74
|
+
) {
|
|
75
|
+
return value;
|
|
76
|
+
}
|
|
77
|
+
// Plain string (e.g. 'light', 'dark') → Sass unquote with single quotes,
|
|
78
|
+
// matching t.SassValue("string.unquote('...')") in the old builder.
|
|
79
|
+
return `string.unquote('${value}')`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* @param {{ dictionary: import('style-dictionary').Dictionary, options: object, file: object }} args
|
|
84
|
+
* @returns {string}
|
|
85
|
+
*/
|
|
86
|
+
function carbonScssThemesFormat({ dictionary, options, file }) {
|
|
87
|
+
const themeName = options?.themeName ?? file?.options?.themeName ?? 'unknown';
|
|
88
|
+
|
|
89
|
+
// Sort alphabetically — matches dtcg-themes.js which calls .sort(([a],[b])=>a.localeCompare(b))
|
|
90
|
+
// color-scheme lands at its natural alpha position ('c' section).
|
|
91
|
+
const sorted = [...dictionary.allTokens].sort((a, b) =>
|
|
92
|
+
a.name.localeCompare(b.name)
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
const entries = sorted
|
|
96
|
+
.map((token) => {
|
|
97
|
+
const name = token.name;
|
|
98
|
+
// SD v5 may store the post-alias value as a full token node object in
|
|
99
|
+
// token.value when an alias points to a sibling token. Unwrap it.
|
|
100
|
+
let value = token.value ?? token.$value;
|
|
101
|
+
if (value !== null && typeof value === 'object' && '$value' in value) {
|
|
102
|
+
value = value.$value;
|
|
103
|
+
}
|
|
104
|
+
// Unquoted key — matches t.SassMapProperty(t.Identifier(token), ...) in old builder
|
|
105
|
+
return ` ${name}: ${toSassLiteral(value)},`;
|
|
106
|
+
})
|
|
107
|
+
.join('\n');
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
FILE_BANNER +
|
|
111
|
+
'\n' +
|
|
112
|
+
SCSS_IMPORTS +
|
|
113
|
+
'\n' +
|
|
114
|
+
`$${themeName}: (\n${entries}\n) !default;\n` +
|
|
115
|
+
`$${themeName}: utilities.merge(\n` +
|
|
116
|
+
` $${themeName},\n` +
|
|
117
|
+
` layout.$spacing,\n` +
|
|
118
|
+
` layout.$fluid-spacing,\n` +
|
|
119
|
+
` type.$tokens\n` +
|
|
120
|
+
`);\n`
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
module.exports = {
|
|
125
|
+
name: 'carbon/scss-themes',
|
|
126
|
+
format: carbonScssThemesFormat,
|
|
127
|
+
};
|
|
@@ -0,0 +1,91 @@
|
|
|
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
|
+
* The format does not actually use the SD `dictionary` — it reads the token
|
|
12
|
+
* list from the Carbon JS metadata directly, matching the original builder.
|
|
13
|
+
* This is intentional: _tokens.scss is a mapping layer, not derived from the
|
|
14
|
+
* token values.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const { types: t } = require('@carbon/scss-generator');
|
|
18
|
+
const { generate } = require('@carbon/scss-generator');
|
|
19
|
+
const { group } = require('../../src/tokens');
|
|
20
|
+
|
|
21
|
+
const FILE_BANNER = `// Code generated by @carbon/themes. DO NOT EDIT.
|
|
22
|
+
//
|
|
23
|
+
// Copyright IBM Corp. 2018, 2023
|
|
24
|
+
//
|
|
25
|
+
// This source code is licensed under the Apache-2.0 license found in the
|
|
26
|
+
// LICENSE file in the root directory of this source tree.
|
|
27
|
+
//
|
|
28
|
+
`;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @returns {Promise<string>}
|
|
32
|
+
*/
|
|
33
|
+
async function carbonScssTokensFormat() {
|
|
34
|
+
const tokens = group.getTokens();
|
|
35
|
+
|
|
36
|
+
const variables = tokens.flatMap((token) => {
|
|
37
|
+
const id = token.name;
|
|
38
|
+
return [
|
|
39
|
+
t.Newline(),
|
|
40
|
+
t.Comment(`/ The CSS Custom Property for the \`${id}\` token`),
|
|
41
|
+
t.Assignment({
|
|
42
|
+
id: t.Identifier(id),
|
|
43
|
+
init: t.SassFunctionCall({
|
|
44
|
+
id: t.Identifier('_get'),
|
|
45
|
+
params: [t.SassString(id)],
|
|
46
|
+
}),
|
|
47
|
+
default: true,
|
|
48
|
+
}),
|
|
49
|
+
];
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const stylesheet = t.StyleSheet([
|
|
53
|
+
t.SassModule('sass:map'),
|
|
54
|
+
t.SassModule('../config'),
|
|
55
|
+
t.SassModule('../theme'),
|
|
56
|
+
t.Newline(),
|
|
57
|
+
t.Comment('/ Internal helper for generating CSS Custom Properties'),
|
|
58
|
+
t.SassFunction({
|
|
59
|
+
id: t.Identifier('_get'),
|
|
60
|
+
params: [t.Identifier('token')],
|
|
61
|
+
body: t.BlockStatement([
|
|
62
|
+
t.IfStatement({
|
|
63
|
+
test: t.LogicalExpression({
|
|
64
|
+
left: t.SassValue('config.$use-fallback-value'),
|
|
65
|
+
operator: '==',
|
|
66
|
+
right: t.SassBoolean(false),
|
|
67
|
+
}),
|
|
68
|
+
consequent: t.BlockStatement([
|
|
69
|
+
t.AtReturn(t.SassValue('var(--#{config.$prefix}-#{$token})')),
|
|
70
|
+
]),
|
|
71
|
+
alternate: t.BlockStatement([
|
|
72
|
+
t.AtReturn(
|
|
73
|
+
t.SassValue(
|
|
74
|
+
'var(--#{config.$prefix}-#{$token}, #{theme.get($token)})'
|
|
75
|
+
)
|
|
76
|
+
),
|
|
77
|
+
]),
|
|
78
|
+
}),
|
|
79
|
+
]),
|
|
80
|
+
}),
|
|
81
|
+
...variables,
|
|
82
|
+
]);
|
|
83
|
+
|
|
84
|
+
const { code } = generate(stylesheet);
|
|
85
|
+
return FILE_BANNER + '\n' + (await code);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
module.exports = {
|
|
89
|
+
name: 'carbon/scss-tokens',
|
|
90
|
+
format: carbonScssTokensFormat,
|
|
91
|
+
};
|
|
@@ -0,0 +1,119 @@
|
|
|
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
|
+
* Preprocessor: carbon/component-tokens
|
|
12
|
+
*
|
|
13
|
+
* Expands the `$extensions["carbon.themes"]` multi-theme shape that component
|
|
14
|
+
* token files (button.json, tag.json, …) use into flat per-theme tokens that
|
|
15
|
+
* Style Dictionary can process natively.
|
|
16
|
+
*
|
|
17
|
+
* Input — one token node with no $value but a per-theme map in extensions:
|
|
18
|
+
*
|
|
19
|
+
* "button": {
|
|
20
|
+
* "danger-active": {
|
|
21
|
+
* "$type": "color",
|
|
22
|
+
* "$extensions": {
|
|
23
|
+
* "carbon.themes": { "white": "{red.80}", "g10": "{red.80}", ... },
|
|
24
|
+
* "org.carbon": { "alphaModifiers": { "g90": 0.3, "g100": 0.3 } }
|
|
25
|
+
* }
|
|
26
|
+
* }
|
|
27
|
+
* }
|
|
28
|
+
*
|
|
29
|
+
* Output — one flat sibling token per theme, keyed as
|
|
30
|
+
* `<tokenPath>-_by_theme-<themeName>` so the format functions can reconstruct
|
|
31
|
+
* the original token name and theme:
|
|
32
|
+
*
|
|
33
|
+
* "button": {
|
|
34
|
+
* "danger-active": {
|
|
35
|
+
* "_by_theme": {
|
|
36
|
+
* "white": { "$type": "color", "$value": "{red.80}" },
|
|
37
|
+
* "g10": { "$type": "color", "$value": "{red.80}" },
|
|
38
|
+
* "g90": { "$type": "color", "$value": "{red.80}",
|
|
39
|
+
* "$extensions": { "org.carbon": { "alphaModifier": 0.3 } } },
|
|
40
|
+
* "g100": { "$type": "color", "$value": "{red.80}",
|
|
41
|
+
* "$extensions": { "org.carbon": { "alphaModifier": 0.3 } } }
|
|
42
|
+
* }
|
|
43
|
+
* }
|
|
44
|
+
* }
|
|
45
|
+
*
|
|
46
|
+
* SD v5 walks DTCG token trees and treats any node with a `$value` as a leaf
|
|
47
|
+
* token. The `_by_theme.<themeName>` nodes each have `$value`, so SD resolves
|
|
48
|
+
* aliases and applies transforms on them individually.
|
|
49
|
+
*
|
|
50
|
+
* The format functions identify these tokens by the `-_by_theme-` infix in
|
|
51
|
+
* their flattened name (using the `name/kebab` transform).
|
|
52
|
+
*
|
|
53
|
+
* Tokens that already carry a top-level $value are left untouched.
|
|
54
|
+
*
|
|
55
|
+
* @param {object} dictionary Raw DTCG token tree passed by SD
|
|
56
|
+
* @returns {object} Expanded token tree
|
|
57
|
+
*/
|
|
58
|
+
function carbonComponentTokensPreprocessor(dictionary) {
|
|
59
|
+
function walk(node) {
|
|
60
|
+
if (!node || typeof node !== 'object') return node;
|
|
61
|
+
|
|
62
|
+
const out = {};
|
|
63
|
+
|
|
64
|
+
for (const [key, value] of Object.entries(node)) {
|
|
65
|
+
if (key.startsWith('$')) {
|
|
66
|
+
out[key] = value;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!value || typeof value !== 'object') {
|
|
71
|
+
out[key] = value;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const carbonThemes = value.$extensions?.['carbon.themes'];
|
|
76
|
+
const alphaModifiers =
|
|
77
|
+
value.$extensions?.['org.carbon']?.alphaModifiers ?? {};
|
|
78
|
+
|
|
79
|
+
// Only expand nodes that have carbon.themes and NO existing $value.
|
|
80
|
+
if (carbonThemes && value.$value === undefined) {
|
|
81
|
+
const byTheme = {};
|
|
82
|
+
|
|
83
|
+
// Preserve original JSON key order (important for 'fallback' position)
|
|
84
|
+
for (const [theme, themeValue] of Object.entries(carbonThemes)) {
|
|
85
|
+
const alpha = alphaModifiers[theme];
|
|
86
|
+
const syntheticNode = {
|
|
87
|
+
$type: value.$type ?? 'color',
|
|
88
|
+
$value: themeValue,
|
|
89
|
+
};
|
|
90
|
+
if (alpha !== undefined) {
|
|
91
|
+
syntheticNode.$extensions = {
|
|
92
|
+
'org.carbon': { alphaModifier: alpha },
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
byTheme[theme] = syntheticNode;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
out[key] = {
|
|
99
|
+
...(value.$type ? { $type: value.$type } : {}),
|
|
100
|
+
...(value.$description ? { $description: value.$description } : {}),
|
|
101
|
+
// _by_theme is a DTCG group — each child is a leaf token with $value
|
|
102
|
+
_by_theme: byTheme,
|
|
103
|
+
};
|
|
104
|
+
} else {
|
|
105
|
+
// Recurse into nested groups
|
|
106
|
+
out[key] = walk(value);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return out;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return walk(dictionary);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
module.exports = {
|
|
117
|
+
name: 'carbon/component-tokens',
|
|
118
|
+
preprocessor: carbonComponentTokensPreprocessor,
|
|
119
|
+
};
|