Skip to content

Commit

Permalink
Merge pull request #1749 from chanzuckerberg/release-v13.2.0
Browse files Browse the repository at this point in the history
## [13.2.0](v13.1.1...v13.2.0) (2023-09-07)


### Features

* **Avatar:** add in expanded size range ([#1734](#1734)) ([7af6e5e](7af6e5e))
* **tokens:** add tooling for EDS theming ([#1738](#1738)) ([91497bf](91497bf))


### Bug Fixes

* opt our rollup CJS build into TS's module interop behavior ([#1747](#1747)) ([2b0855e](2b0855e))
  • Loading branch information
booc0mtaco committed Sep 7, 2023
2 parents b7224ed + 72f3567 commit 11c1a05
Show file tree
Hide file tree
Showing 19 changed files with 1,087 additions and 97 deletions.
2 changes: 1 addition & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
"files": [
"config.js",
"*.config.js",
"main.js",
"main.ts",
"plopfile.js",
"src/**/*.js"
],
Expand Down
101 changes: 99 additions & 2 deletions .storybook/components/Docs/Guidelines/Theming.stories.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,110 @@ import { Canvas, Meta } from '@storybook/blocks';

# Theming overview

"Theming", in the context of EDS, is the process of overriding the default styles of EDS components to match a different brand (or "theme"). The "Theming" directory in storybook demonstrates examples of theming:
"Theming", in the context of EDS, is the process of overriding the default styles of EDS components to match a different brand (or "theme"). We include useful examples under "Pages":

- A [wireframe theme](./?path=/story/pages-theming-wireframedemo--default) (an unbranded theme that can be used for prototyping a product before it has an official visual style).

Below are instructions on how to use the tooling and tokens to define custom theme values for a project.

## How to apply a theme in another product

In EDS, theming is implemented by overriding the values of the CSS variables representing tokens, which the EDS components use in their styles. This should update the style of the components to match the branding of a different product with minimum manual CSS styling overrides. (Some manual styling overrides will be necessary though because we don't have tokens for every little detail. In those cases, we could create a new token to make those overrides easier if it looks like something that could very well be useful for other products as well.)
EDS comes with some tooling to allow easy transfer of theme data from Figma (or some style-dictionary compatible format) into code.

* `eds-init-theme` - This command sets up the initial file(s) for theming your application
* `eds-apply-theme` - This command parses the style dictionary files to generate the tokens (CSS Variables) used by EDS

Each of these tools reads config to figure out where to read/write files. This can be defined in several ways, e.g., a top-level file `.edsrc.json`, or as a key-value set in package.json. Example:

`package.json`

```json
"eds": {
"json": "src/components/",
"css": "src/components/"
},
```

`.edsrc.json`

```json
{
"json": "src/components/",
"css": "src/components/"
}
```

`json` determines where the core theme file will be copied to OR read from, and `css` determines where the resulting css token file will be stored.

### eds-init-theme

This will create an initial JSON file `app-theme.json` that defines ALL the available tokens for EDS that you can edit.

EDS comes pre-packaged with many tokens that define the base style and character of the system. Users of EDS can theme certain aspects of all components, or details on specific components.

```json
{
"eds": {
"anim": {
"fade": {
"quick": {
"value": "0.15s"
},
"long": {
"value": "0.4s"
}
},
"move": {
"quick": {
"value": "0.15s"
},
"medium": {
"value": "0.3s"
},
"long": {
"value": "0.4s"
}
},
"ease": {
"value": "ease"
}
},
// ...other token values
},
}
```

### eds-apply-theme

After making changes to the `app-theme.json` to reflect what has been defined by design, update the CSS token file by running `npx eds-apply-theme`.

Once run, you will have a CSS file `app-theme.css` that includes a set of token values that can be used in the app as appropriate.

```css
/**
* Do not edit directly
* Generated on Sunday, 01 Jan 2023 12:34:56 GMT
* To update, edit app-theme.json, then run `npx eds-apply-theme`
*/

:root {
--eds-anim-fade-quick: 0.15s;
--eds-anim-fade-long: 0.4s;
--eds-anim-move-quick: 0.15s;
--eds-anim-move-medium: 0.3s;
--eds-anim-move-long: 0.4s;
--eds-anim-ease: ease;
/* ...other token values... */
}
```

Add this file to your core app root file. That's it! Now, the theme will be applied to the tokens used by EDS components. To make other changes, edit `app-theme.json`, then re-run `npx eds-apply-theme`.

**NOTE**: do not edit this file directly. Instead, follow the instructions at the top of the file!

## How to manually apply a theme in another product

You can also manage the creation of theme token definitions manually. In EDS, theming is implemented by overriding the values of the CSS variables representing tokens, which the EDS components use in their styles. This should update the style of the components to match the branding of a different product with minimum manual CSS styling overrides. (Some manual styling overrides will be necessary though because we don't have tokens for every little detail. In those cases, we could create a new token to make those overrides easier if it looks like something that could very well be useful for other products as well.)

These CSS variables overrides lives in the products using EDS components. This allows product teams to quickly iterate on their theme without making changes to EDS itself.

Expand Down
2 changes: 1 addition & 1 deletion .storybook/components/Docs/Guidelines/Tokens.stories.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -174,4 +174,4 @@ If the EDS tailwind config theme is being used, Tier 2 and tier 3 color tokens a
<!-- will reflect respective color utility tokens
(background-brand-primary-strong and border-brand-primary-strong) -->
<div className="bg-brand-primary-strong border-brand-primary-strong"></div>
```
```
48 changes: 47 additions & 1 deletion .storybook/main.js → .storybook/main.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import type { StorybookConfig } from '@storybook/react-webpack5';
import type { Configuration } from 'webpack';

/**
* Although `--static-dir` is marked as deprecated. The The Chromatic CLI
* currently pulls the staticDir from the build script, but does not support
Expand All @@ -6,7 +9,7 @@
* We should refrain from using the staticDirs option in this configuration until
* https://github.com/chromaui/chromatic-cli/issues/462 is resolved.
*/
module.exports = {
const config: StorybookConfig = {
stories: [
'./components/**/*.stories.mdx',
'./components/**/*.stories.@(js|jsx|ts|tsx)',
Expand Down Expand Up @@ -70,4 +73,47 @@ module.exports = {
plugins: [],
};
},
webpackFinal(config, { configType }) {
if (configType === 'DEVELOPMENT') {
updateCSSLoaderPlugin(config);
}
return config;
},
};

/**
* Updates the `css-loader` webpack plugin to make class names human readable.
*
* NOTE: This should only be used for local development.
*/
function updateCSSLoaderPlugin(config: Configuration): Configuration {
config.module?.rules?.forEach((rule) => {
if (rule && typeof rule === 'object' && Array.isArray(rule.use)) {
const isRuleForCSS = rule.test?.toString() === '/\\.css$/';
if (isRuleForCSS) {
rule.use.forEach((ruleSetRule) => {
if (
typeof ruleSetRule === 'object' &&
ruleSetRule?.loader?.includes('node_modules/css-loader')
) {
ruleSetRule.options = {
// @ts-expect-error css-loader doesn't accept "string" options
// and will either be an object or undefined
...ruleSetRule.options,
modules: {
// @ts-expect-error css-loader doesn't accept "string" options
// and will either be an object or undefined
...ruleSetRule.options?.modules,
localIdentName: '[name]__[local]--[hash:base64:5]',
},
};
}
});
}
}
});

return config;
}

export default config;
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,19 @@

All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.

## [13.2.0](https://github.com/chanzuckerberg/edu-design-system/compare/v13.1.1...v13.2.0) (2023-09-07)


### Features

* **Avatar:** add in expanded size range ([#1734](https://github.com/chanzuckerberg/edu-design-system/issues/1734)) ([7af6e5e](https://github.com/chanzuckerberg/edu-design-system/commit/7af6e5ecb6b79bfdece58643faeac1b741194964))
* **tokens:** add tooling for EDS theming ([#1738](https://github.com/chanzuckerberg/edu-design-system/issues/1738)) ([91497bf](https://github.com/chanzuckerberg/edu-design-system/commit/91497bf2f0c334566c1144607b7d4b27a9677a30))


### Bug Fixes

* opt our rollup CJS build into TS's module interop behavior ([#1747](https://github.com/chanzuckerberg/edu-design-system/issues/1747)) ([2b0855e](https://github.com/chanzuckerberg/edu-design-system/commit/2b0855eee3d236931eb0ae2e68ffeceedf3a2cbe))

### [13.1.1](https://github.com/chanzuckerberg/edu-design-system/compare/v13.1.0...v13.1.1) (2023-09-01)


Expand Down
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ html {

### Tailwind Setup

The EDS Tailwind theme provides EDS color [tokens](https://chanzuckerberg.github.io/edu-design-system/?path=/story/documentation-guidelines-tokens--page) and screens. Import the tailwind config into the app's tailwind config and supply the [content](https://tailwindcss.com/docs/content-configuration) property for use:
The EDS Tailwind theme provides EDS color [tokens][tokens] and screens. Import the tailwind config into the app's tailwind config and supply the [content](https://tailwindcss.com/docs/content-configuration) property for use:

```js
const edsConfig.theme = require('@chanzuckerberg/eds/tailwind.config');
Expand All @@ -47,7 +47,15 @@ module.exports = {
};
```

Refer to the [tokens tailwind section](https://chanzuckerberg.github.io/edu-design-system/?path=/story/documentation-guidelines-tokens--page#tailwind-class-tokens) for usage guidelines.
Refer to the [tokens tailwind section][tokens] for usage guidelines.

[tokens]: https://chanzuckerberg.github.io/edu-design-system/?path=/docs/documentation-guidelines-tokens--docs


### Theming Setup

Refer to the "EDS Token and Theme Tools" in [the tokens documentation](https://chanzuckerberg.github.io/edu-design-system/?path=/docs/documentation-theming--docs) to learn about the optional tooling setup.


## Usage

Expand Down Expand Up @@ -101,6 +109,6 @@ This project is governed under the [Contributor Covenant](https://www.contributo

See our [Security Readme](https://github.com/chanzuckerberg/edu-design-system/blob/main/SECURITY.md).

## More Information and Support
## FAQ, More Information, and Support

Please review our Education Design System Site (SSO Required): [/Paper](https://eds.czi.design/0843bc428/p/581284-education-design-system)
24 changes: 24 additions & 0 deletions bin/_util.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
module.exports = {
/**
* Fetch the EDS config from the project using the lilconfig hierarchy.
* This can be from package.json, or from various separate non-YAML files.
*
* @see https://github.com/antonk52/lilconfig#usage
* @returns nullable config object returned from lilconfig
*/
getConfig: async function () {
const { lilconfig } = require('lilconfig');

// read in the config from config file, package json "eds", etc.
const settings = await lilconfig('eds').search();

// If no config exists, fail
if (!settings) {
throw new Error(
'Please add EDS config to your project before continuing (specify "json" and "css" target paths)',
);
}

return settings.config;
},
};
95 changes: 95 additions & 0 deletions bin/eds-apply-theme.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
#!/usr/bin/env node
(async function () {
const StyleDictionary = require('style-dictionary');
const path = require('path');
const fs = require('fs');
const { getConfig } = require('./_util');

let packageRootPath;
try {
packageRootPath =
path.dirname(require.resolve('@chanzuckerberg/eds')) + '/tokens/json/';
} catch (e) {
console.error('EDS package not installed. Using local path...');
packageRootPath =
path.dirname(require.main.path) + '/src/tokens-dist/json/';
}

// Read the config to sort out where to read JSON from and where to write the CSS file
const config = await getConfig();

// read and parse JSON files on disk
const localTheme = JSON.parse(
fs.readFileSync(`${config.json}app-theme.json`, 'utf8'),
);
const baseTheme = JSON.parse(
fs.readFileSync(`${packageRootPath}theme-base.json`, 'utf8'),
);

// define the header to use in the resulting CSS file so people know not to edit it directly
StyleDictionary.registerFileHeader({
name: 'cssOverrideHeader',
fileHeader: (defaultMessage) => [
...defaultMessage,
'To update, edit app-theme.json, then run `npx eds-apply-theme`',
],
});

const EDSStyleDictionary = StyleDictionary.extend({
source: [config.json + 'app-theme.json'],
platforms: {
css: {
transforms: [...StyleDictionary.transformGroup.css, 'name/cti/kebab'],
buildPath: config.css,
files: [
{
format: 'css/variables',
destination: 'app-theme.css',
options: {
fileHeader: 'cssOverrideHeader',
},
filter: function (token) {
// don't allow theming on legacy tokens
return token.attributes.category !== 'legacy';
},
},
],
},
},
});

/**
* Determine if the given theme file is a subset of what's in the base theme file.
* If it isnt, throw an error:
* - If keys are in base that are missing in the theme file, that's OK (no need to override everything)
* - If keys are in theme that aren't in base, throw (you can't theme tokens that don't exist in EDS)
* @param {object} base The tokens theme file stored in the EDS project
* @param {object} theme The project theme file stored in the app code (same format as bas)
* @param {Array} path The base path, stored as an array of object key names (default [])
* @throws Error when there are tokens in theme that aren't in base
*/
function isStrictSubset(base, theme, path = []) {
for (const name in theme) {
if (typeof theme[name] === 'object') {
if (base[name] === undefined) {
throw new Error(
`Local themeable value does not exist in base theme: --${path.join(
'-',
)}.${name}"`,
);
}
isStrictSubset(base[name], theme[name], path.concat(name));
}
}
}

try {
// Keys in the theme file must be a strict subset of those in the base file
isStrictSubset(baseTheme, localTheme);
EDSStyleDictionary.buildAllPlatforms();
} catch (error) {
// TODO: if theme has things not in base, error showing where the conflict
console.error('EDS theming error:', error.message);
return;
}
})();
Loading

0 comments on commit 11c1a05

Please sign in to comment.