Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WB-1816] Rework semanticColor tokens to be exported as CSS variables #2481

Open
wants to merge 15 commits into
base: main
Choose a base branch
from

Conversation

jandrade
Copy link
Member

@jandrade jandrade commented Feb 26, 2025

Summary:

  • Tokens: Replaces semanticColor values with references to css variables.
    Also, adds a build step to convert semanticColor tokens to css variables that
    can be consumed via CSS imports.
  • Theming: Adds a new ThemeSwitcher component to change themes using CSS
    variables.
  • Pill: Replaces mix() with color-mix() for active background colors.

Issue: WB-1816

Test plan:

  1. Navigate to /?path=/story/packages-labeledfield--validation.
  2. Click on the Theme dropdown in the storybook toolbar.
  3. Select a different theme from the dropdown (Classroom).
  4. Verify that the theme changes and the colors are updated.
Screen.Recording.2025-02-27.at.1.04.41.PM.mov

Juan Andrade added 4 commits February 26, 2025 17:44
…h references to css variables. Creates a build step to convert semanticColor tokens to css variables that can be consumed via CSS imports.
@jandrade jandrade self-assigned this Feb 26, 2025
Copy link

changeset-bot bot commented Feb 26, 2025

🦋 Changeset detected

Latest commit: 5f147af

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 24 packages
Name Type
@khanacademy/wonder-blocks-form Patch
@khanacademy/wonder-blocks-pill Patch
@khanacademy/wonder-blocks-tokens Major
@khanacademy/wonder-blocks-theming Minor
@khanacademy/wonder-blocks-search-field Patch
@khanacademy/wonder-blocks-dropdown Patch
@khanacademy/wonder-blocks-accordion Patch
@khanacademy/wonder-blocks-banner Patch
@khanacademy/wonder-blocks-birthday-picker Patch
@khanacademy/wonder-blocks-breadcrumbs Patch
@khanacademy/wonder-blocks-button Patch
@khanacademy/wonder-blocks-cell Patch
@khanacademy/wonder-blocks-clickable Patch
@khanacademy/wonder-blocks-grid Patch
@khanacademy/wonder-blocks-icon-button Patch
@khanacademy/wonder-blocks-labeled-field Patch
@khanacademy/wonder-blocks-layout Patch
@khanacademy/wonder-blocks-link Patch
@khanacademy/wonder-blocks-modal Patch
@khanacademy/wonder-blocks-popover Patch
@khanacademy/wonder-blocks-progress-spinner Patch
@khanacademy/wonder-blocks-switch Patch
@khanacademy/wonder-blocks-toolbar Patch
@khanacademy/wonder-blocks-tooltip Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link
Contributor

github-actions bot commented Feb 26, 2025

Size Change: +273 B (+0.28%)

Total Size: 98.2 kB

Filename Size Change
packages/wonder-blocks-form/dist/es/index.js 6.03 kB -5 B (-0.08%)
packages/wonder-blocks-pill/dist/es/index.js 1.52 kB +37 B (+2.5%)
packages/wonder-blocks-theming/dist/es/index.js 745 B +66 B (+9.72%) ⚠️
packages/wonder-blocks-tokens/dist/es/index.js 2.68 kB +175 B (+6.97%) 🔍
ℹ️ View Unchanged
Filename Size
packages/wonder-blocks-accordion/dist/es/index.js 3.54 kB
packages/wonder-blocks-banner/dist/es/index.js 1.55 kB
packages/wonder-blocks-birthday-picker/dist/es/index.js 1.82 kB
packages/wonder-blocks-breadcrumbs/dist/es/index.js 886 B
packages/wonder-blocks-button/dist/es/index.js 4.35 kB
packages/wonder-blocks-cell/dist/es/index.js 2.01 kB
packages/wonder-blocks-clickable/dist/es/index.js 3.05 kB
packages/wonder-blocks-core/dist/es/index.js 2.85 kB
packages/wonder-blocks-data/dist/es/index.js 6.25 kB
packages/wonder-blocks-dropdown/dist/es/index.js 19.5 kB
packages/wonder-blocks-grid/dist/es/index.js 1.36 kB
packages/wonder-blocks-icon-button/dist/es/index.js 3.11 kB
packages/wonder-blocks-icon/dist/es/index.js 873 B
packages/wonder-blocks-labeled-field/dist/es/index.js 1.22 kB
packages/wonder-blocks-layout/dist/es/index.js 1.82 kB
packages/wonder-blocks-link/dist/es/index.js 2.04 kB
packages/wonder-blocks-modal/dist/es/index.js 5.5 kB
packages/wonder-blocks-popover/dist/es/index.js 4.92 kB
packages/wonder-blocks-progress-spinner/dist/es/index.js 1.52 kB
packages/wonder-blocks-search-field/dist/es/index.js 1.33 kB
packages/wonder-blocks-switch/dist/es/index.js 2 kB
packages/wonder-blocks-testing-core/dist/es/index.js 3.73 kB
packages/wonder-blocks-testing/dist/es/index.js 1.07 kB
packages/wonder-blocks-timing/dist/es/index.js 1.79 kB
packages/wonder-blocks-toolbar/dist/es/index.js 923 B
packages/wonder-blocks-tooltip/dist/es/index.js 7.01 kB
packages/wonder-blocks-typography/dist/es/index.js 1.23 kB

