Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/thin-buttons-appear.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@guardian/source': minor
---

generate CSS/SCSS files for typography, palette, breakpoints
6 changes: 6 additions & 0 deletions libs/@guardian/source/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
"import": "./dist/foundations.js",
"require": "./dist/foundations.cjs"
},
"./foundations/breakpoints.scss": "./dist/foundations/__generated__/breakpoints.scss",
"./foundations/palette.css": "./dist/foundations/__generated__/palette.css",
"./foundations/typography.css": "./dist/foundations/__generated__/typography.css",
"./react-components": {
"types": "./dist/react-components.d.ts",
"import": "./dist/react-components.js",
Expand Down Expand Up @@ -67,10 +70,13 @@
"jest": "30.0.5",
"lightningcss": "1.30.0",
"mkdirp": "3.0.1",
"postcss": "8.5.6",
"postcss-scss": "4.0.9",
"prettier": "3.3.3",
"react": "18.2.0",
"react-dom": "18.2.0",
"rollup": "4.50.1",
"rollup-plugin-copy": "3.5.0",
"storybook": "8.6.4",
"ts-jest": "29.4.0",
"tslib": "2.6.2",
Expand Down
11 changes: 11 additions & 0 deletions libs/@guardian/source/rollup.config.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
import copy from 'rollup-plugin-copy';
import config from '../../../configs/rollup/rollup.config.js';

export default config({
input: {
foundations: 'src/foundations/index.ts',
'react-components': 'src/react-components/index.ts',
},
plugins: [
copy({
targets: [
{
src: 'src/foundations/__generated__/*.{scss,css}',
dest: 'dist/foundations/__generated__',
},
],
}),
],
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/* eslint-disable import/no-default-export -- cobalt plugins do this */

// @ts-check

import { defaultTransformer } from '@cobalt-ui/plugin-js';
import { set } from '@cobalt-ui/utils';
import { template } from '../../lib/template.js';

/**
* @param {{ filename: string; }} options
* @returns {import('@cobalt-ui/core').Plugin}
*/
export default function pluginBreakpointsScss(options) {
return {
name: 'plugin-breakpoints-scss',

config(/* config */) {},
async build({ tokens /*, rawSchema, metadata */ }) {
const TOKEN_GROUP = 'breakpoint';

const breakpointTokens = tokens.filter((token) =>
token.id.startsWith(TOKEN_GROUP),
);

/**
* @type {Object.<string, string>}
*/
const transformedTokens = {};

// we can re-use the default transformer from `@cobalt-ui/plugin-js`
for (const token of breakpointTokens) {
set(transformedTokens, token.id, defaultTransformer(token));
}

const breakpointPairs = [];

for (const breakpoints of Object.values(transformedTokens)) {
for (const [name, value] of Object.entries(breakpoints)) {
breakpointPairs.push({ name, value });
}
}

breakpointPairs.sort((a, b) => parseFloat(a.value) - parseFloat(b.value));

const breakpointEntries = breakpointPairs.map(
({ name, value }) => `\t${name}: ${value},`,
);

const scssSource = `$breakpoints: (\n${breakpointEntries.join('\n')}\n);`;

return [
{
filename: options.filename,
contents: template(import.meta.filename, scssSource),
},
];
},
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,17 @@

import { defaultTransformer, serializeJS } from '@cobalt-ui/plugin-js';
import { set } from '@cobalt-ui/utils';
import { pxStringToNumber } from '../lib/convert-value.js';
import { getCommentId } from '../lib/get-comment-id.js';
import { template } from '../lib/template.js';
import { pxStringToNumber } from '../../lib/convert-value.js';
import { getCommentId } from '../../lib/get-comment-id.js';
import { template } from '../../lib/template.js';

/**
* @param {{ filename: string; }} options
* @returns {import('@cobalt-ui/core').Plugin}
*/
export default function pluginBreakpoints(options) {
return {
name: 'plugin-breakpoints',
name: 'plugin-breakpoints-typescript',

config(/* config */) {},
async build({ tokens, rawSchema /*, metadata */ }) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/* eslint-disable import/no-default-export -- cobalt plugins do this */

// @ts-check

import { defaultTransformer } from '@cobalt-ui/plugin-js';
import { set } from '@cobalt-ui/utils';
import { camelToKebab } from '../../lib/case.js';
import { template } from '../../lib/template.js';

/**
* @param {string[]} variableDecls eg. ["--src-brand-100: #001536;", "--src-brand-300: #041F4A;"]
*
* NOTE: we don't bother with proper indentation here, prettier will sort that out at build
*/
const cssTemplate = (variableDecls) => `:root { ${variableDecls.join('\n')} }
`;

/**
* @param {{ filename: string; }} options
* @returns {import('@cobalt-ui/core').Plugin}
*/
export default function pluginPaletteCss(options) {
return {
name: 'plugin-palette-css',

config(/* config */) {},
async build({ tokens /*, rawSchema, metadata */ }) {
const TOKEN_GROUP = 'palette';

/** @type {Object.<string, string>} */
const transformedTokens = {};

const paletteTokens = tokens.filter((token) =>
token.id.startsWith(TOKEN_GROUP),
);

// we can re-use the default transformer from `@cobalt-ui/plugin-js`
for (const token of paletteTokens) {
set(transformedTokens, token.id, defaultTransformer(token));
}

const cssVariablesDecls = [];

for (const tokens of Object.values(transformedTokens)) {
// eg. [ "brand", { "100": "#001536", "300": "#041F4A", ... } ]
for (const [category, shades] of Object.entries(tokens)) {
// eg. [ "100", "#001536" ]
for (const [shade, color] of Object.entries(shades)) {
const varName = `--src-${camelToKebab(category)}-${shade}`;
cssVariablesDecls.push(`\t${varName}: ${color};`);
}
}
}

return [
{
filename: options.filename,
contents: template(
import.meta.filename,
cssTemplate(cssVariablesDecls),
),
},
];
},
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@

import { defaultTransformer, serializeJS } from '@cobalt-ui/plugin-js';
import { set } from '@cobalt-ui/utils';
import { getCommentId } from '../lib/get-comment-id.js';
import { template } from '../lib/template.js';
import { getCommentId } from '../../lib/get-comment-id.js';
import { template } from '../../lib/template.js';

/**
* @param {{ filename: string; }} options
* @returns {import('@cobalt-ui/core').Plugin}
*/
export default function pluginPalette(options) {
return {
name: 'plugin-palette',
name: 'plugin-palette-typescript',

config(/* config */) {},
async build({ tokens, rawSchema /*, metadata */ }) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/** @param {string} fontSize */
export const textDecorationThickness = (fontSize) => {
switch (fontSize) {
case '20px':
case '24px':
case '28px':
return '3px';
case '34px':
return '4px';
case '42px':
return '5px';
case '50px':
case '64px':
case '70px':
return '6px';
default:
return '2px';
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/* eslint-disable import/no-default-export -- cobalt plugins do this */

// @ts-check

import { defaultTransformer } from '@cobalt-ui/plugin-js';
import { set } from '@cobalt-ui/utils';
import { camelToKebab } from '../../lib/case.js';
import { fontArrayToString, pxStringToRem } from '../../lib/convert-value.js';
import { template } from '../../lib/template.js';
import { textDecorationThickness } from './common.js';

/**
* @typedef {Object} TypographyPreset
* @property {string[]} fontFamily
* @property {string} fontSize
* @property {number} lineHeight
* @property {number} fontWeight
* @property {string} fontStyle
*/

const GROUP_PREFIX = 'typographyPresets.';

/**
* Converts eg. "headlineBold24" to "src-headline-bold-24"
*
* @param {string} presetName
*/
const classNameFromPreset = (presetName) => {
return `.src-${camelToKebab(presetName).replace(/(\d+)$/, '-$1')}`;
};

/**
*
* @param {string} preset
* @param {TypographyPreset} properties
*/
const presetClass = (preset, properties) => `${classNameFromPreset(preset)} {
font-family: ${fontArrayToString(properties.fontFamily)};
font-size: ${pxStringToRem(properties.fontSize)};
line-height: ${properties.lineHeight};
font-weight: ${properties.fontWeight};
font-style: ${properties.fontStyle};
--source-text-decoration-thickness: ${textDecorationThickness(properties.fontSize)};
}`;

/**
* @param {{ filename: string; }} options
* @returns {import('@cobalt-ui/core').Plugin}
*/
export default function pluginTypographyCss(options) {
return {
name: 'plugin-typography-css',

config(/* config */) {},
async build({ tokens /* metadata, rawSchema */ }) {
/** @type {Object.<string, string>} */
const transformedTokens = {};

const typographyTokens = tokens.filter((token) =>
token.id.startsWith(GROUP_PREFIX),
);

// we can re-use the default transformer from `@cobalt-ui/plugin-js`
for (const token of typographyTokens) {
set(transformedTokens, token.id, defaultTransformer(token));
}

/** @type {Object.<!string, TypographyPreset>} */
const typographyPresets = transformedTokens.typographyPresets;

const cssClasses = Object.entries(typographyPresets)
.map(([preset, properties]) => presetClass(preset, properties))
.join('\n\n');

const cssSource = cssClasses;

return [
{
filename: options.filename,
contents: template(import.meta.filename, cssSource),
},
];
},
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,10 @@

import { defaultTransformer } from '@cobalt-ui/plugin-js';
import { set } from '@cobalt-ui/utils';
import { fontArrayToString, pxStringToRem } from '../lib/convert-value.js';
import { getCommentId } from '../lib/get-comment-id.js';
import { template } from '../lib/template.js';

const GROUP_PREFIX = 'typographyPresets.';

/** @param {string} fontSize */
const textDecorationThickness = (fontSize) => {
switch (fontSize) {
case '20px':
case '24px':
case '28px':
return '3px';
case '34px':
return '4px';
case '42px':
return '5px';
case '50px':
case '64px':
case '70px':
return '6px';
default:
return '2px';
}
};
import { fontArrayToString, pxStringToRem } from '../../lib/convert-value.js';
import { getCommentId } from '../../lib/get-comment-id.js';
import { template } from '../../lib/template.js';
import { textDecorationThickness } from './common.js';

/**
* @typedef {Object} TypographyPreset
Expand All @@ -39,6 +18,8 @@ const textDecorationThickness = (fontSize) => {
* @property {string} fontStyle
*/

const GROUP_PREFIX = 'typographyPresets.';

/**
*
* @param {string} preset
Expand Down Expand Up @@ -69,7 +50,7 @@ export const ${preset}Object = {
*/
export default function pluginBreakpoints(options) {
return {
name: 'plugin-typography',
name: 'plugin-typography-typescript',

config(/* config */) {},
async build({ tokens /* metadata, rawSchema */ }) {
Expand Down
6 changes: 6 additions & 0 deletions libs/@guardian/source/src/design-tokens/lib/case.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/**
* Convert camelCase to kebab-case
* @param {string} str
*/
export const camelToKebab = (str) =>
str.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
Loading
Loading