Skip to content

Commit b2588f8

Browse files
authored
feat: support themes in community design tokens package (#609)
# Contents Builds .css files for (at the moment) `bwbapp` and `expertuser` themes. ## Checklist - [x] ~~New features/components and bugfixes are covered by tests~~ - [x] Changesets are created
1 parent f1099d9 commit b2588f8

File tree

6 files changed

+115
-652
lines changed

6 files changed

+115
-652
lines changed

.changeset/long-toys-cheer.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@lux-design-system/lux-community-design-tokens": major
3+
---
4+
5+
Now exports themes in css and scss formats

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,11 @@ package-lock.json
6262
# Ignore generated files from Stencil
6363
components.d.ts
6464

65-
# Do not ignore our build scripts
65+
# Do not ignore our build scripts
6666
# (This leaves other build/ folders ignored, see line #9)
6767
!/proprietary/design-tokens/build
6868
!/proprietary/icons/build
69+
70+
# Ignore most generated json files for lux-community-design-tokens
71+
/proprietary/lux-community-design-tokens/merged
72+
!/proprietary/lux-community-design-tokens/merged/figma.tokens.json

proprietary/lux-community-design-tokens/build.mjs

Lines changed: 66 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
import { register } from '@tokens-studio/sd-transforms';
2+
import { existsSync, mkdirSync } from 'fs';
3+
import { readFile, writeFile } from 'node:fs/promises';
4+
import { posix } from 'path';
25
import StyleDictionary from 'style-dictionary';
36

7+
// Will take the theme name and remove all spaces and make it lowercase
8+
const normalizeThemeName = (name) => {
9+
return name.toLowerCase().replace(/\s+/g, '');
10+
};
11+
412
// Custom header to add generation date
513
StyleDictionary.registerFileHeader({
614
name: 'nlds-rhc-header',
@@ -12,31 +20,29 @@ StyleDictionary.registerFileHeader({
1220
register(StyleDictionary, { excludeParentKeys: true });
1321

1422
// Get the platforms config
15-
const getPlatformsConfig = (buildPath) => {
16-
return {
17-
web: {
18-
transformGroup: 'tokens-studio',
19-
transforms: ['name/kebab'],
20-
buildPath,
21-
options: {
22-
fileHeader: 'nlds-rhc-header',
23-
outputReferences: true,
24-
},
25-
files: [
26-
{
27-
destination: 'root.css',
28-
format: 'css/variables',
29-
},
30-
{
31-
destination: '_variables.scss',
32-
format: 'scss/variables',
33-
},
34-
],
23+
const getPlatformsConfig = (buildPath) => ({
24+
web: {
25+
transformGroup: 'tokens-studio',
26+
transforms: ['name/kebab'],
27+
buildPath,
28+
options: {
29+
fileHeader: 'nlds-rhc-header',
30+
outputReferences: true,
3531
},
36-
};
37-
};
32+
files: [
33+
{
34+
destination: 'variables.css',
35+
format: 'css/variables',
36+
},
37+
{
38+
destination: 'variables.scss',
39+
format: 'scss/variables',
40+
},
41+
],
42+
},
43+
});
3844

39-
// This will build the tokens
45+
// This will build the base tokens
4046
async function buildBaseTokens() {
4147
const config = getPlatformsConfig('dist/');
4248
const StyleDictionaryBase = new StyleDictionary({
@@ -53,9 +59,46 @@ async function buildBaseTokens() {
5359
await StyleDictionaryBase.buildAllPlatforms();
5460
}
5561

62+
// This will build the themes
63+
async function buildThemes() {
64+
const themesJson = await readFile('./merged/themes.json', 'utf-8');
65+
const themes = JSON.parse(themesJson);
66+
67+
// Process each theme separately
68+
for (const [theme, themeData] of Object.entries(themes)) {
69+
const themeName = normalizeThemeName(theme);
70+
const themesDir = `./merged/${themeName}`;
71+
72+
// Create the theme directory if it doesn't exist
73+
if (!existsSync(themesDir)) {
74+
mkdirSync(themesDir, { recursive: true });
75+
}
76+
77+
// Write individual theme tokens
78+
await writeFile(posix.join(themesDir, `tokens.json`), JSON.stringify(themeData.tokens, null, 2));
79+
80+
const config = getPlatformsConfig(`dist/${themeName}/`, themeName);
81+
// Create a separate Style Dictionary instance for each theme
82+
const StyleDictionaryTheme = new StyleDictionary({
83+
log: { verbosity: 'verbose' },
84+
source: [`./merged/${themeName}/tokens.json`],
85+
preprocessors: ['tokens-studio'],
86+
platforms: {
87+
...config,
88+
},
89+
});
90+
await StyleDictionaryTheme.hasInitialized;
91+
92+
// Build this specific theme
93+
await StyleDictionaryTheme.cleanAllPlatforms();
94+
await StyleDictionaryTheme.buildAllPlatforms();
95+
}
96+
}
97+
5698
async function build() {
5799
try {
58100
await buildBaseTokens();
101+
await buildThemes();
59102
} catch (error) {
60103
console.error(error);
61104
}

proprietary/lux-community-design-tokens/merge-token-sets.mjs

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@ export const mergeFigmaTokenFiles = async () => {
3636
// Write the merged tokens to a file
3737
await writeJsonFile(outputPath, mergedTokens);
3838

39-
console.log(`Merged Figma tokens generated successfully at ${outputPath}.`);
39+
console.log(`Merged Figma tokens generated ${collisions.length === 0 ? 'successfully ' : ''}at ${outputPath}.`);
40+
41+
processThemes(outputPath);
4042

4143
if (collisions.length > 0) {
4244
console.error('Token set collisions found, exiting with error');
@@ -56,12 +58,43 @@ export const mergeTokenSets = (original, addition) => {
5658
else if (key !== '$themes') collisions.push(key);
5759
}
5860

59-
// Concatenate $themes array of both
60-
merged.$themes = merged.$themes.concat(addition.$themes);
61+
// No need to re-publish the themes available in RHC here
62+
merged.$themes = addition.$themes;
6163

6264
return [merged, collisions];
6365
};
6466

67+
// Process themes to add the enabled components
68+
const processThemes = async (file) => {
69+
// Read the raw JSON file directly
70+
const json = await fs.readFile(file, 'utf-8');
71+
const tokens = JSON.parse(json);
72+
73+
const processedThemes = {};
74+
75+
(tokens.$themes || []).forEach((theme) => {
76+
const themeTokens = {};
77+
78+
Object.entries(theme.selectedTokenSets).forEach(([tokenSet, status]) => {
79+
if (status === 'enabled' && !tokenSet.endsWith('[figma-only]')) {
80+
if (tokens[tokenSet]) {
81+
themeTokens[tokenSet] = tokens[tokenSet];
82+
}
83+
}
84+
});
85+
// Add default type scale here, because Figma does not understand `clamp(...)`
86+
themeTokens['overrides/type-scale/default [code-only]'] = tokens['overrides/type-scale/default [code-only]'];
87+
88+
processedThemes[theme.name] = {
89+
id: theme.id,
90+
tokens: themeTokens,
91+
group: theme.group,
92+
};
93+
});
94+
95+
writeJsonFile(path.resolve(__dirname, './merged/themes.json'), processedThemes);
96+
};
97+
6598
// Run the generator if this script is executed directly
6699
if (import.meta.url === `file://${process.argv[1]}`) {
67100
mergeFigmaTokenFiles().catch(console.error);

proprietary/lux-community-design-tokens/merge-token-sets.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,13 @@ describe('mergeTokenSets', () => {
2222
expect(collisions).toStrictEqual(['a']);
2323
});
2424

25-
it('should merge themes by concatenation', () => {
25+
it('should not merge themes, but only use the local ones', () => {
2626
const orig = { $themes: [{ id: 'some' }] };
2727
const add = { $themes: [{ id: 'stuff' }] };
2828

2929
const [merged] = mergeTokenSets(orig, add);
3030

31-
expect(merged.$themes).toStrictEqual([{ id: 'some' }, { id: 'stuff' }]);
31+
expect(merged.$themes).toStrictEqual([{ id: 'stuff' }]);
3232
});
3333

3434
it('should not suffer from shallow copy residue', () => {

0 commit comments

Comments
 (0)