compressed-size-action

Copy link
Contributor

github-actions bot commented Feb 26, 2025

A new build was pushed to Chromatic! 🚀

https://5e1bf4b385e3fb0020b7073c-fhkfqaiwix.chromatic.com/

Chromatic results:

Metric Total
Captured snapshots 381
Tests with visual changes 0
Total stories 566
Inherited (not captured) snapshots [TurboSnap] 0
Tests on the build 381

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: I'm just adding some arbitrary values here for testing purposes (and to show how it's going to work), but I'll remove this before landing this PR.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: I just moved all the contents from tokens/semantic-color.ts to this file.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: This is one of the main changes:

  • Instead of exporting a static object, I'm exporting the result of the function that iterates over the default semanticColor object and replaces all values to references to CSS variables.
  • The other part of the change is made at the build process, where we generate the styles.css file in dist to export all the semantic-color tokens as css variables.

Comment on lines +5 to +11
"exports": {
".": {
"import": "./dist/es/index.js",
"require": "./dist/index.js"
},
"./styles.css": "./dist/css/index.css"
},
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: This change is required for the consumers to be able to import/resolve the CSS file correctly.

This means that consumers can do this:

import "@khanacademy/wonder-blocks-tokens/styles.css";

@@ -1,4 +1,3 @@
import * as tokens from "@khanacademy/wonder-blocks-tokens";
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: Changed this to avoid a circular dependencies issue between theming and tokens. (we only use tokens in this package for testing purposes).

Comment on lines +249 to +250
: // NOTE(WB-1880): This will be simplified once we split this into Badge and Pill.
`color-mix(in srgb, ${tokens.color.offBlack32}, ${backgroundColor})`;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: I applied this change to prevent an issue with mix() when used with css variables. This change should be safe for two reasons:

  1. These variants are not currently used in webapp.
  2. We are going to be able to use color-mix() very soon (according to our upcoming browser support matrix).

@@ -3,6 +3,7 @@ import Choice from "./components/choice";
import CheckboxGroup from "./components/checkbox-group";
import RadioGroup from "./components/radio-group";
import TextField from "./components/text-field";
// eslint-disable-next-line import/no-deprecated
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: ESLint complained about this, so I added these lines in this file.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ohh interesting, I recently added the @deprecated tag for LabeledTextField, but didn't get any lint errors in the PR checks 🤔

Copy link
Member

@beaesguerra beaesguerra Feb 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I know why! The PR lint check only linted the files changed in the PR! Thanks for fixing it here!
https://github.com/Khan/wonder-blocks/actions/runs/13462088339/job/37619603166?pr=2476

image

@jandrade jandrade marked this pull request as ready for review February 27, 2025 18:05
@khan-actions-bot khan-actions-bot requested a review from a team February 27, 2025 18:06
@khan-actions-bot
Copy link
Contributor

Gerald

