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-1634: Add supported themes list to wb-theming #2098

Merged
merged 2 commits into from
Oct 27, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
7 changes: 7 additions & 0 deletions .changeset/nine-meals-yell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@khanacademy/wonder-blocks-theming": minor
"@khanacademy/wonder-blocks-button": patch
"@khanacademy/wonder-blocks-switch": patch
---

Export supported themes list, change WB components to use these new exports
13 changes: 7 additions & 6 deletions __docs__/wonder-blocks-theming/adding-a-theme.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -107,31 +107,32 @@ to access the theme values in the component styles.
import {
createThemeContext,
ThemeSwitcherContext,
Themes,
} from "@khanacademy/wonder-blocks-theming";

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

// 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;

// Define the themes that will be available to the consumer(s).
const themes = {
const themes: Themes<ButtonThemeContract> = {
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;
const theme = themes[currentTheme] ?? themes.default;

return (
<ButtonThemeContext.Provider value={theme}>
Expand Down
13 changes: 7 additions & 6 deletions __docs__/wonder-blocks-theming/theme-examples.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as React from "react";

import {StyleSheet} from "aphrodite";
import Button from "@khanacademy/wonder-blocks-button";
import {View} from "@khanacademy/wonder-blocks-core";
import {
createThemeContext,
Expand All @@ -12,6 +13,7 @@ import {
ThemeSwitcherContext,
withScopedTheme,
WithThemeProps,
SupportedThemes,
} from "@khanacademy/wonder-blocks-theming";

const defaultTheme = {
Expand Down Expand Up @@ -41,9 +43,8 @@ const customTheme = mergeTheme(defaultTheme, {

const ThemeContext = createThemeContext(defaultTheme);

// TODO(WB-1577): Replace this with the actual WB Button component.
type ButtonProps = {
children?: React.ReactNode;
children?: string;
onClick?: (e: React.SyntheticEvent) => unknown;
};

Expand All @@ -54,7 +55,7 @@ const ThemedButton = ({
const {theme} = useScopedTheme(ThemeContext);

return (
<button
<Button
style={{
background: theme.color.bg.primary,
color: theme.color.text.light,
Expand All @@ -64,7 +65,7 @@ const ThemedButton = ({
onClick={onClick}
>
{children}
</button>
</Button>
);
};

Expand Down Expand Up @@ -197,10 +198,10 @@ function ThemedButtonContainer(props: ButtonProps) {
}

export const ThemeSwitcherContextExample = () => {
const [theme, setTheme] = React.useState("default");
const [theme, setTheme] = React.useState<SupportedThemes>("default");

const changeTheme = () => {
const newTheme = theme === "custom" ? "default" : "custom";
const newTheme = theme === "khanmigo" ? "default" : "khanmigo";
setTheme(newTheme);
};

Expand Down
7 changes: 5 additions & 2 deletions __docs__/wonder-blocks-theming/theme-switcher-context.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,13 @@ const ThemeSwitcherContext: React.Context<string>;
<ThemeSwitcherContextExample />

```ts
import {ThemeSwitcherContext} from "@khanacademy/wonder-blocks-theming";
import {
SupportedThemes,
ThemeSwitcherContext,
} from "@khanacademy/wonder-blocks-theming";

export default function App() {
const [theme, setTheme] = React.useState("default");
const [theme, setTheme] = React.useState<SupportedThemes>("default");

const changeTheme = () => {
const newTheme = theme === "brand" ? "default" : "brand";
Expand Down
9 changes: 5 additions & 4 deletions packages/wonder-blocks-button/src/themes/themed-button.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as React from "react";
import {
createThemeContext,
Themes,
ThemeSwitcherContext,
} from "@khanacademy/wonder-blocks-theming";

Expand All @@ -11,16 +12,16 @@ type Props = {
children: React.ReactNode;
};

export type ButtonThemeContract = typeof defaultTheme;

/**
* The themes available to the Button component.
*/
const themes = {
const themes: Themes<ButtonThemeContract> = {
default: defaultTheme,
khanmigo: khanmigoTheme,
};

export type ButtonThemeContract = typeof defaultTheme;

/**
* The context that provides the theme to the Button component.
* This is generally consumed via the `useScopedTheme` hook.
Expand All @@ -33,7 +34,7 @@ export const ButtonThemeContext = createThemeContext(defaultTheme);
export default function ThemedButton(props: Props) {
const currentTheme = React.useContext(ThemeSwitcherContext);

const theme = themes[currentTheme as keyof typeof themes] || defaultTheme;
const theme = themes[currentTheme] || defaultTheme;
return (
<ButtonThemeContext.Provider value={theme}>
{props.children}
Expand Down
9 changes: 5 additions & 4 deletions packages/wonder-blocks-switch/src/themes/themed-switch.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as React from "react";
import {
createThemeContext,
Themes,
ThemeSwitcherContext,
} from "@khanacademy/wonder-blocks-theming";

Expand All @@ -11,16 +12,16 @@ type Props = {
children: React.ReactNode;
};

export type SwitchThemeContract = typeof defaultTheme;

/**
* The themes available to the Switch component.
*/
const themes = {
const themes: Themes<SwitchThemeContract> = {
default: defaultTheme,
khanmigo: khanmigoTheme,
};

export type SwitchThemeContract = typeof defaultTheme;

/**
* The context that provides the theme to the Switch component.
* This is generally consumed via the `useScopedTheme` hook.
Expand All @@ -33,7 +34,7 @@ export const SwitchThemeContext = createThemeContext(defaultTheme);
export default function ThemedSwitch(props: Props) {
const currentTheme = React.useContext(ThemeSwitcherContext);

const theme = themes[currentTheme as keyof typeof themes] || defaultTheme;
const theme = themes[currentTheme] || defaultTheme;
return (
<SwitchThemeContext.Provider value={theme}>
{props.children}
Expand Down
2 changes: 1 addition & 1 deletion packages/wonder-blocks-theming/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ export {
default as withScopedTheme,
type WithThemeProps,
} from "./components/with-scoped-theme";
export {type ThemedStylesFn} from "./types";
export {type ThemedStylesFn, type SupportedThemes, type Themes} from "./types";
export {default as useStyles} from "./hooks/use-styles";
export {ThemeSwitcherContext} from "./utils/theme-switcher-context";
3 changes: 3 additions & 0 deletions packages/wonder-blocks-theming/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import {StyleDeclaration} from "aphrodite";

export type ThemedStylesFn<T> = (theme: T) => StyleDeclaration;
Copy link
Member

Choose a reason for hiding this comment

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

question: Does T need to be constrained here to object or something?


export type SupportedThemes = "default" | "khanmigo";
export type Themes<T> = Partial<Record<SupportedThemes, T>>;
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 T here an object? I wonder if we need to be specific about that with T extends Object?

Copy link
Member Author

Choose a reason for hiding this comment

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

nice catch! I'll add that, and yes, it expects an object.

Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ describe("ThemeSwitcherContext", () => {

// Act
render(
<ThemeSwitcherContext.Provider value="dark">
<ThemeSwitcherContext.Provider value="khanmigo">
<ThemeSwitcherContext.Consumer>
{(value) => <>The current theme is: {value}</>}
</ThemeSwitcherContext.Consumer>
Expand All @@ -32,6 +32,6 @@ describe("ThemeSwitcherContext", () => {
);

// Assert
expect(screen.getByText(/The current theme is: dark/)).toBeTruthy();
expect(screen.getByText(/The current theme is: khanmigo/)).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as React from "react";
import {SupportedThemes} from "../types";

/**
* A React Context that holds a reference to the selected theme. It should use
Expand All @@ -8,4 +9,5 @@ import * as React from "react";
* @param theme The theme name to be used. It should be one of the themes
* defined in the themes object. Defaults to `default`.
*/
export const ThemeSwitcherContext = React.createContext<string>("default");
export const ThemeSwitcherContext =
React.createContext<SupportedThemes>("default");
Loading