Skip to content

Commit

Permalink
feat: update build-tokens.js to include --source-tokens-only arg (#…
Browse files Browse the repository at this point in the history
…2351)

* feat: update build-tokens.js to include --source-tokens-only arg
* fix: rename paragon-theme.json to theme-urls.json
* fix: downgrade chalk to non-esm
  • Loading branch information
adamstankiewicz authored and PKulkoRaccoonGang committed Aug 1, 2024
1 parent 1f11cae commit cfa640e
Show file tree
Hide file tree
Showing 9 changed files with 67 additions and 28 deletions.
2 changes: 1 addition & 1 deletion build-scss.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const { pathToFileURL } = require('url');
const path = require('path');
const { program, Option } = require('commander');

const paragonThemeOutputFilename = 'paragon-theme.json';
const paragonThemeOutputFilename = 'theme-urls.json';

/**
* Updates `paragonThemeOutput` object with appropriate name and URLs.
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,14 @@
"type-check": "tsc --noEmit && tsc --project www --noEmit",
"type-check:watch": "npm run type-check -- --watch",
"build-types": "tsc --emitDeclarationOnly",
"build-tokens": "node tokens/build-tokens.js --build-dir ./styles/css/",
"build-tokens": "node tokens/build-tokens.js --build-dir ./styles/css",
"replace-variables-usage-with-css": "node tokens/replace-variables.js -p src -t usage",
"replace-variables-definition-with-css": "node tokens/replace-variables.js -p src -t definition"
},
"dependencies": {
"@popperjs/core": "^2.11.4",
"bootstrap": "^4.6.2",
"chalk": "^4.1.2",
"chroma-js": "^2.4.2",
"classnames": "^2.3.1",
"commander": "^9.4.1",
Expand Down
2 changes: 1 addition & 1 deletion styles/css/core/custom-media-breakpoints.css
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* IMPORTANT: This file is the result of assembling design tokens
* Do not edit directly
* Generated on Sat, 03 Jun 2023 13:27:55 GMT
* Generated on Sun, 04 Jun 2023 14:05:19 GMT
*/

@custom-media --min-pgn-size-breakpoint-xs (min-width: 0);
Expand Down
2 changes: 1 addition & 1 deletion styles/css/core/variables.css
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* IMPORTANT: This file is the result of assembling design tokens
* Do not edit directly
* Generated on Sat, 03 Jun 2023 13:27:55 GMT
* Generated on Sun, 04 Jun 2023 14:05:19 GMT
*/

