Skip to content

Commit

Permalink
Merge branch 'next' into feat/relative-sizing
Browse files Browse the repository at this point in the history
  • Loading branch information
eirikbacker authored Oct 24, 2024
2 parents 4dcf746 + 7494557 commit 2f4b48c
Show file tree
Hide file tree
Showing 11 changed files with 359 additions and 304 deletions.
2 changes: 1 addition & 1 deletion packages/cli/bin/designsystemet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ function makeTokenCommands() {
const out = typeof opts.out === 'string' ? opts.out : './dist/tokens';
const preview = opts.preview;
const verbose = opts.verbose;
console.log(`Bulding tokens in ${chalk.green(tokens)}`);
console.log(`Building tokens in ${chalk.green(tokens)}`);
return buildTokens({ tokens, out, preview, verbose });
});

Expand Down
167 changes: 71 additions & 96 deletions packages/cli/src/tokens/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,125 +6,100 @@ import chalk from 'chalk';
import * as R from 'ramda';
import StyleDictionary from 'style-dictionary';

import * as configs from './build/configs.js';
import { configs, getConfigsForThemeDimensions } from './build/configs.js';
import type { BuildConfig, ThemePermutation } from './build/types.js';
import { makeEntryFile } from './build/utils/entryfile.js';

const { permutateThemes, getConfigs } = configs;
import { processThemeObject } from './build/utils/getMultidimensionalThemes.js';

type Options = {
/** Design tokens path */
/** Design tokens path */
tokens: string;
/** Output directoru for built tokens */
/** Output directory for built tokens */
out: string;
/** Generate preview tokens */
preview: boolean;
/** Enable verbose output */
verbose: boolean;
};

// type FormattedCSSPlatform = { css: { output: string; destination: string }[] };
export let buildOptions: Options | undefined;

const sd = new StyleDictionary();

/*
* Declarative configuration of the build output
*/
const buildConfigs = {
typography: { getConfig: configs.typographyVariables, dimensions: ['typography'] },
'color-mode': { getConfig: configs.colorModeVariables, dimensions: ['mode'] },
semantic: { getConfig: configs.semanticVariables, dimensions: ['semantic'] },
storefront: {
name: 'Storefront preview tokens',
getConfig: configs.typescriptTokens,
dimensions: ['mode'],
options: { outPath: path.resolve('../../apps/storefront/tokens') },
},
entryFiles: {
name: 'CSS entry files',
getConfig: configs.semanticVariables,
dimensions: ['semantic'],
build: async (sdConfigs, { outPath }) => {
await Promise.all(
sdConfigs.map(async ({ permutation: { theme } }) => {
console.log(`👷 ${theme}.css`);

return makeEntryFile({ theme, outPath, buildPath: path.resolve(`${outPath}/${theme}`) });
}),
);
},
},
} satisfies Record<string, BuildConfig>;

