feat(tokens): add html preview based on design-tokens (#29545)

Issue number: internal

---------

<!-- 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 new behavior?
<!-- Please describe the behavior or changes that are being added by
this PR. -->

- Added new files to scripts/tokens for the html template and css to
style the generated preview
- Added Style Dictionary register for generating html elements for each
main token

## 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.
-->
This commit is contained in:
Bernardo Cardoso
2024-05-28 15:57:30 +01:00
committed by GitHub
parent b4ce7129b2
commit 3b93bb4a9b
6 changed files with 2004 additions and 2 deletions

View File

@@ -15,6 +15,7 @@ ignoreFiles:
- src/themes/functions.string.scss
- src/themes/native.theme.default.scss
- src/css/themes/*.scss
- scripts/tokens/*.css
indentation: 2

View File

@@ -77,7 +77,7 @@
"build.css": "npm run css.sass && npm run css.minify",
"build.debug": "npm run clean && stencil build --debug",
"build.docs.json": "stencil build --docs-json dist/docs.json",
"build.tokens": "node ./scripts/tokens/index.js && npm run lint.sass.fix && npm run prettier.tokens",
"build.tokens": "node ./scripts/tokens/index.js && npm run lint.fix && npm run prettier.tokens",
"clean": "node scripts/clean.js",
"css.minify": "cleancss -O2 -o ./css/ionic.bundle.css ./css/ionic.bundle.css",
"css.sass": "sass --embed-sources --style compressed src/css:./css",
@@ -90,7 +90,7 @@
"lint.ts.fix": "npm run eslint -- --fix",
"prerender.e2e": "node scripts/testing/prerender.js",
"prettier": "prettier \"./src/**/*.{html,ts,tsx,js,jsx,scss}\"",
"prettier.tokens": "prettier \"./src/foundations/*.scss\" --write --cache",
"prettier.tokens": "prettier \"./src/foundations/*.{scss, html}\" --write --cache",
"start": "npm run build.css && stencil build --dev --watch --serve",
"test": "npm run test.spec && npm run test.e2e",
"test.spec": "stencil test --spec --max-workers=2",

View File

@@ -5,6 +5,8 @@
// - 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.
const fs = require('fs');
const path = require('path');
const StyleDictionary = require('style-dictionary');
const targetPath = './src/foundations/';
@@ -138,6 +140,137 @@ StyleDictionary.registerFormat({
},
});
// Register the custom format to generate HTML
// Load the HTML template
const template = fs.readFileSync(path.join(__dirname, 'preview.template.html'), 'utf8');
StyleDictionary.registerFormat({
name: 'html/tokens',
formatter: function ({ dictionary }) {
// Function to extract numerical value from token name
const extractValue = (tokenName) => {
const match = tokenName.match(/-([0-9]+)/);
return match ? parseInt(match[1], 10) : Number.MAX_SAFE_INTEGER;
};
let colorTokens = `
<table>
<thead>
<tr>
<th>Color</th>
<th>Hex</th>
<th>Token Name</th>
</tr>
</thead>
<tbody>
`;
let fontSizeTokens = '';
let boxShadowTokens = '';
let borderSizeTokens = '';
let borderRadiusTokens = '';
let borderStyleTokens = '';
let fontWeightTokens = '';
let letterSpacingTokens = '';
let spaceTokens = '';
// Collect border-radius and space tokens for separate sorting
let borderRadiusTokenList = [];
let spaceTokenList = [];
dictionary.allProperties.forEach((token) => {
if (token.attributes.category === 'color') {
colorTokens += `
<tr>
<td><div class="color-swatch" style="background-color: ${token.value};"></div></td>
<td>${token.value}</td>
<td>${token.name}</td>
</tr>
`;
} else if (token.attributes.category === 'font-size') {
fontSizeTokens += `
<div class="font-size-token" style="font-size: ${token.value};">
${token.name} (${token.value})
</div>
`;
} else if (token.attributes.category.startsWith('Elevation')) {
const cssShadow = token.value.map(generateShadowValue).join(', ');
boxShadowTokens += `
<div class="shadow-token" style="box-shadow: ${cssShadow};">
${token.name}
</div>
`;
} else if (token.attributes.category === 'border-size' || token.attributes.category === 'border-width') {
borderSizeTokens += `
<div class="border-token" style="border-width: ${token.value};">
${token.name} (${token.value})
</div>
`;
} else if (token.attributes.category === 'border-radius') {
borderRadiusTokenList.push(token); // Collect border-radius tokens
} else if (token.attributes.category === 'border-style') {
borderStyleTokens += `
<div class="border-token" style="border: 1px ${token.value} #000;">
${token.name} (${token.value})
</div>
`;
} else if (token.attributes.category === 'font-weight') {
fontWeightTokens += `
<div class="weight-token" style="font-weight: ${token.value};">
${token.name} (${token.value})
</div>
`;
} else if (token.attributes.category === 'letter-spacing') {
// Convert % to px
const letterSpacingValue = token.value.replace('%', '') + 'px';
letterSpacingTokens += `
<div class="letter-spacing-token" style="letter-spacing: ${letterSpacingValue};">
${token.name} (${letterSpacingValue})
</div>
`;
} else if (token.attributes.category === 'space') {
spaceTokenList.push(token); // Collect space tokens
}
});
// Sort border-radius and space tokens
borderRadiusTokenList.sort((a, b) => extractValue(a.name) - extractValue(b.name));
spaceTokenList.sort((a, b) => extractValue(a.name) - extractValue(b.name));
// Generate HTML for sorted border-radius tokens
borderRadiusTokenList.forEach((token) => {
borderRadiusTokens += `
<div class="border-token" style="border-radius: ${token.value};">
${token.name} (${token.value})
</div>
`;
});
// Generate HTML for sorted space tokens
spaceTokenList.forEach((token) => {
spaceTokens += `
<div class="spacing-wrapper">
<div class="space-token" style="margin: ${token.value};">
${token.name} (${token.value})
</div>
</div>
`;
});
colorTokens += '</tbody></table>';
return template
.replace('{{colorTokens}}', colorTokens)
.replace('{{fontSizeTokens}}', fontSizeTokens)
.replace('{{boxShadowTokens}}', boxShadowTokens)
.replace('{{borderSizeTokens}}', borderSizeTokens)
.replace('{{borderRadiusTokens}}', borderRadiusTokens)
.replace('{{borderStyleTokens}}', borderStyleTokens)
.replace('{{fontWeightTokens}}', fontWeightTokens)
.replace('{{letterSpacingTokens}}', letterSpacingTokens)
.replace('{{spaceTokens}}', spaceTokens);
},
});
// Custom transform to ensure unique token names
StyleDictionary.registerTransform({
name: 'name/cti/kebab-unique',
@@ -210,6 +343,16 @@ StyleDictionary.extend({
},
],
},
html: {
transformGroup: 'custom',
buildPath: targetPath,
files: [
{
destination: 'tokens.preview.html',
format: 'html/tokens',
},
],
},
},
fileHeader: {
myFileHeader: () => {

View File

@@ -0,0 +1,63 @@
table {
width: 100%;
border-collapse: collapse;
margin-bottom: 20px;
}
th,
td {
padding: 10px;
border: 1px solid #ccc;
text-align: left;
}
th {
background-color: #f4f4f4;
}
thead th {
position: sticky;
top: 0;
background-color: #f4f4f4;
z-index: 1;
}
.color-swatch {
width: 50px;
height: 50px;
}
.font-size-token,
.weight-token,
.letter-spacing-token {
margin: 10px 0;
}
.border-token,
.shadow-token {
margin: 10px;
padding: 10px;
}
.border-token {
border: 1px solid #000;
}
.spacing-wrapper {
background-color: lightblue;
}
.spacing-wrapper > div {
background-color: #fff;
}
.token-wrapper:has(.spacing-wrapper) {
display: flex;
flex-direction: column;
gap: 20px;
}
hr {
background-color: #ccc;
margin: 20px 0;
}

View File

@@ -0,0 +1,52 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<title>Design Tokens</title>
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
<link rel="stylesheet" href="../../scripts/tokens/preview.styles.css" />
</head>
<body>
<ion-app>
<ion-header>
<ion-toolbar>
<ion-title>Design Tokens - Preview</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding-horizontal">
<h1>Color Tokens</h1>
<div class="token-wrapper">{{colorTokens}}</div>
<hr />
<h1>Font Size Tokens</h1>
<div class="token-wrapper">{{fontSizeTokens}}</div>
<hr />
<h1>Font Weight Tokens</h1>
<div class="token-wrapper">{{fontWeightTokens}}</div>
<hr />
<h1>Letter Spacing Tokens</h1>
<div class="token-wrapper">{{letterSpacingTokens}}</div>
<hr />
<h1>Box Shadow Tokens</h1>
<div class="token-wrapper">{{boxShadowTokens}}</div>
<hr />
<h1>Border Size Tokens</h1>
<div class="token-wrapper">{{borderSizeTokens}}</div>
<hr />
<h1>Border Radius Tokens</h1>
<div class="token-wrapper">{{borderRadiusTokens}}</div>
<hr />
<h1>Border Style Tokens</h1>
<div class="token-wrapper">{{borderStyleTokens}}</div>
<hr />
<h1>Space Tokens</h1>
<div class="token-wrapper">{{spaceTokens}}</div>
</ion-content>
</ion-app>
</body>
</html>

View File

File diff suppressed because it is too large Load Diff