:root {
Expand Down
2 changes: 1 addition & 1 deletion styles/css/themes/light/utility-classes.css
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* IMPORTANT: This file is the result of assembling design tokens
* Do not edit directly
* Generated on Sat, 03 Jun 2023 13:27:55 GMT
* Generated on Sun, 04 Jun 2023 14:05:19 GMT
*/

.bg-accent-a {
Expand Down
2 changes: 1 addition & 1 deletion styles/css/themes/light/variables.css
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* IMPORTANT: This file is the result of assembling design tokens
* Do not edit directly
* Generated on Sat, 03 Jun 2023 13:27:55 GMT
* Generated on Sun, 04 Jun 2023 14:05:19 GMT
*/

:root {
Expand Down
27 changes: 20 additions & 7 deletions tokens/build-tokens.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,14 @@ program
.description('CLI to build design tokens for various platforms (currently only CSS is supported) from Paragon Design Tokens.')
.option('--build-dir <char>', 'A path to directory where to put files with built tokens, must end with a /.', './build/')
.option('--source <char>', 'A path where to look for additional tokens that will get merged with Paragon ones, must be a path to root directory of the token files that contains "root" and "themes" subdirectories.')
.option('--source-tokens-only', 'If provided, only tokens from --source will be included in the output; Paragon tokens will be used for references but not included in the output.')
.parse();

const { buildDir, source: tokensSource } = program.opts();
const {
buildDir,
source: tokensSource,
sourceTokensOnly: hasSourceTokensOnly,
} = program.opts();

const coreConfig = {
include: [path.resolve(__dirname, 'src/core/**/*.json')],
Expand All @@ -19,20 +24,23 @@ const coreConfig = {
css: {
prefix: 'pgn',
transformGroup: 'css',
buildPath: buildDir,
// NOTE: buildPath must end with a slash
buildPath: buildDir.slice(-1) === '/' ? buildDir : `${buildDir}/`,
files: [
{
format: 'css/custom-variables',
destination: 'core/variables.css',
filter: hasSourceTokensOnly ? 'isSource' : undefined,
options: {
outputReferences: true,
outputReferences: !hasSourceTokensOnly,
},
},
{
format: 'css/custom-media-breakpoints',
destination: 'core/custom-media-breakpoints.css',
filter: hasSourceTokensOnly ? 'isSource' : undefined,
options: {
outputReferences: true,
outputReferences: !hasSourceTokensOnly,
},
},
],
Expand All @@ -55,7 +63,10 @@ const getStyleDictionaryConfig = (themeVariant) => ({
},
},
format: {
'css/custom-variables': (args) => createCustomCSSVariables(args, themeVariant),
'css/custom-variables': formatterArgs => createCustomCSSVariables({
formatterArgs,
themeVariant,
}),
},
platforms: {
css: {
Expand All @@ -64,15 +75,17 @@ const getStyleDictionaryConfig = (themeVariant) => ({
{
format: 'css/custom-variables',
destination: `themes/${themeVariant}/variables.css`,
filter: hasSourceTokensOnly ? 'isSource' : undefined,
options: {
outputReferences: true,
outputReferences: !hasSourceTokensOnly,
},
},
{
format: 'css/utility-classes',
destination: `themes/${themeVariant}/utility-classes.css`,
filter: hasSourceTokensOnly ? 'isSource' : undefined,
options: {
outputReferences: true,
outputReferences: !hasSourceTokensOnly,
},
},
],
Expand Down
27 changes: 20 additions & 7 deletions tokens/sass-helpers.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
const path = require('path');
const fs = require('fs');
const chroma = require('chroma-js');
const chalk = require('chalk');

/**
* Javascript version of bootstrap's color-yiq function. Decides whether to return light color variant or dark one
* based on contrast value of the input color
*
* @param {Object} args
* @param {Object} args.tokenName - Name of design token, used to log warnings
* @param {Object} args.color - chroma-js color instance
* @param {Object} args.backgroundColor - chroma-js color instance
* @param {String} args.light - light color variant from ./src/themes/{themeVariant}/global/other.json
* @param {String} args.dark - dark color variant from ./src/themes/{themeVariant}/global/other.json
* @param {Number} args.threshold - contrast threshold from ./src/core/global/other.json
Expand All @@ -18,7 +19,7 @@ const chroma = require('chroma-js');
*/
function colorYiq({
tokenName,
originalColor,
backgroundColor,
light,
dark,
threshold,
Expand All @@ -37,7 +38,7 @@ function colorYiq({
const lightColor = light || defaultLight;
const darkColor = dark || defaultDark;

const [r, g, b] = originalColor.rgb();
const [r, g, b] = backgroundColor.rgb();
const yiq = ((r * 299) + (g * 587) + (b * 114)) * 0.001;

let result = yiq >= contrastThreshold ? chroma(darkColor) : chroma(lightColor);
Expand All @@ -47,12 +48,18 @@ function colorYiq({
// check whether the resulting combination of colors passes a11y contrast ratio of 4:5:1
// if not - darken resulting color until it does until maxAttempts is reached.
let numDarkenAttempts = 1;
while (chroma.contrast(originalColor, result) < 4.5 && numDarkenAttempts <= maxAttempts) {
while (chroma.contrast(backgroundColor, result) < 4.5 && numDarkenAttempts <= maxAttempts) {
result = result.darken(0.1);
numDarkenAttempts += 1;
if (numDarkenAttempts === maxAttempts) {
const title = `[a11y] Warning: Failed to sufficiently darken token ${chalk.keyword('orange').bold(tokenName)} to pass contrast ratio of 4.5:1.`;
const warningMetadata = [
`Background color: ${backgroundColor.hex()}`,
`Attempted foreground color: ${result.hex()}`,
].join('\n ');
const warn = `${title}\n ${warningMetadata}`;
// eslint-disable-next-line no-console
console.warn(`WARNING: Failed to darken ${tokenName} to pass contrast ratio of 4.5:1 (Original: ${originalColor.hex()}; Attempted: ${result.hex()}).`);
console.log(chalk.keyword('yellow').bold(warn));
}
}
return result;
Expand All @@ -61,12 +68,18 @@ function colorYiq({
// check whether the resulting combination of colors passes a11y contrast ratio of 4:5:1
// if not - brighten resulting color until it does until maxAttempts is reached.
let numBrightenAttempts = 1;
while (chroma.contrast(originalColor, result) < 4.5 && numBrightenAttempts <= maxAttempts) {
while (chroma.contrast(backgroundColor, result) < 4.5 && numBrightenAttempts <= maxAttempts) {
result = result.brighten(0.1);
numBrightenAttempts += 1;
if (numBrightenAttempts === maxAttempts) {
const title = `[a11y] Warning: Failed to sufficiently brighten token ${chalk.keyword('orange').bold(tokenName)} to pass contrast ratio of 4.5:1.`;
const warningMetadata = [
`Background color: ${backgroundColor.hex()}`,
`Attempted foreground color: ${result.hex()}`,
].join('\n ');
const warn = `${title}\n ${warningMetadata}`;
// eslint-disable-next-line no-console
console.warn(`WARNING: Failed to brighten ${tokenName} to pass contrast ratio of 4.5:1 (Original: ${originalColor.hex()}; Attempted: ${result.hex()}).`);
console.log(chalk.keyword('yellow').bold(warn));
}
}
return result;
Expand Down
28 changes: 20 additions & 8 deletions tokens/style-dictionary.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const colorTransform = (token, theme) => {
const { light, dark, threshold } = modifier;
color = colorYiq({
tokenName,
originalColor: color,
backgroundColor: color,
light,
dark,
threshold,
Expand Down Expand Up @@ -63,11 +63,14 @@ const colorTransform = (token, theme) => {
* 2. 'theme' to output only theme's variables (e.g, 'light' or 'dark'), if theme is not provided - only
* core tokens are built.
*/
const createCustomCSSVariables = (args, theme) => {
const { dictionary, options, file } = args;

const outputTokens = theme
? dictionary.allTokens.filter(token => token.filePath.includes(theme))
const createCustomCSSVariables = ({
formatterArgs,
themeVariant,
}) => {
const { dictionary, options, file } = formatterArgs;

const outputTokens = themeVariant
? dictionary.allTokens.filter(token => token.filePath.includes(themeVariant))
: dictionary.allTokens;

const variables = outputTokens.sort(sortByReference(dictionary)).map(token => {
Expand Down Expand Up @@ -123,7 +126,7 @@ StyleDictionary.registerTransform({
*/
StyleDictionary.registerFormat({
name: 'css/custom-variables',
formatter: (args) => createCustomCSSVariables(args),
formatter: formatterArgs => createCustomCSSVariables({ formatterArgs }),
});

/**
Expand Down Expand Up @@ -178,7 +181,7 @@ StyleDictionary.registerFormat({
const { size: { breakpoint } } = dictionary.properties;

let customMediaVariables = '';
const breakpoints = Object.values(breakpoint);
const breakpoints = Object.values(breakpoint || {});

for (let i = 0; i < breakpoints.length; i++) {
const [currentBreakpoint, nextBreakpoint] = [breakpoints[i], breakpoints[i + 1]];
Expand All @@ -203,6 +206,15 @@ StyleDictionary.registerFileHeader({
],
});

/**
* Registers a filter `isSource` that filters output to only include tokens
* that are marked as `isSource` in their metadata.
*/
StyleDictionary.registerFilter({
name: 'isSource',
matcher: token => token?.isSource === true,
});

module.exports = {
StyleDictionary,
createCustomCSSVariables,
Expand Down

0 comments on commit cfa640e

Please sign in to comment.