Required Reviewers
  • @Khan/wonder-blocks for changes to package.json, pnpm-lock.yaml, .changeset/gold-peaches-guess.md, .changeset/heavy-islands-tan.md, .changeset/silver-pumas-marry.md, .changeset/tidy-humans-fail.md, .storybook/preview.tsx, types/assets.d.ts, packages/wonder-blocks-theming/tsconfig-build.json, packages/wonder-blocks-tokens/package.json, packages/wonder-blocks-tokens/tsconfig-build.json, packages/wonder-blocks-form/src/index.ts, packages/wonder-blocks-theming/src/index.ts, packages/wonder-blocks-theming/src/types.ts, packages/wonder-blocks-form/src/components/text-area.tsx, packages/wonder-blocks-form/src/components/text-field.tsx, packages/wonder-blocks-pill/src/components/pill.tsx, packages/wonder-blocks-theming/src/components/theme-switcher.tsx, packages/wonder-blocks-tokens/src/build/generate-css-variables.ts, packages/wonder-blocks-tokens/src/internal/generate-tokens.ts, packages/wonder-blocks-tokens/src/internal/map-values-to-css-vars.ts, packages/wonder-blocks-tokens/src/tokens/semantic-color.ts, packages/wonder-blocks-tokens/src/util/constants.ts, packages/wonder-blocks-tokens/src/util/types.ts, packages/wonder-blocks-theming/src/utils/__tests__/merge-theme.test.ts, packages/wonder-blocks-tokens/src/internal/__test__/generate-tokens.test.ts, packages/wonder-blocks-tokens/src/internal/__test__/map-values-to-css-vars.test.ts, packages/wonder-blocks-tokens/src/theme/color/classroom.ts, packages/wonder-blocks-tokens/src/theme/color/default.ts

Don't want to be involved in this pull request? Comment #removeme and we won't notify you of further changes.

Copy link
Contributor

github-actions bot commented Feb 27, 2025

npm Snapshot: Published

🎉 Good news!! We've packaged up the latest commit from this PR (6c89458) and published all packages with changesets to npm.

You can install the packages in webapp by running:

./services/static/dev/tools/deploy_wonder_blocks.js --tag="PR2481"

Packages can also be installed manually by running:

pnpm add @khanacademy/wonder-blocks-<package-name>@PR2481

Copy link
Member

@marcysutton marcysutton left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work, @jandrade! This is going to be super useful!!

// NOTE: External consumers should import the CSS variables from the
// wonder-blocks-tokens package directly.
// e.g. import "@khanacademy/wonder-blocks-tokens/styles.css";
import "../node_modules/@khanacademy/wonder-blocks-tokens/dist/css/index.css";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: Is there a reason why you can't import the styles.css file from the tokens like you'd recommend for consumers?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For some reason, Storybook wasn't able to resolve that import here. It's probably down to some vite setting that I'm missing. I tried to add the wb-tokens import directly in the root package.json but that didn't solve the issue.

