Skip to content

Commit da2b962

Browse files
authored
This is a feature-branch pull-request from feature/css-vars to main (#2541)
## Summary: - [WB-1816] Rework `semanticColor` tokens to be exported as CSS variables (#2481) - [WB-1644.1] Modify ThemeSwitcher to use a data-wb-theme attribute for theming (#2557) - [WB-1644.2] Convert border and sizing tokens to CSS variables (#2561) - [🔥AUDIT🔥] Tokens: Export TS types correctly in package.json so consumers can use them correctly (#2564) [WB-1816]: https://khanacademy.atlassian.net/browse/WB-1816?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ Issue: "none" ## Test plan: Verify that all the components are now using CSS variables when `sizing`, `border` and `semanticColor` tokens are used. <img width="2910" alt="Screenshot 2025-04-17 at 3 56 39 PM" src="https://github.com/user-attachments/assets/65602a0e-073c-4109-9b28-cfd7d11f4752" /> Author: jandrade Reviewers: beaesguerra Required Reviewers: Approved By: beaesguerra Checks: ✅ 34 checks were successful, ⏭️ 8 checks have been skipped, ⏹️ 15 checks were cancelled Pull Request URL: #2541
2 parents 3ba0641 + 6018552 commit da2b962

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+614
-50
lines changed

.changeset/clean-ravens-happen.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@khanacademy/wonder-blocks-tokens": minor
3+
---
4+
5+
Export `border` and `sizing` as CSS variables. Map JS tokens to css var counterparts

.changeset/gold-peaches-guess.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@khanacademy/wonder-blocks-form": patch
3+
---
4+
5+
TextField: Fix outline-width on focus

.changeset/heavy-islands-tan.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@khanacademy/wonder-blocks-pill": patch
3+
---
4+
5+
Use color-mix() for active background colors

.changeset/odd-turkeys-train.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@khanacademy/wonder-blocks-tokens": patch
3+
---
4+
5+
Export TS types correctly in package.json so consumers can use them correctly

.changeset/silver-pumas-marry.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@khanacademy/wonder-blocks-tokens": major
3+
---
4+
5+
Replaces `semanticColor` values with references to css variables. Creates a build step to convert semanticColor tokens to css variables that can be consumed via CSS imports.

.changeset/slimy-meals-tickle.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@khanacademy/wonder-blocks-theming": minor
3+
---
4+
5+
Export THEME_DATA_ATTRIBUTE to centralize how the theme is set in WB

.changeset/ten-gorillas-invent.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@khanacademy/wonder-blocks-icon-button": patch
3+
---
4+
5+
Simplify IconButton pseudo-classes

.changeset/tidy-humans-fail.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@khanacademy/wonder-blocks-theming": minor
3+
---
4+
5+
Add ThemeSwitcher component to change themes using CSS variables

.changeset/yellow-lies-sort.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@khanacademy/wonder-blocks-tokens": minor
3+
---
4+
5+
Update build script to use the new data-wb-theme attribute to generate styles.css. Fixes vite.config to allow importing css files in Storybbok

.storybook/main.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ const config: StorybookConfig = {
2828
disableTelemetry: true,
2929
},
3030
framework: "@storybook/react-vite",
31-
async viteFinal(config, options) {
31+
async viteFinal(config) {
3232
// Merge custom configuration into the default config
3333
const {mergeConfig} = await import("vite");
3434

.storybook/preview.tsx

+15-5
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
import * as React from "react";
22
import wonderBlocksTheme from "./wonder-blocks-theme";
33
import {Decorator} from "@storybook/react";
4+
import {RenderStateRoot} from "@khanacademy/wonder-blocks-core";
45
import {semanticColor} from "@khanacademy/wonder-blocks-tokens";
56
import {initAnnouncer} from "@khanacademy/wonder-blocks-announcer";
67
import Link from "@khanacademy/wonder-blocks-link";
7-
import {ThemeSwitcherContext} from "@khanacademy/wonder-blocks-theming";
8-
import {RenderStateRoot} from "../packages/wonder-blocks-core/src";
8+
import {
9+
ThemeSwitcherContext,
10+
ThemeSwitcher,
11+
} from "@khanacademy/wonder-blocks-theming";
912
import {Preview} from "@storybook/react";
1013

14+
// Import the Wonder Blocks CSS variables
15+
import "@khanacademy/wonder-blocks-tokens/styles.css";
16+
1117
/**
1218
* WB Official breakpoints
1319
* @see https://khanacademy.atlassian.net/wiki/spaces/WB/pages/2099970518/Layout+Breakpoints
@@ -111,14 +117,18 @@ const withThemeSwitcher: Decorator = (
111117
return (
112118
<RenderStateRoot>
113119
<ThemeSwitcherContext.Provider value={theme}>
114-
<Story />
120+
<ThemeSwitcher theme={theme}>
121+
<Story />
122+
</ThemeSwitcher>
115123
</ThemeSwitcherContext.Provider>
116124
</RenderStateRoot>
117125
);
118126
}
119127
return (
120128
<ThemeSwitcherContext.Provider value={theme}>
121-
<Story />
129+
<ThemeSwitcher theme={theme}>
130+
<Story />
131+
</ThemeSwitcher>
122132
</ThemeSwitcherContext.Provider>
123133
);
124134
};
@@ -202,7 +212,7 @@ const preview: Preview = {
202212
},
203213
{
204214
value: "khanmigo",
205-
icon: "circle",
215+
icon: "comment",
206216
title: "Khanmigo",
207217
},
208218
],

__docs__/components/color.tsx

+9-6
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {
1515
semanticColor,
1616
spacing,
1717
} from "@khanacademy/wonder-blocks-tokens";
18-
import {getTokenName} from "./tokens-util";
18+
import {getTokenName, maybeGetCssVariableInfo} from "./tokens-util";
1919

2020
type Variant = "primitive" | "semantic" | "compact";
2121

@@ -66,6 +66,8 @@ type ColorProps = {
6666

6767
function Color({name, value, variant}: ColorProps) {
6868
function renderInfo() {
69+
const rawValue = maybeGetCssVariableInfo(value).value;
70+
6971
if (variant === "compact") {
7072
const tokenName = name.toString().split(".");
7173
return (
@@ -83,7 +85,7 @@ function Color({name, value, variant}: ColorProps) {
8385

8486
<Footnote>
8587
Primitive:{" "}
86-
<em>{getTokenName(color, value) || value}</em>
88+
<em>{getTokenName(color, rawValue) || rawValue}</em>
8789
</Footnote>
8890
</View>
8991
);
@@ -114,10 +116,11 @@ function Color({name, value, variant}: ColorProps) {
114116
{name}
115117
</LabelSmall>
116118
<Caption>
117-
Primitive: <em>{getTokenName(color, value) || value}</em>
119+
Primitive:{" "}
120+
<em>{getTokenName(color, rawValue) || rawValue}</em>
118121
</Caption>
119122
<LabelSmall>
120-
Value: <Footnote style={styles.code}>{value}</Footnote>
123+
Reference: <Footnote style={styles.code}>{value}</Footnote>
121124
</LabelSmall>
122125
</>
123126
);
@@ -160,8 +163,8 @@ type ActionColorGroupProps = {
160163
};
161164

162165
export function ActionColorGroup({category, group}: ActionColorGroupProps) {
163-
return Object.entries(category).map(([state, colorGroup]) => (
164-
<View style={styles.actionGroup}>
166+
return Object.entries(category).map(([state, colorGroup], index) => (
167+
<View style={styles.actionGroup} key={index}>
165168
<LabelLarge style={styles.capitalized}>{state}</LabelLarge>
166169
<Example style={colorGroup} />
167170
<ColorGroup

__docs__/components/token-table.tsx

+13-3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {StyleSheet} from "aphrodite";
33

44
import {addStyle} from "@khanacademy/wonder-blocks-core";
55
import {color, spacing} from "@khanacademy/wonder-blocks-tokens";
6+
import {maybeGetCssVariableInfo} from "./tokens-util";
67

78
const StyledTable = addStyle("table");
89
const StyledTr = addStyle("tr");
@@ -37,9 +38,18 @@ export default function TokenTable<T>({
3738
tokens,
3839
}: Props<T>): React.ReactElement {
3940
// Convert the tokens object into an array of objects.
40-
const data = Object.entries(tokens).map(
41-
([key, value]) => ({label: key, value}) as T,
42-
);
41+
const data = Object.entries(tokens).map(([key, value]) => {
42+
if (typeof value === "string") {
43+
// Check if the value is a CSS variable so we can transform it into
44+
// its actual value.
45+
const {name: cssVariable, value: rawValue} =
46+
maybeGetCssVariableInfo(value);
47+
48+
return {label: key, css: cssVariable, value: rawValue};
49+
}
50+
51+
return {label: key, value};
52+
});
4353

4454
return (
4555
<StyledTable style={styles.table}>

__docs__/components/tokens-util.ts

+24-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,30 @@ export function getTokenName(tokens: Record<string, string>, value: string) {
6565
}
6666

6767
if (tokens[property] === value) {
68-
return property;
68+
return maybeGetCssVariableInfo(value).name;
6969
}
7070
}
7171
}
72+
73+
/**
74+
* Given a string, evaluates if it is a CSS variable and returns its name and
75+
* value. Otherwise, it returns the string as is.
76+
*
77+
* @param value The string to evaluate.
78+
* @returns An object containing the name and value of the CSS variable or the
79+
* original string.
80+
*/
81+
export function maybeGetCssVariableInfo(value: string) {
82+
// If the value is a CSS variable, we need to transform it into its
83+
// actual value.
84+
if (value.startsWith("var(--")) {
85+
const cssVariable = value.slice(4, -1);
86+
const rawValue = getComputedStyle(
87+
document.documentElement,
88+
).getPropertyValue(cssVariable);
89+
90+
return {name: cssVariable, value: rawValue};
91+
}
92+
93+
return {name: value, value: value};
94+
}

__docs__/wonder-blocks-tabs/navigation-tabs.stories.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ export const HeaderWithNavigationTabsExample: StoryComponentType = {
299299
navigationTabsRoot: {
300300
// set margin to negative value of header vertical spacing so
301301
// that selected indicator lines up with header border
302-
margin: `-${headerVerticalSpacing} 0`,
302+
margin: `calc(${headerVerticalSpacing} * -1) 0`,
303303
},
304304
});
305305
const [currentTab, setCurrentTab] = React.useState(0);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import {Meta, Canvas} from "@storybook/blocks";
2+
import * as ThemeSwitcherStories from "./theme-switcher.stories";
3+
4+
<Meta of={ThemeSwitcherStories} />
5+
6+
# `ThemeSwitcher`
7+
8+
`ThemeSwitcher` is a component that wraps a children with a reference to the
9+
selected theme. This uses a combination of custom data attributes
10+
(`data-wb-theme`) and CSS variables to apply the theme to the children. The
11+
default CSS variables are defined in the `:root` selector, and the
12+
theme-specific CSS variables are defined in the `data-wb-theme` selector. For
13+
more info about the CSS variables, see the
14+
`@khanacademy/wonder-blocks-tokens/styles.css` file.
15+
16+
## Usage
17+
18+
```tsx
19+
import {ThemeSwitcher} from "@khanacademy/wonder-blocks-theming";
20+
21+
<ThemeSwitcher theme="default">
22+
<Button>Themed button</Button>
23+
</ThemeSwitcher>;
24+
```
25+
26+
This example demonstrates how to use the `ThemeSwitcher` component to switch
27+
between themes.
28+
29+
<Canvas of={ThemeSwitcherStories.Default} />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import {Meta, StoryObj} from "@storybook/react";
2+
import * as React from "react";
3+
4+
import {
5+
SupportedThemes,
6+
ThemeSwitcher,
7+
} from "@khanacademy/wonder-blocks-theming";
8+
import {View} from "@khanacademy/wonder-blocks-core";
9+
import {sizing} from "@khanacademy/wonder-blocks-tokens";
10+
import Button from "@khanacademy/wonder-blocks-button";
11+
12+
export default {
13+
title: "Packages / Theming / ThemeSwitcher",
14+
component: ThemeSwitcher,
15+
parameters: {
16+
// This is only for documentation purposes.
17+
chromatic: {
18+
disableSnapshot: true,
19+
},
20+
},
21+
// Hide stories in the sidebar. Only show Docs.
22+
tags: ["!dev"],
23+
} as Meta<typeof ThemeSwitcher>;
24+
25+
type Story = StoryObj<typeof ThemeSwitcher>;
26+
27+
export const Default: Story = (() => {
28+
const [theme, setTheme] = React.useState<SupportedThemes>("default");
29+
30+
const changeTheme = () => {
31+
// NOTE: Right now, this is just a placeholder, meaning that no visual
32+
// changes are expected.
33+
// TODO(WB-1896): Update this to use the `thunderblocks` theme.
34+
const newTheme = theme === "khanmigo" ? "default" : "khanmigo";
35+
setTheme(newTheme);
36+
};
37+
38+
return (
39+
<>
40+
<View style={{gap: sizing.size_160, flexDirection: "row"}}>
41+
<Button kind="secondary" onClick={changeTheme}>
42+
Switch theme
43+
</Button>
44+
<Button>Outside button (doesn&apos;t affect new theme)</Button>
45+
</View>
46+
<ThemeSwitcher theme={theme}>
47+
<p>Theming demo using: {theme}</p>
48+
<Button>Themed button</Button>
49+
</ThemeSwitcher>
50+
</>
51+
);
52+
}) as Story;

0 commit comments

Comments
 (0)