mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
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:
@@ -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
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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: () => {
|
||||
|
||||
63
core/scripts/tokens/preview.styles.css
Normal file
63
core/scripts/tokens/preview.styles.css
Normal 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;
|
||||
}
|
||||
52
core/scripts/tokens/preview.template.html
Normal file
52
core/scripts/tokens/preview.template.html
Normal 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>
|
||||
1743
core/src/foundations/tokens.preview.html
Normal file
1743
core/src/foundations/tokens.preview.html
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user