@@ -388,7 +388,7 @@ const styles = StyleSheet.create({
},
":focus-visible": {
// TODO(WB-1856): Verify if we can use the global focus color
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this TODO comment be removed with this PR, or is that different work?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is happening here: #2480

: // NOTE(WB-1880): This will be simplified once we split this into Badge and Pill.
mix(tokens.color.offBlack32, backgroundColor);
kind === "transparent" || kind === "neutral"
? tokens.color.offBlack16
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm surprised to see semanticColor replaced by tokens.color. Is that intentional?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ahhh hmmm yeah, so turned out that I used the incorrect semantic token (compared to the Figma specs), and we don't have that value represented as a semantic token, so I went back to using a primitive for this case for now. I'll revisit this as soon as we split this component + add theming support to it. I'm going to add a TODO comment to provide better visibility of when this is going to be fixed.

Figma: https://www.figma.com/design/VbVu3h2BpBhH80niq101MHHE/%F0%9F%92%A0-Main-Components?node-id=15622-5172&t=IaSf1JkId8CwSnky-4

// Remove the file extension
const filename = file.split(".")[0];
// eslint-disable-next-line @typescript-eslint/no-require-imports
const {default: themeObject} = require(`../theme/color/${filename}`);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you use THEMES_DIR here to make it easy to update?

const {default: themeObject} = require(`${THEMES_DIR}/${filename}`);

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! I'll replace it :)

Copy link
Member

@beaesguerra beaesguerra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yayy nice work! This is big step towards supporting more theming 🎉 Left some questions!

// Import the Wonder Blocks CSS variables
// NOTE: External consumers should import the CSS variables from the
// wonder-blocks-tokens package directly.
// e.g. import "@khanacademy/wonder-blocks-tokens/styles.css";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have instructions for setting up WB in projects somewhere? We could add these details there too! I poked around and couldn't find any docs for setting up though! (This could be a later task 😄 also happy to take this on, if we think it would be useful!)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great question! we don't have explicit instructions, and that would be great. Go for it if you could work on those docs :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created WB-1892 for set up docs! 😄

{
value: "classroom",
icon: "lightning",
title: "Classroom",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to name this Thunder Blocks since that's the name of the associated Figma library? Or Classroom (Thunder Blocks) or Thunder Blocks (Classroom)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds good! I'll go with Thunder Blocks (Classroom).

Comment on lines +56 to +58
"@khanacademy/wonder-blocks-link": "workspace:*",
"@khanacademy/wonder-blocks-tokens": "workspace:*",
"@khanacademy/wonder-blocks-theming": "workspace:*",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For my own learning, how do we know when we should include WB packages as a dev dependency at the root level?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For some context on why I included this here: this was supposed to fix the SB issue not properly resolving the styles.css import from tokens. The other imports were added as we are using them in .storybook/preview.tsx.

As for devDeps vs regular deps, I think the distinction mostly applies for nested package.json files (all the WB packages), but for the root package, I think we should include them as devDep as these are packages used/required by internal tooling (Storybook in this case).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, thanks for the explanation!

@@ -3,6 +3,7 @@ import Choice from "./components/choice";
import CheckboxGroup from "./components/checkbox-group";
import RadioGroup from "./components/radio-group";
import TextField from "./components/text-field";
// eslint-disable-next-line import/no-deprecated
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ohh interesting, I recently added the @deprecated tag for LabeledTextField, but didn't get any lint errors in the PR checks 🤔

* NOTE: The prefix uses the following structure:
* --wb-<namespace>-<token_category>-
*
* - namespace: The design token namespace (`s` for semantic tokens).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Related to namespaces, would p be the other namespace for primitive tokens? Would primitive tokens also use css variables?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep! p stands for primitives. I'm still not sure if primitive tokens will be exported as CSS variables. I'm trying to be very explicit on what's meant to be themed and want to encourage folks to use semantic variables, so I'm not sure if exposing primitives as CSS vars would give the right signal. Would love to hear your thoughts about this!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, I don't think we need to expose the primitive colors as css vars (for now at least!)

package.json Outdated
@@ -25,7 +26,7 @@
"lint:ci": "eslint --ext .ts --ext .js --ext .tsx --ext .jsx",
"publish:check": "pnpm run lint:pkg-json && ./utils/publish/pre-publish-check-ci.ts",
"publish:ci": "pnpm run publish:check && git diff --stat --exit-code HEAD && pnpm build && pnpm build:types && pnpm changeset publish",
"start": "pnpm start:storybook",
"start": "pnpm dev & pnpm start:storybook",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about moving pnpm dev to the start:storybook script instead so it'll always run with storybook? (I ask because I checked out the branch locally to poke around and pnpm start:storybook wasn't working because it couldn't find the new css file!)

title: "Khanmigo",
},
{
value: "classroom",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Going forward, how would we like to account for theming in our stories for Chromatic tests? Include all themes in the All Variants stories? Or all types of stories? Curious to hear what others think!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could see two ways for tackling this:

  1. I know that Chromatic supports modes for these cases. In theory, this looks like a great take on this! Unfortunately, this would mean that the # of snapshots would be multiplied and that's a big reason why I'd be hesitant to use this approach.

  2. Including themes in the All variants stories. Which is what we have been using for the existing khanmigo theme. The story/snapshot would be twice the size, but at least we would have the same number of snapshots being sent to Chromatic.

My vote would be #2 for trying to keep the chromatic snapshots number down.

What do you all think? Is there another approach?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, #2 makes sense to keep snapshots number down! I think as long as the All Variants story cover the main use cases, we'll get good coverage of the same features across different themes!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants