Skip to content

Commit

Permalink
WB-1573: Add storybook example for adding theming support to a compon…
Browse files Browse the repository at this point in the history
…ent (#1574) (#1986)

## Summary:

- Adds an overview section to the theming docs.
- Adds guidelines for adding theming support to a component.
- Modifies the sorting order of the storybook stories to put the overview
sections at the top.

Issue: WB-1573

## Test plan:

Verify that the new page is clear and documents how to add themes to a given component:

https://5e1bf4b385e3fb0020b7073c-jsshumrrxg.chromatic.com/?path=/docs/theming-guides-adding-a-theme--docs

Author: jandrade

Reviewers: #wonder-blocks, nishasy

Required Reviewers:

Approved By:

Checks: ✅ codecov/project, ✅ Test (ubuntu-latest, 16.x, 2/2), ✅ Check build sizes (ubuntu-latest, 16.x), ✅ Lint (ubuntu-latest, 16.x), ✅ Test (ubuntu-latest, 16.x, 1/2), ✅ gerald, ⏭  Publish npm snapshot, ✅ gerald, ✅ Prime node_modules cache for primary configuration (ubuntu-latest, 16.x), ✅ Test (ubuntu-latest, 16.x, 2/2), ✅ Test (ubuntu-latest, 16.x, 1/2), ✅ Lint (ubuntu-latest, 16.x), ✅ Check build sizes (ubuntu-latest, 16.x), ⏭  Publish npm snapshot, ✅ Prime node_modules cache for primary configuration (ubuntu-latest, 16.x), ✅ gerald, ⏭  dependabot, ✅ Chromatic - Build on review PR (push) / chromatic (ubuntu-latest, 16.x), ⏭  Chromatic - Skip on dependabot PRs (push)

Pull Request URL: #1986
  • Loading branch information
jandrade authored Sep 18, 2023
1 parent dcd0d07 commit ced86b0
Show file tree
Hide file tree
Showing 3 changed files with 231 additions and 1 deletion.
9 changes: 8 additions & 1 deletion .storybook/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,14 @@ export const parameters = {
// display the stories (or examples first), then we will display all the
// mdx pages under __docs__.
storySort: {
order: ["Components", "**/__docs__/**", "Overview"],
includeNames: true,
order: [
"Overview",
"*",
["Overview", "*"],
"**/__docs__/**/*.mdx",
"**/*.stories.@(js|jsx|ts|tsx)",
],
},
},
docs: {
Expand Down
11 changes: 11 additions & 0 deletions __docs__/wonder-blocks-theming/_overview_.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import {Meta} from "@storybook/blocks";

<Meta title="Theming/Overview" />

# Theming

`wonder-blocks-theming` provides hooks and utilities for theming other
Wonder Blocks components.

Theming in Wonder Blocks can be set per package/component level using a theme
object that defines your components's colors, spacing, fonts, and more.
212 changes: 212 additions & 0 deletions __docs__/wonder-blocks-theming/adding-a-theme.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
import {Canvas, Meta} from "@storybook/blocks";

import * as ButtonStories from "../wonder-blocks-button/button.stories";

<Meta title="Theming / Guides / Adding a theme" />

# Adding a new theme

## Introduction

In order to support theming in a particular component, we need to create a theme
and complete a series of steps before making the component themeable.

## Steps

### Define the default theme

Themes are composed by core Wonder Blocks tokens. The first step is to define
the default theme for the component. This theme should be defined in the
component folder (under `themes`).

```ts
// e.g. wonder-blocks-button/src/themes/default.js
import {tokens} from "@khanacademy/wonder-blocks-theming";

export default {
color: {
bg: {
critical: tokens.color.red,
action: tokens.color.blue,
primary: tokens.color.white,
disabled: tokens.color.offBlack32,
inverse: tokens.color.darkBlue,
},
text: {
primary: tokens.color.offBlack,
inverse: tokens.color.white,
disabled: tokens.color.offBlack32,
},
border: {
primaryInverse: tokens.color.white,
secondary: tokens.color.offBlack50,
disabled: tokens.color.offBlack32,
},
},
spacing: {
small: tokens.spacing.xSmall_8,
medium: tokens.spacing.medium_16,
large: tokens.spacing.xLarge_32,
},
border: {
radius: {
small: tokens.spacing.xxxxSmall_2,
medium: tokens.spacing.xxxSmall_4,
large: tokens.spacing.xxSmall_6,
},
},
size: {
small: tokens.spacing.xLarge_32,
medium: tokens.spacing.medium_16 + tokens.spacing.large_24,
large: tokens.spacing.xLarge_32 + tokens.spacing.large_24,
},
};
```

### Include another theme

After that, you can define new theme(s) to be supported by the component.

```ts
// e.g. wonder-blocks-button/src/themes/khanmigo.ts
import {mergeTheme, tokens} from "@khanacademy/wonder-blocks-theming";
import themeDefault from "./default";

export default mergeTheme(themeDefault, {
color: {
bg: {
primary: tokens.color.lightBlue,
action: tokens.color.darkBlue,
},
text: {
inverse: tokens.color.gold,
},
},
border: {
radius: {
medium: tokens.spacing.xSmall_8,
},
},
spacing: {
medium: tokens.spacing.xxxLarge_64,
},
size: {
medium: tokens.spacing.xxxLarge_64,
},
});
```

### Create a `ThemedComponent` wrapper

The next step is to create a wrapper that will allow theming the component. By
doing this, we can use the `useScopedTheme`. `useStyles` and `withScopedTheme`
to access the theme values in the component styles.

```tsx
// e.g. wonder-blocks-button/src/themes/themed-button.tsx
import {
createThemeContext,
ThemeSwitcherContext,
} from "@khanacademy/wonder-blocks-theming";

import defaultTheme from "./default";
import brandTheme from "./brand";

// Define the themes that will be available to the consumer(s).
const themes = {
default: defaultTheme,
brand: brandTheme,
};

// Create the theme context and assign an initial value.
export const ButtonThemeContext = createThemeContext(themes.default);

// Infer the type of the theme from the default theme.
// NOTE: Any other theme created should be compatible with this type.
export type ButtonThemeContract = typeof defaultTheme;

type Props = {
children: React.ReactNode;
};

export default function ThemedButton({children}: Props) {
const currentTheme = React.useContext(ThemeSwitcherContext);
const theme = themes[currentTheme as keyof typeof themes] ?? themes.default;

return (
<ButtonThemeContext.Provider value={theme}>
{children}
</ButtonThemeContext.Provider>
);
}
```

### Import the ThemedComponent wrapper in the component

After this, you can use use the new `ThemedButton` component to support multiple
themes in the existing component.

```tsx
// e.g. wonder-blocks-button/src/components/button.tsx
import * as React from "react";
import ThemedButton from "../themes/themed-button";
import ButtonCore from "./button-core";

export default Button(props) {
return (
<ThemedButton> // 👈 This is the only change needed in the component.
<ButtonCore {...props} />
</ThemedButton>
);
}
```

### Use the theme values in the component styles

Finally, you can use the `theme` values in the component styles.

```tsx
// e.g. wonder-blocks-button/src/components/button-core.tsx
import * as React from "react";
import {StyleSheet} from "aphrodite";

import {
ThemedStylesFn,
useStyles,
useScopedTheme,
} from "@khanacademy/wonder-blocks-theming";

import {ButtonThemeContext, ButtonThemeContract} from "../theme/themed-button";

export default function ButtonCore(props) {
const theme = useScopedTheme(ButtonThemeContext);
const sharedStyles = useStyles(wbThemeStyles, theme);

return <StyledButton style={sharedStyles.button} {...props} />;
}

const wbThemeStyles: ThemedStylesFn<ButtonThemeContract> = (theme) => ({
shared: {
position: "relative",
display: "inline-flex",
alignItems: "center",
justifyContent: "center",
height: theme.size.medium,
paddingTop: 0,
paddingBottom: 0,
paddingLeft: theme.spacing.medium,
paddingRight: theme.spacing.medium,
border: "none",
borderRadius: theme.border.radius.medium,
cursor: "pointer",
outline: "none",
textDecoration: "none",
},
...
});
```

Below you can see the result of using the `Button` component with the
`KhanmigoTheme`:

<Canvas of={ButtonStories.KhanmigoTheme} />

0 comments on commit ced86b0

Please sign in to comment.