Files
Sean Perkins 2414b78bef feat(tokens): generate rgb variants of colors (#29331)
Issue number: N/A

---------

<!-- Please do not submit updates to dependencies unless it fixes an
issue. -->

<!-- Please try to limit your pull request to one type (bugfix, feature,
etc). Submit multiple pull requests if needed. -->

## What is the current behavior?
<!-- Please describe the current behavior that you are modifying. -->

The generated design tokens only generate HEX output of colors. This
prevents us from using the color tokens when we need to manipulate the
alpha channel of the color.

## What is the new behavior?
<!-- Please describe the behavior or changes that are being added by
this PR. -->

- Generate a rgb color token and sass variable for each existing color
token.
- These "rgb" formatted variables are an existing practice to Ionic
Framework. Developers are expected to pass the variable into an `rgb` or
`rgba` function.

## Does this introduce a breaking change?

- [ ] Yes
- [x] No

<!--
  If this introduces a breaking change:
1. Describe the impact and migration path for existing applications
below.
  2. Update the BREAKING.md file with the breaking change.
3. Add "BREAKING CHANGE: [...]" to the commit description when merging.
See
https://github.com/ionic-team/ionic-framework/blob/main/docs/CONTRIBUTING.md#footer
for more information.
-->


## Other information

<!-- Any other information that is important to this PR such as
screenshots of how the component looks before and after the change. -->
2024-04-12 09:45:02 -04:00

194 lines
6.2 KiB
JavaScript

/* For generating Design Tokens, we use Style Dictionary for several reasons:
- It's prepared to easily generate tokens for multiple types of outputs (CSS, SCSS, iOS, Android, documentation, etc.).
- It also works very well out of the box with any kind of Design Tokens formats, like Figma Tokens, as well as APIs to adjust to more custom ones.
- It is probably the most well-known and widely used Design Tokens tool. It has also been regularly maintained for a long time.
- It can easily scale to different necessities we might have in the future.
*/
// eslint-disable-next-line @typescript-eslint/no-var-requires
const StyleDictionary = require('style-dictionary');
const { fileHeader } = StyleDictionary.formatHelpers;
// Empty for as an example of how we can add some extra variables, not from the tokens JSON
const customVariables = ``;
// Prefix for all generated variables
const variablesPrefix = 'ionic';
function hexToRgb(hex) {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result
? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16),
}
: null;
}
// CSS vanilla :root format
StyleDictionary.registerFormat({
name: 'rootFormat',
formatter({ dictionary, file }) {
// Add a prefix to all variable names
const prefixedVariables = dictionary.allProperties.map((prop) => {
const rgb = hexToRgb(prop.value);
return ` --${variablesPrefix}-${prop.name}: ${prop.value};${
rgb ? `\n --${variablesPrefix}-${prop.name}-rgb: ${rgb.r}, ${rgb.g}, ${rgb.b};` : ``
}`;
});
return (
fileHeader({ file }) +
':root {\n' +
prefixedVariables.join('\n') + // Join all prefixed variables with a newline
customVariables +
'\n}\n'
);
},
});
// scss variables format
StyleDictionary.registerFormat({
name: 'scssVariablesFormat',
formatter({ dictionary, file }) {
// Add a prefix to all variable names
const prefixedVariables = dictionary.allProperties.map((prop) => {
const rgb = hexToRgb(prop.value);
return `$${variablesPrefix}-${prop.name}: var(--${variablesPrefix}-${prop.name}, ${prop.value});${
rgb
? `\n$${variablesPrefix}-${prop.name}-rgb: var(--${variablesPrefix}-${prop.name}-rgb, ${rgb.r}, ${rgb.g}, ${rgb.b});`
: ``
}`;
});
return (
fileHeader({ file }) +
prefixedVariables.join('\n') + // Join all prefixed variables with a newline
customVariables +
'\n'
);
},
});
// Create utility-classes
StyleDictionary.registerFormat({
name: 'cssUtilityClassesFormat',
formatter({ dictionary, file }) {
const utilityClasses = dictionary.allProperties.map((prop) => {
let tokenType = prop.attributes.category;
const className = `${prop.name}`;
let utilityClass = '';
switch (tokenType) {
case 'color':
utilityClass = `.${variablesPrefix}-${className} {\n color: $ionic-${prop.name};\n}
.${variablesPrefix}-background-${className} {\n background-color: $ionic-${prop.name};\n}`;
break;
case 'border':
const borderAttribute = prop.attributes.type === 'radius' ? 'border-radius' : 'border-width';
utilityClass = `.${variablesPrefix}-${className} {\n ${borderAttribute}: $ionic-${prop.name};\n}`;
break;
case 'font':
let fontAttribute;
switch (prop.attributes.type) {
case 'size':
fontAttribute = 'font-size';
break;
case 'weight':
fontAttribute = 'font-weight';
break;
case 'line-height':
fontAttribute = 'line-height';
break;
case 'letter-spacing':
fontAttribute = 'letter-spacing';
break;
case 'family':
return;
}
utilityClass = `.${variablesPrefix}-${className} {\n ${fontAttribute}: $ionic-${prop.name};\n}`;
break;
case 'elevation':
utilityClass = `.${variablesPrefix}-${className} {\n box-shadow: $ionic-${prop.name};\n}`;
break;
case 'space':
utilityClass = `.${variablesPrefix}-margin-${className} {\n margin: $ionic-${prop.name};\n};
.${variablesPrefix}-padding-${className} {\n padding: $ionic-${prop.name};\n}`;
break;
default:
utilityClass = `.${variablesPrefix}-${className} {\n ${tokenType}: $ionic-${prop.name};\n}`;
}
return utilityClass;
});
return [fileHeader({ file }), '@import "./ionic.vars";\n', utilityClasses.join('\n')].join('\n');
},
});
// Make Style Dictionary comply with the $ format on properties from W3C Guidelines
const w3cTokenJsonParser = {
pattern: /\.json|\.tokens\.json|\.tokens$/,
parse({ contents }) {
// replace $value with value so that style dictionary recognizes it
var preparedContent = (contents || '{}')
.replace(/"\$?value":/g, '"value":')
// convert $description to comment
.replace(/"\$?description":/g, '"comment":');
//
return JSON.parse(preparedContent);
},
};
StyleDictionary.registerParser(w3cTokenJsonParser);
// Generate Tokens
StyleDictionary.extend({
source: ['./src/foundations/*.json'],
platforms: {
css: {
buildPath: './src/foundations/',
transformGroup: 'css',
files: [
{
destination: 'ionic.root.scss',
format: 'rootFormat',
options: {
outputReferences: true,
fileHeader: `myFileHeader`,
},
},
],
},
scss: {
buildPath: './src/foundations/',
transformGroup: 'scss',
files: [
{
destination: 'ionic.vars.scss',
format: 'scssVariablesFormat',
options: {
outputReferences: true,
fileHeader: `myFileHeader`,
},
},
{
destination: 'ionic.utility.scss',
format: 'cssUtilityClassesFormat',
options: {
outputReferences: true,
fileHeader: `myFileHeader`,
},
},
],
},
},
fileHeader: {
myFileHeader: () => {
return [`This is an auto-generated file, please do not change it directly.`, `Ionic Design System`];
},
},
}).buildAllPlatforms();