export async function buildTokens(options: Options): Promise<void> {
const verbosity = options.verbose ? 'verbose' : 'silent';
buildOptions = options;
const tokensDir = options.tokens;
const storefrontOutDir = path.resolve('../../apps/storefront/tokens');
const outPath = path.resolve(options.out);

/*
* Build the themes
*/
const $themes = JSON.parse(fs.readFileSync(path.resolve(`${tokensDir}/$themes.json`), 'utf-8')) as ThemeObject[];

const relevant$themes = $themes.filter((theme) => {
const group = R.toLower(R.defaultTo('')(theme.group));
if (group === 'size' && theme.name.toLowerCase() !== 'default') return false;

return true;
});

const themes = permutateThemes(relevant$themes);

const typographyThemes = R.filter((val) => val.mode === 'light', themes);
const colormodeThemes = R.filter((val) => val.typography === 'primary', themes);
const semanticThemes = R.filter((val) => val.mode === 'light' && val.typography === 'primary', themes);

const colorModeConfigs = getConfigs(configs.colorModeVariables, outPath, tokensDir, colormodeThemes, verbosity);
const semanticConfigs = getConfigs(configs.semanticVariables, outPath, tokensDir, semanticThemes, verbosity);
const typographyConfigs = getConfigs(configs.typographyVariables, outPath, tokensDir, typographyThemes, verbosity);
const storefrontConfigs = getConfigs(
configs.typescriptTokens,
storefrontOutDir,
tokensDir,
colormodeThemes,
verbosity,
const relevant$themes = $themes
.map(processThemeObject)
// We only use the 'default' theme for the 'size' group
.filter((theme) => R.not(theme.group === 'size' && theme.name !== 'default'));

const buildAndSdConfigs = R.map(
(val: BuildConfig) => ({
buildConfig: val,
sdConfigs: getConfigsForThemeDimensions(val.getConfig, relevant$themes, val.dimensions, {
outPath,
tokensDir,
...val.options,
}),
}),
buildConfigs,
);

try {
if (typographyConfigs.length > 0) {
console.log(`\n🍱 Building ${chalk.green('typography')}`);

await Promise.all(
typographyConfigs.map(async ({ theme, typography, config }) => {
console.log(`👷 ${theme} - ${typography}`);

const typographyClasses = await sd.extend(config);

return typographyClasses.buildAllPlatforms();
}),
);
}

if (colorModeConfigs.length > 0) {
console.log(`\n🍱 Building ${chalk.green('color-mode')}`);

await Promise.all(
colorModeConfigs.map(async ({ theme, mode, config }) => {
console.log(`👷 ${theme} - ${mode}`);

const themeVariablesSD = await sd.extend(config);

return themeVariablesSD.buildAllPlatforms();
}),
);
}

if (semanticConfigs.length > 0) {
console.log(`\n🍱 Building ${chalk.green('semantic')}`);

await Promise.all(
semanticConfigs.map(async ({ theme, config, semantic }) => {
console.log(`👷 ${theme} - ${semantic}`);

const typographyClasses = await sd.extend(config);

return typographyClasses.buildAllPlatforms();
}),
);
}

if (storefrontConfigs.length > 0 && options.preview) {
console.log(`\n🍱 Building ${chalk.green('Storefront preview tokens')}`);

await Promise.all(
storefrontConfigs.map(async ({ theme, mode, config }) => {
console.log(`👷 ${theme} - ${mode}`);

const storefrontSD = await sd.extend(config);

return storefrontSD.buildAllPlatforms();
}),
);
}

if (semanticConfigs.length > 0) {
console.log(`\n🍱 Building ${chalk.green('CSS file')}`);

await Promise.all(
semanticConfigs.map(async ({ theme }) => {
console.log(`👷 ${theme}.css`);

return makeEntryFile({ theme, outPath, buildPath: path.resolve(`${outPath}/${theme}`) });
}),
);
for (const [key, { buildConfig, sdConfigs }] of R.toPairs(buildAndSdConfigs)) {
if (sdConfigs.length > 0) {
console.log(`\n🍱 Building ${chalk.green(buildConfig.name ?? key)}`);

if (buildConfig.build) {
return await buildConfig.build(sdConfigs, { outPath, tokensDir, ...buildConfig.options });
}
await Promise.all(
sdConfigs.map(async ({ config, permutation }) => {
const modes: Array<keyof ThemePermutation> = ['theme', ...buildConfig.dimensions];
const modeMessage = modes.map((x) => permutation[x]).join(' - ');
console.log(modeMessage);

return (await sd.extend(config)).buildAllPlatforms();
}),
);
}
}
} catch (err) {
// Fix crash error message from StyleDictionary from
Expand Down
102 changes: 42 additions & 60 deletions packages/cli/src/tokens/build/configs.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
import { expandTypesMap, register } from '@tokens-studio/sd-transforms';
import type { ThemeObject } from '@tokens-studio/types';
import * as R from 'ramda';
import StyleDictionary from 'style-dictionary';
import type { Config, LogConfig, TransformedToken } from 'style-dictionary/types';
import type { Config as StyleDictionaryConfig, TransformedToken } from 'style-dictionary/types';
import { outputReferencesFilter } from 'style-dictionary/utils';

import { buildOptions } from '../build.js';
import * as formats from './formats/css.js';
import { jsTokens } from './formats/js-tokens.js';
import { nameKebab, resolveMath, sizeRem, typographyName } from './transformers.js';
import { permutateThemes as permutateThemes_ } from './utils/permutateThemes.js';
import type { PermutatedThemes } from './utils/permutateThemes.js';
import type {
GetSdConfigOptions,
IsCalculatedToken,
SDConfigForThemePermutation,
ThemeDimension,
ThemePermutation,
} from './types.js';
import { type ProcessedThemeObject, getMultidimensionalThemes } from './utils/getMultidimensionalThemes.js';
import { pathStartsWithOneOf, typeEquals } from './utils/utils.js';

void register(StyleDictionary, { withSDBuiltins: false });
Expand All @@ -19,7 +25,6 @@ void register(StyleDictionary, { withSDBuiltins: false });
const usesDtcg = true;
export const prefix = 'ds';
export const basePxFontSize = 16;
export const separator = '_';

const fileHeader = () => [`These files are generated from design tokens defind using Token Studio`];

Expand Down Expand Up @@ -48,8 +53,6 @@ const dsTransformers = [

const paritionPrimitives = R.partition(R.test(/(?!.*global\.json).*primitives.*/));

const hasUnknownProps = R.pipe(R.values, R.none(R.equals('unknown')), R.not);

const outputColorReferences = (token: TransformedToken) => {
if (
R.test(/accent|neutral|brand1|brand2|brand3|success|danger|warning/, token.name) &&
Expand All @@ -61,23 +64,14 @@ const outputColorReferences = (token: TransformedToken) => {
return false;
};

export type IsCalculatedToken = (token: TransformedToken, options?: Config) => boolean;

export const permutateThemes = ($themes: ThemeObject[]) =>
permutateThemes_($themes, {
separator,
});

type GetConfig = (options: {
mode?: string;
theme?: string;
semantic?: string;
size?: string;
typography?: string;
outPath?: string;
}) => Config;
export type GetStyleDictionaryConfig = (
permutation: ThemePermutation,
options: {
outPath?: string;
},
) => StyleDictionaryConfig;

export const colorModeVariables: GetConfig = ({ mode = 'light', outPath, theme }) => {
const colorModeVariables: GetStyleDictionaryConfig = ({ mode = 'light', theme }, { outPath }) => {
const selector = `${mode === 'light' ? ':root, ' : ''}[data-ds-color-mode="${mode}"]`;
const layer = `ds.theme.color-mode.${mode}`;

Expand Down Expand Up @@ -112,7 +106,7 @@ export const colorModeVariables: GetConfig = ({ mode = 'light', outPath, theme }
};
};

export const semanticVariables: GetConfig = ({ outPath, theme }) => {
const semanticVariables: GetStyleDictionaryConfig = ({ theme }, { outPath }) => {
const selector = `:root`;
const layer = `ds.theme.semantic`;

Expand Down Expand Up @@ -163,7 +157,7 @@ export const semanticVariables: GetConfig = ({ outPath, theme }) => {
};
};

export const typescriptTokens: GetConfig = ({ mode = 'unknown', outPath, theme }) => {
const typescriptTokens: GetStyleDictionaryConfig = ({ mode, theme }, { outPath }) => {
return {
usesDtcg,
preprocessors: ['tokens-studio'],
Expand Down Expand Up @@ -201,7 +195,7 @@ export const typescriptTokens: GetConfig = ({ mode = 'unknown', outPath, theme }
};
};

export const typographyVariables: GetConfig = ({ outPath, theme, typography }) => {
const typographyVariables: GetStyleDictionaryConfig = ({ theme, typography }, { outPath }) => {
const selector = `${typography === 'primary' ? ':root, ' : ''}[data-ds-typography="${typography}"]`;
const layer = `ds.theme.typography.${typography}`;

Expand Down Expand Up @@ -256,53 +250,41 @@ export const typographyVariables: GetConfig = ({ outPath, theme, typography }) =
};
};

type getConfigs = (
getConfig: GetConfig,
outPath: string,
tokensDir: string,
themes: PermutatedThemes,
logVerbosity: LogConfig['verbosity'],
) => { mode: string; theme: string; semantic: string; size: string; typography: string; config: Config }[];

export const getConfigs: getConfigs = (getConfig, outPath, tokensDir, permutatedThemes, logVerbosity) =>
permutatedThemes
.map((permutatedTheme) => {
const {
selectedTokenSets = [],
mode = 'unknown',
theme = 'unknown',
semantic = 'unknown',
size = 'unknown',
typography = 'unknown',
} = permutatedTheme;

if (hasUnknownProps(permutatedTheme)) {
throw Error(`Theme ${permutatedTheme.name} has unknown props: ${JSON.stringify(permutatedTheme)}`);
}
export const configs = {
colorModeVariables,
typographyVariables,
semanticVariables,
typescriptTokens,
};

export const getConfigsForThemeDimensions = (
getConfig: GetStyleDictionaryConfig,
themes: ProcessedThemeObject[],
dimensions: ThemeDimension[],
options: GetSdConfigOptions,
): SDConfigForThemePermutation[] => {
const { outPath, tokensDir } = options;

const permutations = getMultidimensionalThemes(themes, dimensions);
return permutations
.map(({ selectedTokenSets, permutation }) => {
const setsWithPaths = selectedTokenSets.map((x) => `${tokensDir}/${x}.json`);

const [source, include] = paritionPrimitives(setsWithPaths);

const config_ = getConfig({
outPath,
theme,
mode,
semantic,
size,
typography,
});
const config_ = getConfig(permutation, { outPath });

const config: Config = {
const config: StyleDictionaryConfig = {
...config_,
log: {
...config_?.log,
verbosity: logVerbosity,
verbosity: buildOptions?.verbose ? 'verbose' : 'silent',
},
source,
include,
};

return { mode, theme, semantic, size, typography, config };
return { permutation, config };
})
.sort();
};
7 changes: 3 additions & 4 deletions packages/cli/src/tokens/build/formats/css.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import * as R from 'ramda';
import type { TransformedToken } from 'style-dictionary';
import type { Format } from 'style-dictionary/types';
import { createPropertyFormatter, fileHeader, getReferences, usesReferences } from 'style-dictionary/utils';
import { createPropertyFormatter, fileHeader, usesReferences } from 'style-dictionary/utils';

import type { IsCalculatedToken } from '../configs.js';
import { prefix } from '../configs.js';
import { getValue, typeEquals } from '../utils/utils.js';
import type { IsCalculatedToken } from '../types.js';
import { getValue } from '../utils/utils.js';

const prefersColorScheme = (mode: string, content: string) => `
@media (prefers-color-scheme: ${mode}) {
Expand Down
Loading

0 comments on commit 2f4b48c

Please sign in to comment.