Skip to content

Commit 798165d

Browse files
Design tokens and core theme config (GoogleChrome#5767)
* Add color tokens * Text sizes * Temp colors ref * First pass at tokens data complete * Basic scaffold of a token to Sass conversion setup * Fix typo * Add dev light task because the emulators weren't working very well on an m1 mac * Create a new Sass task and a design token to Sass variable converter * Switch to using hex because Sass is converting to those values anyway 😤 * Really basic gorko config and theme config * Improve the fluid type generator * Add a themes config, a gulp task and wire it all up to Gorko * Tidy up and add some comments * Add some notes to color * Add modified version of the modern css reset * Add some more tokens and theme values * Linter * Fix lockfile * Linting * Theme tweaks * Merge Sass into one task * Tweak naming * Get a modified version of the d.c.c reset * System fonts in place of Roboto * Clean packages * Adjust write-version * Remove test build * Make write-version verify or create the dist directory before it attempts to write. Co-authored-by: Rob Dodson <[email protected]>
1 parent 3cc4ade commit 798165d

17 files changed

+1287
-445
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ entrypoint.hashmanifest.json
8585
.tmp
8686
.firebase
8787
firebase.json
88+
src/scss/_tokens.scss
89+
src/scss/_themes.scss
8890

8991
# Eleventy
9092
# We generate our own .eleventyignore file dynamically during builds

gulp-tasks/convert-design-themes.js

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
const fs = require('fs');
2+
const prettier = require('prettier');
3+
const themes = require('../src/site/_data/design/themes.js');
4+
5+
/*
6+
Converts the theme config into Sass variables that can be used
7+
by Gorko to generate both Custom Property groups and also utility
8+
classes that have a direct relationship to in-context design tokens
9+
*/
10+
const convertDesignThemes = (cb) => {
11+
let result = '';
12+
13+
// Add a note that this is auto generated
14+
result += `
15+
/// Sass THEMES GENERATED WITH CONFIG ON ${new Date().toLocaleDateString()}.
16+
/// Config location: ../src/site/_data/design/themes.js
17+
`;
18+
19+
result += `
20+
21+
/// THEME KEYS
22+
/// These are used to generate utility classes for themeable properties like color and background
23+
`;
24+
result += '$gorko-theme-keys: (';
25+
26+
Object.keys(themes.colorKeys).forEach((colorKey) => {
27+
result += `'${themes.colorKeys[colorKey]}': var(--color-${themes.colorKeys[colorKey]}),`;
28+
});
29+
30+
result += ');';
31+
32+
result += `
33+
34+
/// THEMES
35+
/// The gorko config for the actual themes starts https://github.com/andy-piccalilli/gorko#using-themes
36+
`;
37+
result += '$gorko-themes: (';
38+
39+
themes.generate().forEach(({name, key, value, tokens}) => {
40+
result += `'${name}': (`;
41+
42+
if (key) {
43+
result += `'${key}': '${value}',`;
44+
}
45+
46+
result += `'tokens': (`;
47+
48+
result += `'color': (`;
49+
50+
Object.keys(tokens).forEach((tokenKey) => {
51+
result += `'${themes.colorKeys[tokenKey]}': var(--color-${tokens[tokenKey]}),`;
52+
});
53+
54+
result += '),'; // end color
55+
56+
result += '),'; // end tokens
57+
58+
result += '),'; // end theme
59+
});
60+
61+
result += ');'; // end $gorko-themes
62+
63+
// Make the Sass readable to help people with auto-complete in their editors
64+
result = prettier.format(result, {parser: 'scss'});
65+
66+
// Push this file into the Sass dir, ready to go
67+
fs.writeFileSync('./src/scss/_themes.scss', result);
68+
cb();
69+
};
70+
71+
module.exports = convertDesignThemes;

gulp-tasks/convert-design-tokens.js

+128
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
const fs = require('fs');
2+
const slugify = require('slugify');
3+
const prettier = require('prettier');
4+
const tokens = require('../src/site/_data/design/tokens.json');
5+
6+
/*
7+
Converts the design tokens into Sass variables so Gorko can use
8+
them as the core of the CSS system.
9+
*/
10+
const convertDesignTokens = (cb) => {
11+
let result = '';
12+
const rootSize = 16;
13+
const nameSlug = (text) => slugify(text, {lower: true});
14+
15+
// Generates a CSS clamp size with min and max values passed
16+
const clampGenerator = (min, max) => {
17+
if (min === max) {
18+
return `${min / 16}rem`;
19+
}
20+
21+
const minSize = min / rootSize;
22+
const maxSize = max / rootSize;
23+
const minViewport = 20; // rems
24+
const maxViewport = 90; // rems
25+
26+
// Slope and intersection allow us to have a fluid value but also keep that sensible
27+
const slope = (maxSize - minSize) / (maxViewport - minViewport);
28+
const intersection = -1 * minViewport * slope + minSize;
29+
30+
return `clamp(${minSize}rem, ${intersection.toFixed(2)}rem + ${
31+
slope.toFixed(2) * 100
32+
}vw, ${maxSize}rem)`;
33+
};
34+
35+
// Removes trailing commas and closes Sass map group
36+
const cleanResult = () => {
37+
result = result.replace(/,\s*$/, '');
38+
result += ');';
39+
};
40+
41+
// Add a note that this is auto generated
42+
result += `
43+
/// Sass VARIABLES GENERATED WITH DESIGN TOKENS ON ${new Date().toLocaleDateString()}.
44+
/// Tokens location: ../src/site/_data/design/tokens.json
45+
`;
46+
47+
// Start with colors
48+
result += `
49+
50+
/// COLORS
51+
`;
52+
result += '$gorko-colors: (';
53+
54+
tokens.colors.forEach((group) => {
55+
group.items.forEach((color) => {
56+
result += `'${nameSlug(group.group + '-' + color.name)}': ${color.hex},`;
57+
});
58+
});
59+
60+
cleanResult();
61+
62+
// Move on to text sizes
63+
result += `
64+
65+
/// TEXT SIZES
66+
`;
67+
result += '$gorko-size-scale: (';
68+
69+
tokens.textSizes.forEach((size) => {
70+
result += `'${nameSlug(size.name)}': ${clampGenerator(
71+
size.min,
72+
size.max,
73+
)},`;
74+
});
75+
76+
cleanResult();
77+
78+
// Move on to spacing
79+
result += `
80+
81+
/// SPACING SIZES
82+
`;
83+
result += '$gorko-space-scale: (';
84+
85+
tokens.spacing.forEach((size) => {
86+
result += `'${nameSlug(size.name)}': ${clampGenerator(
87+
size.min,
88+
size.max,
89+
)},`;
90+
});
91+
92+
cleanResult();
93+
94+
// Move on to fonts
95+
result += `
96+
97+
/// FONTS
98+
`;
99+
result += '$gorko-fonts: (';
100+
101+
tokens.fonts.forEach((font) => {
102+
result += `'${nameSlug(font.name)}': '${font.values.join(',')}',`;
103+
});
104+
105+
cleanResult();
106+
107+
// Lastly, misc values like radius and transitions
108+
result += `
109+
110+
/// MISC
111+
`;
112+
tokens.radius.forEach((item) => {
113+
result += `$global-radius-${nameSlug(item.name)}: ${item.value};`;
114+
});
115+
116+
tokens.transitions.forEach((item) => {
117+
result += `$global-transition-${nameSlug(item.name)}: ${item.value};`;
118+
});
119+
120+
// Make the Sass readable to help people with auto-complete in their editors
121+
result = prettier.format(result, {parser: 'scss'});
122+
123+
// Push this file into the Sass dir, ready to go
124+
fs.writeFileSync('./src/scss/_tokens.scss', result);
125+
cb();
126+
};
127+
128+
module.exports = convertDesignTokens;

gulp-tasks/sass.js

+10-30
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,16 @@
1-
const {dirname} = require('path');
2-
const {mkdirSync, writeFileSync} = require('fs');
3-
const sassProcessor = require('sass');
1+
const {dest, src} = require('gulp');
2+
const sassProcessor = require('gulp-sass')(require('sass'));
43

5-
const src = './src/styles/main.scss';
6-
const dest = './dist/css/main.css';
4+
const sourceFiles = ['./src/scss/next.scss', './src/styles/main.scss'];
75

8-
// Flags whether we generate sourcemaps
9-
// TODO: d.c.c. uses NODE_ENV but we use ELEVENTY_ENV a lot in web.dev.
10-
const isProd = process.env.ELEVENTY_ENV === 'prod';
6+
// Flags wether we compress the output etc
7+
const isProduction = process.env.NODE_ENV === 'production';
118

12-
// Techincally we're rendering synchronously so we don't need an async function,
13-
// but gulp requires all tasks to return a promise.
14-
const sass = async () => {
15-
// nb. No need to catch errors because gulp handles that for us and logs them.
16-
const result = sassProcessor.renderSync({
17-
file: src,
18-
// nb. Sass doesn't actually write to this outFile, the caller must do that
19-
// themselves.
20-
// outFile is used to determine the URL used to link from the generated CSS
21-
// to the source map, and from the source map to the Sass source files.
22-
outFile: dest,
23-
sourceMap: !isProd,
24-
});
25-
26-
mkdirSync(dirname(dest), {recursive: true});
27-
writeFileSync(dest, result.css.toString(), 'utf8');
28-
29-
// I'm not guarding for result.map here because if we're doing a dev build we
30-
// expect it to be defined and if it isn't, we want it to blow up.
31-
if (!isProd) {
32-
writeFileSync(dest + '.map', result.map.toString(), 'utf8');
33-
}
9+
const sass = (cb) => {
10+
return src(sourceFiles, {sourcemaps: !isProduction})
11+
.pipe(sassProcessor().on('error', sassProcessor.logError))
12+
.pipe(dest('./dist/css', {sourcemaps: !isProduction}))
13+
.on('done', cb);
3414
};
3515

3616
module.exports = sass;

gulp-tasks/write-version.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@
55
*
66
* @returns {Promise<void>}
77
*/
8+
const fs = require('fs');
89
const writeVersion = async () => {
910
if (process.env.ELEVENTY_ENV === 'prod') {
10-
require('fs').writeFileSync('./dist/version', process.env.GITHUB_SHA);
11+
// Verify dist directory exists
12+
fs.mkdirSync('./dist', {recursive: true});
13+
fs.writeFileSync('./dist/version', process.env.GITHUB_SHA || '');
1114
}
1215
};
1316

gulpfile.js

+17-2
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,39 @@
1515
*/
1616

1717
const gulp = require('gulp');
18+
const convertDesignThemes = require('./gulp-tasks/convert-design-themes.js');
19+
const convertDesignTokens = require('./gulp-tasks/convert-design-tokens.js');
1820
const copyDefaultLocale = require('./gulp-tasks/copy-default-locale.js');
1921
const copyFonts = require('./gulp-tasks/copy-fonts.js');
2022
const copyGlobalImages = require('./gulp-tasks/copy-global-images.js');
2123
const copyMisc = require('./gulp-tasks/copy-misc.js');
2224
const sassTask = require('./gulp-tasks/sass.js');
2325
const writeVersion = require('./gulp-tasks/write-version.js');
2426

25-
gulp.task('default-locale', copyDefaultLocale);
27+
gulp.task('convert-design-themes', convertDesignThemes);
28+
gulp.task('convert-design-tokens', convertDesignTokens);
2629
gulp.task('copy-misc', copyMisc);
30+
gulp.task('default-locale', copyDefaultLocale);
2731
gulp.task('sass', sassTask);
2832

2933
gulp.task(
3034
'build',
31-
gulp.parallel(copyGlobalImages, copyMisc, copyFonts, sassTask, writeVersion),
35+
gulp.series(
36+
convertDesignThemes,
37+
convertDesignTokens,
38+
gulp.parallel(
39+
copyGlobalImages,
40+
copyMisc,
41+
copyFonts,
42+
sassTask,
43+
writeVersion,
44+
),
45+
),
3246
);
3347

3448
gulp.task('watch', () => {
3549
gulp.watch('./src/images/**/*', {ignoreInitial: true}, copyGlobalImages);
3650
gulp.watch('./src/misc/**/*', {ignoreInitial: true}, copyMisc);
3751
gulp.watch('./src/styles/**/*.scss', {ignoreInitial: true}, sassTask);
52+
gulp.watch('./src/scss/**/*.scss', {ignoreInitial: true}, sassTask);
3853
});

0 commit comments

Comments
 (0)