@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,127 +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
|
-
*
|
|
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
|
-
};
|
|
@@ -1,91 +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
|
-
* 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
|
-
};
|
|
@@ -1,172 +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
|
-
* 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
|
-
* Two equivalent forms are accepted:
|
|
19
|
-
*
|
|
20
|
-
* Component-token form (legacy, bare strings + separate alphaModifiers):
|
|
21
|
-
* "button": {
|
|
22
|
-
* "danger-active": {
|
|
23
|
-
* "$type": "color",
|
|
24
|
-
* "$extensions": {
|
|
25
|
-
* "carbon.themes": { "white": "{red.80}", "g10": "{red.80}", ... },
|
|
26
|
-
* "org.carbon": { "alphaModifiers": { "g90": 0.3, "g100": 0.3 } }
|
|
27
|
-
* }
|
|
28
|
-
* }
|
|
29
|
-
* }
|
|
30
|
-
*
|
|
31
|
-
* Unified theme-token form (value + alpha co-located per theme):
|
|
32
|
-
* "background": {
|
|
33
|
-
* "hover": {
|
|
34
|
-
* "$type": "color",
|
|
35
|
-
* "$extensions": {
|
|
36
|
-
* "carbon.themes": {
|
|
37
|
-
* "white": { "value": "{gray.50}", "alpha": 0.12 },
|
|
38
|
-
* "g10": { "value": "{gray.50}", "alpha": 0.12 },
|
|
39
|
-
* "g90": "{gray.50}",
|
|
40
|
-
* "g100": "{gray.50}"
|
|
41
|
-
* }
|
|
42
|
-
* }
|
|
43
|
-
* }
|
|
44
|
-
* }
|
|
45
|
-
*
|
|
46
|
-
* Output — one flat sibling token per theme, keyed as
|
|
47
|
-
* `<tokenPath>-_by_theme-<themeName>` so the format functions can reconstruct
|
|
48
|
-
* the original token name and theme:
|
|
49
|
-
*
|
|
50
|
-
* "button": {
|
|
51
|
-
* "danger-active": {
|
|
52
|
-
* "_by_theme": {
|
|
53
|
-
* "white": { "$type": "color", "$value": "{red.80}" },
|
|
54
|
-
* "g10": { "$type": "color", "$value": "{red.80}" },
|
|
55
|
-
* "g90": { "$type": "color", "$value": "{red.80}",
|
|
56
|
-
* "$extensions": { "org.carbon": { "alphaModifier": 0.3 } } },
|
|
57
|
-
* "g100": { "$type": "color", "$value": "{red.80}",
|
|
58
|
-
* "$extensions": { "org.carbon": { "alphaModifier": 0.3 } } }
|
|
59
|
-
* }
|
|
60
|
-
* }
|
|
61
|
-
* }
|
|
62
|
-
*
|
|
63
|
-
* SD v5 walks DTCG token trees and treats any node with a `$value` as a leaf
|
|
64
|
-
* token. The `_by_theme.<themeName>` nodes each have `$value`, so SD resolves
|
|
65
|
-
* aliases and applies transforms on them individually.
|
|
66
|
-
*
|
|
67
|
-
* The format functions identify these tokens by the `-_by_theme-` infix in
|
|
68
|
-
* their flattened name (using the `name/kebab` transform).
|
|
69
|
-
*
|
|
70
|
-
* Tokens that already carry a top-level $value are left untouched.
|
|
71
|
-
*
|
|
72
|
-
* @param {object} dictionary Raw DTCG token tree passed by SD
|
|
73
|
-
* @returns {object} Expanded token tree
|
|
74
|
-
*/
|
|
75
|
-
function carbonComponentTokensPreprocessor(dictionary) {
|
|
76
|
-
function walk(node) {
|
|
77
|
-
if (!node || typeof node !== 'object') return node;
|
|
78
|
-
|
|
79
|
-
const out = {};
|
|
80
|
-
|
|
81
|
-
for (const [key, value] of Object.entries(node)) {
|
|
82
|
-
if (key.startsWith('$')) {
|
|
83
|
-
out[key] = value;
|
|
84
|
-
continue;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
if (!value || typeof value !== 'object') {
|
|
88
|
-
out[key] = value;
|
|
89
|
-
continue;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const carbonThemes = value.$extensions?.['carbon.themes'];
|
|
93
|
-
const alphaModifiers =
|
|
94
|
-
value.$extensions?.['org.carbon']?.alphaModifiers ?? {};
|
|
95
|
-
|
|
96
|
-
// Only expand nodes that have carbon.themes and NO existing $value.
|
|
97
|
-
if (carbonThemes && value.$value === undefined) {
|
|
98
|
-
const byTheme = {};
|
|
99
|
-
|
|
100
|
-
// Preserve original JSON key order (important for 'fallback' position).
|
|
101
|
-
// Each entry in carbon.themes is either:
|
|
102
|
-
// - a bare string/alias → { "white": "{red.80}" } (legacy component form)
|
|
103
|
-
// - an object { value, alpha? } → unified theme form where value and
|
|
104
|
-
// alpha are co-located under the same theme key.
|
|
105
|
-
// The legacy separate "org.carbon.alphaModifiers" map is still supported
|
|
106
|
-
// for backwards-compat with existing component token files.
|
|
107
|
-
for (const [theme, themeEntry] of Object.entries(carbonThemes)) {
|
|
108
|
-
// An entry can be:
|
|
109
|
-
// 1. A bare string alias/hex → treat as the value directly
|
|
110
|
-
// 2. A composite color object → { colorSpace, components, hex }
|
|
111
|
-
// Used for hardcoded non-palette colors. Treat as the value directly.
|
|
112
|
-
// 3. A { value, alpha? } wrapper object → unified theme format
|
|
113
|
-
const isComposite =
|
|
114
|
-
themeEntry !== null &&
|
|
115
|
-
typeof themeEntry === 'object' &&
|
|
116
|
-
!Array.isArray(themeEntry) &&
|
|
117
|
-
'hex' in themeEntry;
|
|
118
|
-
const isWrapper =
|
|
119
|
-
themeEntry !== null &&
|
|
120
|
-
typeof themeEntry === 'object' &&
|
|
121
|
-
!Array.isArray(themeEntry) &&
|
|
122
|
-
!isComposite;
|
|
123
|
-
const themeValue = isWrapper ? themeEntry.value : themeEntry;
|
|
124
|
-
// Per-theme alpha: only { value, alpha } wrappers carry alpha.
|
|
125
|
-
// Legacy org.carbon.alphaModifiers map is the fallback for component files.
|
|
126
|
-
const alpha = isWrapper ? themeEntry.alpha : alphaModifiers[theme];
|
|
127
|
-
const syntheticNode = {
|
|
128
|
-
$type: value.$type ?? 'color',
|
|
129
|
-
$value: themeValue,
|
|
130
|
-
};
|
|
131
|
-
if (alpha !== undefined) {
|
|
132
|
-
syntheticNode.$extensions = {
|
|
133
|
-
'org.carbon': { alphaModifier: alpha },
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
byTheme[theme] = syntheticNode;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// Build the expanded node: preserve DTCG metadata, add _by_theme, and
|
|
140
|
-
// recurse into any non-$ children (dual-role nodes in themes.json have
|
|
141
|
-
// both a carbon.themes value AND group children like `active`, `hover`).
|
|
142
|
-
const expandedNode = {
|
|
143
|
-
...(value.$type ? { $type: value.$type } : {}),
|
|
144
|
-
...(value.$description ? { $description: value.$description } : {}),
|
|
145
|
-
// _by_theme is a DTCG group — each child is a leaf token with $value
|
|
146
|
-
_by_theme: byTheme,
|
|
147
|
-
};
|
|
148
|
-
for (const [childKey, childValue] of Object.entries(value)) {
|
|
149
|
-
if (childKey.startsWith('$') || childKey === '_by_theme') continue;
|
|
150
|
-
if (!childValue || typeof childValue !== 'object') continue;
|
|
151
|
-
// Wrap in a single-key object so walk() sees the key and can apply
|
|
152
|
-
// the carbon.themes expansion logic to it (walk iterates the entries
|
|
153
|
-
// of the object passed in, so childValue alone would skip expansion).
|
|
154
|
-
expandedNode[childKey] = walk({ [childKey]: childValue })[childKey];
|
|
155
|
-
}
|
|
156
|
-
out[key] = expandedNode;
|
|
157
|
-
} else {
|
|
158
|
-
// Recurse into nested groups
|
|
159
|
-
out[key] = walk(value);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
return out;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
return walk(dictionary);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
module.exports = {
|
|
170
|
-
name: 'carbon/component-tokens',
|
|
171
|
-
preprocessor: carbonComponentTokensPreprocessor,
|
|
172
|
-
};
|
|
@@ -1,157 +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
|
-
* The unified themes.json (and previously the individual per-theme files) can
|
|
13
|
-
* contain nodes that are simultaneously token leaves (they have a `$value`) AND
|
|
14
|
-
* token groups (they have non-`$`-prefixed children). Style Dictionary v5 does
|
|
15
|
-
* not support this pattern — when a node has `$value`, SD treats it as a leaf
|
|
16
|
-
* token and silently drops any non-`$` children.
|
|
17
|
-
*
|
|
18
|
-
* Example:
|
|
19
|
-
*
|
|
20
|
-
* background: {
|
|
21
|
-
* $type: "color",
|
|
22
|
-
* $value: "{gray.10}", ← token leaf value
|
|
23
|
-
* active: { $type: "color", $value: "{gray.50}", ... }, ← child token
|
|
24
|
-
* brand: { ... }
|
|
25
|
-
* }
|
|
26
|
-
*
|
|
27
|
-
* Without this preprocessor, SD emits `background` but loses `background-active`
|
|
28
|
-
* and `background-brand`. There are 27 such dual-role nodes in each of
|
|
29
|
-
* g10 / g90 / g100, causing 48 tokens to go missing from the generated output.
|
|
30
|
-
*
|
|
31
|
-
* ── Solution ────────────────────────────────────────────────────────────────
|
|
32
|
-
*
|
|
33
|
-
* For any dual-role node (has `$value` AND non-`$` children), this preprocessor
|
|
34
|
-
* splits the node:
|
|
35
|
-
*
|
|
36
|
-
* 1. The parent's token value (`$value`, `$type`, `$extensions`) is moved into
|
|
37
|
-
* a synthetic `_self` child at the same level.
|
|
38
|
-
* 2. The parent node becomes a pure group (its `$value` is removed).
|
|
39
|
-
*
|
|
40
|
-
* After this transform, the tree becomes:
|
|
41
|
-
*
|
|
42
|
-
* background: {
|
|
43
|
-
* _self: { $type: "color", $value: "{gray.10}" }, ← parent value
|
|
44
|
-
* active: { $type: "color", $value: "{gray.50}", ... },
|
|
45
|
-
* brand: { ... }
|
|
46
|
-
* }
|
|
47
|
-
*
|
|
48
|
-
* SD sees `background._self` as path `['background', '_self']` and
|
|
49
|
-
* `background.active` as path `['background', 'active']`.
|
|
50
|
-
*
|
|
51
|
-
* A companion custom name transform (`carbon/name-kebab`, registered in
|
|
52
|
-
* sd.config.js) strips `_self` from the path when building the token name:
|
|
53
|
-
*
|
|
54
|
-
* ['background', '_self'] → 'background' (parent token)
|
|
55
|
-
* ['background', 'active'] → 'background-active'
|
|
56
|
-
* ['layer', '_self'] → 'layer'
|
|
57
|
-
* ['layer', '01'] → 'layer-01'
|
|
58
|
-
*
|
|
59
|
-
* This reproduces the flat token names that the old dtcg-converter produced,
|
|
60
|
-
* while preserving correct hierarchy for SD's alias resolution and path-based
|
|
61
|
-
* name computation.
|
|
62
|
-
*
|
|
63
|
-
* ── Alias resolution ────────────────────────────────────────────────────────
|
|
64
|
-
*
|
|
65
|
-
* The color-palette.json file is loaded as `source:` (not pre-processed) by
|
|
66
|
-
* sd.config.js, so its tokens stay in their original nested form and SD alias
|
|
67
|
-
* resolution ({blue.60} etc.) continues to work.
|
|
68
|
-
*
|
|
69
|
-
* This preprocessor is called with only the theme JSON as input (passed as
|
|
70
|
-
* `tokens:` in themeConfig()), never seeing the merged palette+theme tree.
|
|
71
|
-
* Therefore palette tokens are unaffected.
|
|
72
|
-
*
|
|
73
|
-
* @param {object} dictionary Raw DTCG token tree (theme file only)
|
|
74
|
-
* @returns {object} Normalised tree with dual-role nodes split
|
|
75
|
-
*/
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Process a single DTCG token node.
|
|
79
|
-
*
|
|
80
|
-
* The node may be:
|
|
81
|
-
* - A pure leaf token (has `$value`, no non-`$` children) → returned as-is.
|
|
82
|
-
* - A pure group (no `$value`, has non-`$` children) → children are recursed.
|
|
83
|
-
* - A dual-role node (has BOTH `$value` AND non-`$` children) → split: the
|
|
84
|
-
* `$value`/`$type`/`$extensions` are moved to a `_self` synthetic leaf, and
|
|
85
|
-
* the node becomes a pure group whose children include `_self` plus the
|
|
86
|
-
* original children (each recursively processed).
|
|
87
|
-
*
|
|
88
|
-
* @param {object} node A single DTCG node object.
|
|
89
|
-
* @returns {object} The normalised node.
|
|
90
|
-
*/
|
|
91
|
-
function processNode(node) {
|
|
92
|
-
if (!node || typeof node !== 'object') return node;
|
|
93
|
-
|
|
94
|
-
const hasValue = '$value' in node;
|
|
95
|
-
const nonDollarChildren = Object.keys(node).filter((k) => !k.startsWith('$'));
|
|
96
|
-
const hasChildren = nonDollarChildren.length > 0;
|
|
97
|
-
|
|
98
|
-
if (hasValue && hasChildren) {
|
|
99
|
-
// ── Dual-role node ────────────────────────────────────────────────────────
|
|
100
|
-
// Collect the DTCG metadata into a `_self` leaf.
|
|
101
|
-
const selfLeaf = {};
|
|
102
|
-
for (const [k, v] of Object.entries(node)) {
|
|
103
|
-
if (k.startsWith('$')) selfLeaf[k] = v;
|
|
104
|
-
}
|
|
105
|
-
// Build the pure-group: _self first, then recursively-processed children.
|
|
106
|
-
const group = { _self: selfLeaf };
|
|
107
|
-
for (const childKey of nonDollarChildren) {
|
|
108
|
-
group[childKey] = processNode(node[childKey]);
|
|
109
|
-
}
|
|
110
|
-
return group;
|
|
111
|
-
} else if (hasChildren) {
|
|
112
|
-
// ── Pure group ────────────────────────────────────────────────────────────
|
|
113
|
-
const out = {};
|
|
114
|
-
for (const [k, v] of Object.entries(node)) {
|
|
115
|
-
if (k.startsWith('$')) {
|
|
116
|
-
out[k] = v;
|
|
117
|
-
} else {
|
|
118
|
-
out[k] = processNode(v);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
return out;
|
|
122
|
-
} else {
|
|
123
|
-
// ── Pure leaf token ───────────────────────────────────────────────────────
|
|
124
|
-
return node;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Top-level preprocessor function.
|
|
130
|
-
*
|
|
131
|
-
* The top-level dictionary itself is always a group (never a token), so we
|
|
132
|
-
* iterate its entries and call processNode() on each non-`$` child.
|
|
133
|
-
*
|
|
134
|
-
* @param {object} dictionary Raw DTCG token tree from the theme JSON file
|
|
135
|
-
* @returns {object} Normalised tree with all dual-role nodes split
|
|
136
|
-
*/
|
|
137
|
-
function carbonDualRolePreprocessor(dictionary) {
|
|
138
|
-
if (!dictionary || typeof dictionary !== 'object') return dictionary;
|
|
139
|
-
|
|
140
|
-
const out = {};
|
|
141
|
-
for (const [key, value] of Object.entries(dictionary)) {
|
|
142
|
-
if (key.startsWith('$')) {
|
|
143
|
-
// Top-level DTCG metadata — preserve as-is.
|
|
144
|
-
out[key] = value;
|
|
145
|
-
} else if (value && typeof value === 'object') {
|
|
146
|
-
out[key] = processNode(value);
|
|
147
|
-
} else {
|
|
148
|
-
out[key] = value;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
return out;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
module.exports = {
|
|
155
|
-
name: 'carbon/dual-role',
|
|
156
|
-
preprocessor: carbonDualRolePreprocessor,
|
|
157
|
-
};
|
|
@@ -1,46 +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
|
-
* Extracts the `$extensions["org.carbon"]["color-scheme"]` value that lives
|
|
13
|
-
* at the top level of the unified themes.json and
|
|
14
|
-
* injects it as a synthetic `color-scheme` token, so the SCSS format can
|
|
15
|
-
* include it in the theme Sass map alongside the regular color tokens.
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* @param {object} dictionary Style Dictionary raw token tree
|
|
19
|
-
* @returns {object}
|
|
20
|
-
*/
|
|
21
|
-
function carbonThemeMetadataPreprocessor(dictionary) {
|
|
22
|
-
const colorScheme = dictionary.$extensions?.['org.carbon']?.['color-scheme'];
|
|
23
|
-
|
|
24
|
-
if (!colorScheme) {
|
|
25
|
-
return dictionary;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Inject as color.scheme so the flattened token name becomes 'color-scheme',
|
|
29
|
-
// matching what dtcg-converter.js produces today.
|
|
30
|
-
return {
|
|
31
|
-
...dictionary,
|
|
32
|
-
color: {
|
|
33
|
-
...(dictionary.color ?? {}),
|
|
34
|
-
scheme: {
|
|
35
|
-
$type: 'other',
|
|
36
|
-
$value: colorScheme,
|
|
37
|
-
$description: 'Color scheme of this theme (light or dark)',
|
|
38
|
-
},
|
|
39
|
-
},
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
module.exports = {
|
|
44
|
-
name: 'carbon/theme-metadata',
|
|
45
|
-
preprocessor: carbonThemeMetadataPreprocessor,
|
|
46
|
-
};
|