From 44e311adea87689444363cf5f6e7227bf3fd4a00 Mon Sep 17 00:00:00 2001 From: Juan Andrade Date: Fri, 17 May 2024 16:31:15 -0400 Subject: [PATCH] Migrate Button to css-modules (hacky version) --- .../button.stories.module.css | 28 + .../wonder-blocks-button/button.stories.tsx | 56 +- package.json | 1 + packages/wonder-blocks-button/package.json | 3 +- .../src/components/button-core.tsx | 814 +++++++++++------- .../src/components/button.module.css | 535 ++++++++++++ .../src/themes/default.module.css | 105 +++ .../src/themes/khanmigo.module.css | 49 ++ .../src/themes/themed-button.tsx | 22 +- yarn.lock | 12 + 10 files changed, 1261 insertions(+), 364 deletions(-) create mode 100644 __docs__/wonder-blocks-button/button.stories.module.css create mode 100644 packages/wonder-blocks-button/src/components/button.module.css create mode 100644 packages/wonder-blocks-button/src/themes/default.module.css create mode 100644 packages/wonder-blocks-button/src/themes/khanmigo.module.css diff --git a/__docs__/wonder-blocks-button/button.stories.module.css b/__docs__/wonder-blocks-button/button.stories.module.css new file mode 100644 index 000000000..86f132c9f --- /dev/null +++ b/__docs__/wonder-blocks-button/button.stories.module.css @@ -0,0 +1,28 @@ +.row { + flex-direction: row; + align-items: center; + margin-bottom: var(--wb-spacing-xSmall_8); +} + +.button { + margin-right: var(--wb-spacing-xSmall_8); +} + +.truncatedButton { + max-width: 200px; + margin-bottom: var(--wb-spacing-medium_16); +} + +.fillSpace { + min-width: 140px; +} + +.example { + background: var(--wb-color-offWhite); + padding: var(--wb-spacing-medium_16); +} + +.label { + margin-top: var(--wb-spacing-large_24); + margin-bottom: var(--wb-spacing-xSmall_8); +} \ No newline at end of file diff --git a/__docs__/wonder-blocks-button/button.stories.tsx b/__docs__/wonder-blocks-button/button.stories.tsx index 227f9a435..11e092c79 100644 --- a/__docs__/wonder-blocks-button/button.stories.tsx +++ b/__docs__/wonder-blocks-button/button.stories.tsx @@ -1,11 +1,11 @@ import * as React from "react"; -import {StyleSheet} from "aphrodite"; +// import {StyleSheet} from "aphrodite"; import type {Meta, StoryObj} from "@storybook/react"; import {expect, fireEvent, userEvent, within} from "@storybook/test"; import {MemoryRouter, Route, Switch} from "react-router-dom"; -import type {StyleDeclaration} from "aphrodite"; +// import type {StyleDeclaration} from "aphrodite"; import pencilSimple from "@phosphor-icons/core/regular/pencil-simple.svg"; import pencilSimpleBold from "@phosphor-icons/core/bold/pencil-simple-bold.svg"; @@ -27,6 +27,8 @@ import ComponentInfo from "../../.storybook/components/component-info"; import ButtonArgTypes from "./button.argtypes"; import {ThemeSwitcherContext} from "@khanacademy/wonder-blocks-theming"; +import styles from "./button.stories.module.css"; + /** * Reusable button component. * @@ -132,31 +134,31 @@ export const Tertiary: StoryComponentType = { }, }; -export const styles: StyleDeclaration = StyleSheet.create({ - row: { - flexDirection: "row", - alignItems: "center", - marginBottom: spacing.xSmall_8, - }, - button: { - marginRight: spacing.xSmall_8, - }, - truncatedButton: { - maxWidth: 200, - marginBottom: spacing.medium_16, - }, - fillSpace: { - minWidth: 140, - }, - example: { - background: color.offWhite, - padding: spacing.medium_16, - }, - label: { - marginTop: spacing.large_24, - marginBottom: spacing.xSmall_8, - }, -}); +// export const styles: StyleDeclaration = StyleSheet.create({ +// row: { +// flexDirection: "row", +// alignItems: "center", +// marginBottom: spacing.xSmall_8, +// }, +// button: { +// marginRight: spacing.xSmall_8, +// }, +// truncatedButton: { +// maxWidth: 200, +// marginBottom: spacing.medium_16, +// }, +// fillSpace: { +// minWidth: 140, +// }, +// example: { +// background: color.offWhite, +// padding: spacing.medium_16, +// }, +// label: { +// marginTop: spacing.large_24, +// marginBottom: spacing.xSmall_8, +// }, +// }); export const Variants: StoryComponentType = () => ( diff --git a/package.json b/package.json index 4be46dba0..0eb525c28 100644 --- a/package.json +++ b/package.json @@ -83,6 +83,7 @@ "babel-loader": "^8.2.3", "babel-plugin-dynamic-import-node": "^2.3.3", "chromatic": "^6.0.4", + "class-variance-authority": "^0.7.0", "eslint": "^8.40.0", "eslint-config-prettier": "^8.8.0", "eslint-import-resolver-typescript": "^3.5.5", diff --git a/packages/wonder-blocks-button/package.json b/packages/wonder-blocks-button/package.json index d57734d1a..9816817ea 100644 --- a/packages/wonder-blocks-button/package.json +++ b/packages/wonder-blocks-button/package.json @@ -31,6 +31,7 @@ "react-router-dom": "5.3.0" }, "devDependencies": { - "@khanacademy/wb-dev-build-settings": "^1.0.0" + "@khanacademy/wb-dev-build-settings": "^1.0.0", + "class-variance-authority": "^0.7.0" } } \ No newline at end of file diff --git a/packages/wonder-blocks-button/src/components/button-core.tsx b/packages/wonder-blocks-button/src/components/button-core.tsx index c02543fc9..ef2e14ceb 100644 --- a/packages/wonder-blocks-button/src/components/button-core.tsx +++ b/packages/wonder-blocks-button/src/components/button-core.tsx @@ -1,31 +1,472 @@ import * as React from "react"; -import {CSSProperties, StyleSheet} from "aphrodite"; +// import {CSSProperties, StyleSheet} from "aphrodite"; +import {cva} from "class-variance-authority"; import {Link} from "react-router-dom"; import {__RouterContext} from "react-router"; import {LabelLarge, LabelSmall} from "@khanacademy/wonder-blocks-typography"; -import {addStyle, View} from "@khanacademy/wonder-blocks-core"; +import {View} from "@khanacademy/wonder-blocks-core"; import {CircularSpinner} from "@khanacademy/wonder-blocks-progress-spinner"; import {isClientSideUrl} from "@khanacademy/wonder-blocks-clickable"; import { - ThemedStylesFn, + // ThemedStylesFn, useScopedTheme, - useStyles, + // useStyles, } from "@khanacademy/wonder-blocks-theming"; import type { ChildrenProps, ClickableState, } from "@khanacademy/wonder-blocks-clickable"; +import sharedStyles from "./button.module.css"; import type {SharedProps} from "./button"; -import {ButtonThemeContext, ButtonThemeContract} from "../themes/themed-button"; +import {ButtonThemeContext} from "../themes/themed-button"; import {ButtonIcon} from "./button-icon"; type Props = SharedProps & ChildrenProps & ClickableState; -const StyledAnchor = addStyle("a"); -const StyledButton = addStyle("button"); -const StyledLink = addStyle(Link); +// const StyledAnchor = addStyle("a"); +// const StyledButton = addStyle("button"); +// const StyledLink = addStyle(Link); +const StyledAnchor = "a"; +const StyledButton = "button"; +// const StyledLink = Link; + +const buttonStyles = cva(sharedStyles.default, { + variants: { + kind: { + primary: [sharedStyles.primary, sharedStyles.primaryDefault], + secondary: [sharedStyles.secondary, sharedStyles.secondaryDefault], + tertiary: [sharedStyles.tertiary, sharedStyles.tertiaryDefault], + }, + size: { + small: sharedStyles.small, + medium: sharedStyles.medium, + large: sharedStyles.large, + }, + color: { + default: {}, + destructive: {}, + }, + light: { + true: {}, + false: {}, + }, + state: { + default: {}, + hover: {}, + focus: {}, + active: {}, + }, + disabled: { + true: sharedStyles.disabled, + false: {}, + }, + }, + compoundVariants: [ + // primary + { + kind: "primary", + color: "default", + light: false, + className: sharedStyles.primaryDefault, + }, + { + kind: "primary", + color: "destructive", + light: false, + className: sharedStyles.primaryDestructive, + }, + { + kind: "primary", + color: "default", + light: true, + className: sharedStyles.primaryDefaultLight, + }, + { + kind: "primary", + color: "destructive", + light: true, + className: sharedStyles.primaryDestructiveLight, + }, + { + kind: "primary", + size: "large", + className: sharedStyles.primarySizeLarge, + }, + // Focus + { + kind: "primary", + color: "default", + light: false, + state: "focus", + className: sharedStyles.primaryDefaultFocus, + }, + { + kind: "primary", + color: "default", + light: true, + state: "focus", + className: sharedStyles.primaryDefaultLightFocus, + }, + { + kind: "primary", + color: "destructive", + light: false, + state: "focus", + className: sharedStyles.primaryDestructiveFocus, + }, + { + kind: "primary", + color: "destructive", + light: true, + state: "focus", + className: sharedStyles.primaryDestructiveLightFocus, + }, + // primary:active + { + kind: "primary", + color: "default", + light: false, + state: "active", + className: sharedStyles.primaryDefaultActive, + }, + { + kind: "primary", + color: "default", + light: true, + state: "active", + className: sharedStyles.primaryDefaultLightActive, + }, + { + kind: "primary", + color: "destructive", + light: false, + state: "active", + className: sharedStyles.primaryDestructiveActive, + }, + { + kind: "primary", + color: "destructive", + light: true, + state: "active", + className: sharedStyles.primaryDestructiveLightActive, + }, + // primary:disabled + { + kind: "primary", + color: "default", + light: false, + disabled: true, + className: sharedStyles.primaryDefaultDisabled, + }, + { + kind: "primary", + color: "default", + light: true, + disabled: true, + className: sharedStyles.primaryDefaultLightDisabled, + }, + { + kind: "primary", + color: "destructive", + light: false, + disabled: true, + className: sharedStyles.primaryDestructiveDisabled, + }, + { + kind: "primary", + color: "destructive", + light: true, + disabled: true, + className: sharedStyles.primaryDestructiveLightDisabled, + }, + // secondary + { + kind: "secondary", + color: "default", + light: false, + state: "default", + className: sharedStyles.secondaryDefault, + }, + { + kind: "secondary", + color: "destructive", + light: false, + state: "default", + className: sharedStyles.secondaryDestructive, + }, + { + kind: "secondary", + color: ["default", "destructive"], + light: true, + state: "default", + className: sharedStyles.secondaryLight, + }, + // secondary:focus + { + kind: "secondary", + color: ["default", "destructive"], + light: false, + state: "focus", + className: sharedStyles.secondaryFocus, + }, + { + kind: "secondary", + color: "default", + light: false, + state: "focus", + className: [ + sharedStyles.secondaryDefault, + sharedStyles.secondaryDefaultFocus, + ], + }, + { + kind: "secondary", + color: "destructive", + light: false, + state: "focus", + className: [ + sharedStyles.secondaryDestructive, + sharedStyles.secondaryDestructiveFocus, + ], + }, + { + kind: "secondary", + color: ["default", "destructive"], + light: true, + state: "focus", + className: sharedStyles.secondaryLightFocus, + }, + // secondary:active + { + kind: "secondary", + color: ["default", "destructive"], + light: false, + state: "active", + className: sharedStyles.secondaryActive, + }, + { + kind: "secondary", + color: "default", + light: false, + state: "active", + className: sharedStyles.secondaryDefaultActive, + }, + { + kind: "secondary", + color: "destructive", + light: false, + state: "active", + className: sharedStyles.secondaryDestructiveActive, + }, + { + kind: "secondary", + color: "default", + light: true, + state: "active", + className: sharedStyles.secondaryDefaultLightActive, + }, + { + kind: "secondary", + color: "destructive", + light: true, + state: "active", + className: sharedStyles.secondaryDestructiveLightActive, + }, + // secondary:disabled + { + kind: "secondary", + color: ["default", "destructive"], + light: false, + disabled: true, + className: sharedStyles.secondaryDisabled, + }, + { + kind: "secondary", + color: "default", + light: true, + disabled: true, + className: sharedStyles.secondaryDefaultLightDisabled, + }, + { + kind: "secondary", + color: "destructive", + light: true, + disabled: true, + className: sharedStyles.secondaryDestructiveLightDisabled, + }, + { + kind: "secondary", + color: ["default", "destructive"], + light: true, + disabled: true, + className: sharedStyles.secondaryLightDisabled, + }, + // tertiary + { + kind: "tertiary", + color: "default", + light: false, + state: "default", + className: sharedStyles.tertiaryDefault, + }, + { + kind: "tertiary", + color: "destructive", + light: false, + state: "default", + className: sharedStyles.tertiaryDestructive, + }, + { + kind: "tertiary", + color: ["default", "destructive"], + light: true, + state: "default", + className: sharedStyles.tertiaryLight, + }, + // tertiary:hover + { + kind: "tertiary", + color: ["default", "destructive"], + light: false, + state: "hover", + className: sharedStyles.tertiaryHover, + }, + { + kind: "tertiary", + color: "default", + light: false, + state: "hover", + className: sharedStyles.tertiaryDefaultHover, + }, + { + kind: "tertiary", + color: "destructive", + light: false, + state: "hover", + className: sharedStyles.tertiaryDestructiveHover, + }, + { + kind: "tertiary", + color: ["default", "destructive"], + light: true, + state: "hover", + className: sharedStyles.tertiaryLightHover, + }, + // tertiary:focus + { + kind: "tertiary", + color: ["default", "destructive"], + light: false, + state: "focus", + className: sharedStyles.tertiaryFocus, + }, + { + kind: "tertiary", + color: "default", + light: false, + state: "focus", + className: sharedStyles.tertiaryDefaultFocus, + }, + { + kind: "tertiary", + color: "destructive", + light: false, + state: "focus", + className: sharedStyles.tertiaryDestructiveFocus, + }, + { + kind: "tertiary", + color: ["default", "destructive"], + light: true, + state: "focus", + className: sharedStyles.tertiaryLightFocus, + }, + // tertiary:active + { + kind: "tertiary", + color: ["default", "destructive"], + light: false, + state: "active", + className: sharedStyles.tertiaryActive, + }, + { + kind: "tertiary", + color: "default", + light: false, + state: "active", + className: sharedStyles.tertiaryDefaultActive, + }, + { + kind: "tertiary", + color: "destructive", + light: false, + state: "active", + className: sharedStyles.tertiaryDestructiveActive, + }, + { + kind: "tertiary", + color: "default", + light: true, + state: "active", + className: sharedStyles.tertiaryDefaultLightActive, + }, + { + kind: "tertiary", + color: "destructive", + light: true, + state: "active", + className: sharedStyles.tertiaryDestructiveLightActive, + }, + // tertiary:disabled + { + kind: "tertiary", + color: ["default", "destructive"], + light: false, + disabled: true, + className: sharedStyles.tertiaryDisabled, + }, + { + kind: "tertiary", + color: "default", + light: true, + disabled: true, + className: sharedStyles.tertiaryDefaultLightDisabled, + }, + { + kind: "tertiary", + color: "destructive", + light: true, + disabled: true, + className: sharedStyles.tertiaryDestructiveLightDisabled, + }, + { + kind: "tertiary", + color: ["default", "destructive"], + light: false, + disabled: true, + state: "focus", + className: sharedStyles.tertiaryDisabledFocus, + }, + { + kind: "tertiary", + color: ["default", "destructive"], + light: true, + disabled: true, + state: "focus", + className: sharedStyles.tertiaryLightDisabledFocus, + }, + ], + defaultVariants: { + kind: "primary", + size: "medium", + color: "default", + light: false, + state: "default", + disabled: false, + }, +}); const ButtonCore: React.ForwardRefExoticComponent< Props & @@ -34,8 +475,7 @@ const ButtonCore: React.ForwardRefExoticComponent< typeof Link | HTMLButtonElement | HTMLAnchorElement, Props >(function ButtonCore(props: Props, ref) { - const {theme, themeName} = useScopedTheme(ButtonThemeContext); - const sharedStyles = useStyles(themedSharedStyles, theme); + const {theme} = useScopedTheme(ButtonThemeContext); const renderInner = (router: any): React.ReactNode => { const { @@ -62,45 +502,37 @@ const ButtonCore: React.ForwardRefExoticComponent< ...restProps } = props; - const buttonStyles = _generateStyles( - color, - kind, - light, - size, - theme, - themeName, - ); - const disabled = spinner || disabledProp; - const defaultStyle = [ - sharedStyles.shared, - disabled && sharedStyles.disabled, - startIcon && sharedStyles.withStartIcon, - endIcon && sharedStyles.withEndIcon, - buttonStyles.default, - disabled && buttonStyles.disabled, - // apply focus effect only to default and secondary buttons - kind !== "tertiary" && - !disabled && - (pressed - ? buttonStyles.active - : (hovered || focused) && buttonStyles.focus), - kind === "tertiary" && - !pressed && - focused && [ - buttonStyles.focus, - disabled && buttonStyles.disabledFocus, - ], - size === "small" && sharedStyles.small, - size === "large" && sharedStyles.large, - ]; + const buttonState = pressed + ? "active" + : hovered || focused + ? "focus" + : "default"; + + const defaultStyle = buttonStyles({ + kind, + size, + color, + light, + state: buttonState, + disabled, + // extra styles + className: [ + theme, + sharedStyles.shared, + startIcon && sharedStyles.withStartIcon, + endIcon && sharedStyles.withEndIcon, + style, + ], + }); const commonProps = { "data-testid": testId, id: id, role: "button", - style: [defaultStyle, style], + // style: [defaultStyle, style], + className: defaultStyle, ...restProps, } as const; @@ -115,11 +547,15 @@ const ButtonCore: React.ForwardRefExoticComponent< spinner && sharedStyles.hiddenText, kind === "tertiary" && sharedStyles.textWithFocus, // apply press/hover effects on the label + // TODO(juan): figure out this part kind === "tertiary" && !disabled && (pressed - ? [buttonStyles.hover, buttonStyles.active] - : hovered && buttonStyles.hover), + ? [ + sharedStyles.tertiaryHover, + sharedStyles.tertiaryActive, + ] + : hovered && sharedStyles.tertiaryHover), ]} testId={testId ? `${testId}-inner-label` : undefined} > @@ -174,7 +610,8 @@ const ButtonCore: React.ForwardRefExoticComponent< testId ? `${testId}-end-icon-wrapper` : undefined } style={[ - styles.endIcon, + // styles.endIcon, + sharedStyles.endIcon, sharedStyles.iconWrapper, sharedStyles.endIconWrapper, kind === "tertiary" && @@ -196,13 +633,14 @@ const ButtonCore: React.ForwardRefExoticComponent< if (href && !disabled) { return router && !skipClientNav && isClientSideUrl(href) ? ( - } > {contents} - + ) : ( = (theme) => ({ - shared: { - position: "relative", - display: "inline-flex", - alignItems: "center", - justifyContent: "center", - height: theme.size.height.medium, - paddingTop: 0, - paddingBottom: 0, - paddingLeft: theme.padding.large, - paddingRight: theme.padding.large, - border: "none", - borderRadius: theme.border.radius.default, - cursor: "pointer", - outline: "none", - textDecoration: "none", - boxSizing: "border-box", - // This removes the 300ms click delay on mobile browsers by indicating that - // "double-tap to zoom" shouldn't be used on this element. - touchAction: "manipulation", - userSelect: "none", - ":focus": { - // Mobile: Removes a blue highlight style shown when the user clicks a button - WebkitTapHighlightColor: "rgba(0,0,0,0)", - }, - }, - disabled: { - cursor: "auto", - }, - small: { - borderRadius: theme.border.radius.small, - height: theme.size.height.small, - }, - large: { - borderRadius: theme.border.radius.large, - height: theme.size.height.large, - }, - text: { - alignItems: "center", - fontWeight: theme.font.weight.default, - whiteSpace: "nowrap", - overflow: "hidden", - textOverflow: "ellipsis", - display: "inline-block", // allows the button text to truncate - pointerEvents: "none", // fix Safari bug where the browser was eating mouse events - }, - largeText: { - fontSize: theme.font.size.large, - lineHeight: `${theme.font.lineHeight.large}px`, - }, - textWithFocus: { - position: "relative", // allows the tertiary button border to use the label width - }, - hiddenText: { - visibility: "hidden", - }, - spinner: { - position: "absolute", - }, - startIcon: { - marginRight: theme.padding.small, - marginLeft: theme.margin.icon.offset, - }, - tertiaryStartIcon: { - // Undo the negative padding from startIcon since tertiary - // buttons don't have extra padding. - marginLeft: 0, - }, - endIcon: { - marginLeft: theme.padding.small, - }, - iconWrapper: { - borderRadius: theme.border.radius.icon, - padding: theme.padding.xsmall, - // View has a default minWidth of 0, which causes the label text - // to encroach on the icon when it needs to truncate. We can fix - // this by setting the minWidth to auto. - minWidth: "auto", - }, - iconWrapperSecondaryHovered: { - backgroundColor: theme.color.bg.icon.secondaryHover, - color: theme.color.text.icon.secondaryHover, - }, - endIconWrapper: { - marginLeft: theme.padding.small, - marginRight: theme.margin.icon.offset, - }, - endIconWrapperTertiary: { - marginRight: 0, - }, -}); - -const styles: Record = {}; - -// export for testing only -export const _generateStyles = ( - buttonColor = "default", - kind: "primary" | "secondary" | "tertiary", - light: boolean, - size: "large" | "medium" | "small", - theme: ButtonThemeContract, - themeName: string, -) => { - const color: string = - buttonColor === "destructive" - ? theme.color.bg.critical.default - : theme.color.bg.action.default; - - const buttonType = `${color}-${kind}-${light}-${size}-${themeName}`; - - if (styles[buttonType]) { - return styles[buttonType]; - } - - const fadedColor = - buttonColor === "destructive" - ? theme.color.bg.critical.inverse - : theme.color.bg.action.inverse; - const activeColor = - buttonColor === "destructive" - ? theme.color.bg.critical.active - : theme.color.bg.action.active; - const padding = - size === "large" ? theme.padding.xLarge : theme.padding.large; - - let newStyles: Record = {}; - if (kind === "primary") { - const boxShadowInnerColor: string = light - ? theme.color.bg.primary.inverse - : theme.color.bg.primary.default; - - newStyles = { - default: { - background: light ? theme.color.bg.primary.default : color, - color: light ? color : theme.color.text.inverse, - paddingLeft: padding, - paddingRight: padding, - }, - focus: { - // This assumes a background of white for the regular button and - // a background of darkBlue for the light version. The inner - // box shadow/ring is also small enough for a slight variation - // in the background color not to matter too much. - boxShadow: `0 0 0 1px ${boxShadowInnerColor}, 0 0 0 3px ${ - light ? theme.color.bg.primary.default : color - }`, - }, - active: { - boxShadow: `0 0 0 1px ${boxShadowInnerColor}, 0 0 0 3px ${ - light ? fadedColor : activeColor - }`, - background: light ? fadedColor : activeColor, - color: light ? activeColor : fadedColor, - }, - disabled: { - background: light - ? fadedColor - : theme.color.bg.primary.disabled, - color: light ? color : theme.color.text.primary.disabled, - cursor: "default", - ":focus": { - boxShadow: `0 0 0 1px ${ - light - ? theme.color.bg.primary.disabled - : theme.color.bg.primary.default - }, 0 0 0 3px ${ - light ? fadedColor : theme.color.bg.primary.disabled - }`, - }, - }, - }; - } else if (kind === "secondary") { - const secondaryBorderColor = - buttonColor === "destructive" - ? theme.color.border.secondary.critical - : theme.color.border.secondary.action; - const secondaryActiveColor = - buttonColor === "destructive" - ? theme.color.bg.secondary.active.critical - : theme.color.bg.secondary.active.action; - - newStyles = { - default: { - background: light - ? theme.color.bg.secondary.inverse - : theme.color.bg.secondary.default, - color: light ? theme.color.text.inverse : color, - borderColor: light - ? theme.color.border.secondary.inverse - : secondaryBorderColor, - borderStyle: "solid", - borderWidth: theme.border.width.secondary, - paddingLeft: padding, - paddingRight: padding, - }, - focus: { - background: light - ? theme.color.bg.secondary.inverse - : theme.color.bg.secondary.focus, - borderColor: "transparent", - outlineColor: light - ? theme.color.border.primary.inverse - : color, - outlineStyle: "solid", - outlineWidth: theme.border.width.focused, - }, - - active: { - background: light ? activeColor : secondaryActiveColor, - color: light ? fadedColor : activeColor, - borderColor: "transparent", - outlineColor: light ? fadedColor : activeColor, - outlineStyle: "solid", - outlineWidth: theme.border.width.focused, - }, - disabled: { - color: light - ? theme.color.text.secondary.inverse - : theme.color.text.disabled, - outlineColor: light ? fadedColor : theme.color.border.disabled, - cursor: "default", - ":focus": { - outlineColor: light - ? theme.color.border.secondary.inverse - : theme.color.border.disabled, - outlineWidth: theme.border.width.disabled, - }, - }, - }; - } else if (kind === "tertiary") { - newStyles = { - default: { - background: "none", - color: light ? theme.color.text.inverse : color, - paddingLeft: 0, - paddingRight: 0, - }, - hover: { - ":after": { - content: "''", - position: "absolute", - height: theme.size.height.tertiaryHover, - width: "100%", - right: 0, - bottom: 0, - background: light ? theme.color.bg.tertiary.hover : color, - borderRadius: theme.border.radius.tertiary, - }, - }, - focus: { - outlineStyle: "solid", - outlineColor: light - ? theme.color.border.tertiary.inverse - : color, - outlineWidth: theme.border.width.focused, - borderRadius: theme.border.radius.default, - }, - active: { - color: light ? fadedColor : activeColor, - ":after": { - height: 1, - background: light ? fadedColor : activeColor, - }, - }, - disabled: { - color: light ? fadedColor : theme.color.text.disabled, - cursor: "default", - }, - disabledFocus: { - outlineColor: light - ? theme.color.border.tertiary.inverse - : theme.color.border.disabled, - }, - }; - } else { - throw new Error("Button kind not recognized"); - } - - styles[buttonType] = StyleSheet.create(newStyles); - return styles[buttonType]; -}; diff --git a/packages/wonder-blocks-button/src/components/button.module.css b/packages/wonder-blocks-button/src/components/button.module.css new file mode 100644 index 000000000..849bf43b5 --- /dev/null +++ b/packages/wonder-blocks-button/src/components/button.module.css @@ -0,0 +1,535 @@ +.shared { + position: relative; + display: inline-flex; + align-items: center; + justify-content: center; + padding-top: 0; + padding-bottom: 0; + padding-left: var(--button-padding-large); + padding-right: var(--button-padding-large); + border: none; + cursor: pointer; + outline: none; + text-decoration: none; + box-sizing: border-box; + /* This removes the 300ms click delay on mobile browsers by indicating that */ + /* double-tap to zoom shouldn't be used on this element. */ + touch-action: manipulation; + user-select: none; + + &:focus { + /* // Mobile: Removes a blue highlight style shown when the user clicks a button */ + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + } +} + +.disabled { + cursor: auto; +} + +/** + * sizes + */ +.medium { + border-radius: var(--button-border-radius-default); + height: var(--button-size-height-medium); +} + +.small { + border-radius: var(--button-border-radius-small); + height: var(--button-size-height-small); +} + +.large { + border-radius: var(--button-border-radius-large); + height: var(--button-size-height-large); +} + +.text { + align-items: center; + font-weight: var(--button-font-weight-default); + white-space: nowrap; + overflow: hidden; + /* allows the button text to truncate */ + display: inline-block; + /* fix Safari bug where the browser was eating mouse events */ + pointer-events: none; +} + +.largeText { + font-size: var(--button-font-size-large); + line-height: var(--button-line-height-large); +} + +.textWithFocus { + /* allows the tertiary button border to use the label width */ + position: relative; +} + +.hiddenText { + visibility: hidden; +} + +.spinner { + position: absolute; +} + +.startIcon { + margin-right: var(--button-padding-small); + margin-left: var(--button-icon-offset); +} + +.tertiaryStartIcon { + /* // Undo the negative padding from startIcon since tertiary + // buttons don't have extra padding. */ + margin-left: 0; +} + +.endIcon { + margin-left: var(--button-padding-small); +} + +.iconWrapper { + border-radius: var(--button-border-radius-icon); + padding: var(--button-padding-small); + /* // View has a default minWidth of 0; which causes the label text + // to encroach on the icon when it needs to truncate. We can fix + // this by setting the minWidth to auto. */ + min-width: auto; +} + +.iconWrapperSecondaryHovered { + background-color: var(--button-color-bg-icon-secondaryHover); + color: var(--button-color-text-icon-secondaryHover); +} + +.endIconWrapper { + margin-left: var(--button-padding-small); + margin-right: var(--button-margin-icon-offset); +} + +.endIconWrapperTertiary { + margin-right: 0; +} + +/** + * ------------------------------------------------------------ + * _generateStyles + * ------------------------------------------------------------ + */ +.default { + /* const color... */ + --button-internal-color-default: var(--button-color-bg-action-default); + --button-internal-color-destructive: var(--button-color-bg-critical-default); + /* const fadedColor */ + --button-internal-faded-color-default: var(--button-color-bg-action-inverse); + --button-internal-faded-color-destructive: var(--button-color-critical-inverse); + /* const activeColor */ + --button-internal-active-color-default: var(--button-color-bg-action-active); + --button-internal-active-color-destructive: var(--button-color-critical-active); + /* const padding */ + --button-internal-size-padding-default: var(--button-padding-large); + --button-internal-size-padding-large: var(--button-padding-xLarge); +} + +/** + * ------------------------------------------------------------ + * kind=primary + */ +.primary { + /* const boxShadowInnerColor */ + --button-internal-box-shadow-inner-color: var(--button-color-bg-primary-default); + --button-internal-box-shadow-inner-color-inverse: var(--button-color-bg-primary-inverse); +} + +/* kind=primary, color=default, light=false, state=resting */ +.primaryDefault { + background: var(--button-internal-color-default); + color: var(--button-color-text-inverse); + padding-left: var(--button-internal-size-padding-default); + padding-right: var(--button-internal-size-padding-default); +} + +.primarySizeLarge { + padding-left: var(--button-internal-size-padding-large); + padding-right: var(--button-internal-size-padding-large); +} + +/* kind=primary, color=default, light=true, state=resting */ +.primaryDefaultLight { + background: var(--button-color-bg-primary-default); + color: var(--button-internal-color-default); +} + +/* kind=primary, color=destructive, state=resting, light=false */ +.primaryDestructive { + background: var(--button-internal-color-destructive); +} + +/* kind=primary, color=destructive, state=resting, light=true */ +.primaryDestructiveLight { + /* duplicate */ + background: var(--button-color-bg-primary-default); + color: var(--button-internal-color-destructive); +} + +/* kind=primary, color=default, light=false, state=focus */ +.primaryDefaultFocus { + box-shadow: 0 0 0 1px var(--button-internal-box-shadow-inner-color), 0 0 0 3px var(--button-internal-color-default); +} + +/* kind=primary, color=default, light=true, state=focus */ +.primaryDefaultLightFocus { + box-shadow: 0 0 0 1px var(--button-internal-box-shadow-inner-color-inverse), 0 0 0 3px var(--button-internal-color-default); +} + +/* kind=primary, color=destructive, light=false, state=focus */ +.primaryDestructiveFocus { + box-shadow: 0 0 0 1px var(--button-internal-box-shadow-inner-color), 0 0 0 3px var(--button-internal-color-destructive); + +} + +/* kind=primary, color=destructive, light=true, state=focus */ +.primaryDestructiveLightFocus { + /* NOTE(juan): same as default (aka primary-focus-light)... MIGHT DELETE */ + box-shadow: 0 0 0 1px var(--button-internal-box-shadow-inner-color-inverse), 0 0 0 3px var(--button-internal-color-destructive); +} + +/* kind=primary, color=default, light=false, state=active */ +.primaryDefaultActive { + box-shadow: 0 0 0 1px var(--button-internal-box-shadow-inner-color), 0 0 0 3px var(--button-internal-active-color-default); + background: var(--button-internal-active-color-default); + color: var(--button-internal-faded-color-default); +} + +/* kind=primary, color=default, light=true, state=active */ +.primaryDefaultLightActive { + box-shadow: 0 0 0 1px var(--button-internal-box-shadow-inner-color-inverse), 0 0 0 3px var(--button-internal-faded-color-default); + background: var(--button-color-bg-action-inverse); + color: var(--button-color-bg-action-active); +} + +/* kind=primary, color=destructive, light=false, state=active */ +.primaryDestructiveActive { + box-shadow: 0 0 0 1px var(--button-internal-box-shadow-inner-color), 0 0 0 3px var(--button-internal-active-color-destructive); + background: var(--button-internal-active-color-destructive); + color: var(--button-internal-faded-color-destructive); +} + +/* kind=primary, color=destructive, light=true, state=active */ +.primaryDestructiveLightActive { + box-shadow: 0 0 0 1px var(--button-internal-box-shadow-inner-color-inverse), 0 0 0 3px var(--button-internal-faded-color-destructive); + background: var(--button-color-bg-critical-inverse); + color: var(--button-color-bg-critical-active); +} + +/* kind=primary, color=default, light=false, state=disabled */ +.primaryDefaultDisabled { + background: var(--button-color-bg-primary-disabled); + color: var(--button-color-text-primary-disabled); + cursor: default; +} + +.primaryDefaultDisabled:focus { + box-shadow: 0 0 0 1px var(--button-color-bg-primary-default), 0 0 0 3px var(--button-color-bg-primary-disabled); +} + +/* kind=primary, color=default, light=true, state=disabled */ +.primaryDefaultLightDisabled { + background: var(--button-color-bg-action-inverse); + color: var(--button-internal-color-default); +} + +.primaryDefaultLightDisabled:focus { + box-shadow: 0 0 0 1px var(--button-color-bg-primary-disabled), 0 0 0 3px var(--button-internal-faded-color-default); +} + +/* kind=primary, color=destructive, light=false, state=disabled */ +.primaryDestructiveDisabled { + /* NOTE(juan): same as default color... MIGHT DELETE */ + background: var(--button-color-bg-primary-disabled); + color: var(--button-internal-color-destructive); + cursor: default; +} + +.primaryDestructiveDisabled:focus { + /* NOTE(juan): same as default color... MIGHT DELETE */ + box-shadow: 0 0 0 1px var(--button-color-bg-primary-default), 0 0 0 3px var(--button-color-bg-primary-disabled); +} + +/* kind=primary, color=destructive, light=true, state=disabled */ +.primaryDestructiveLightDisabled { + background: var(--button-internal-faded-color-destructive); + color: var(--button-internal-color-destructive); +} + +.primaryDestructiveLightDisabled:focus { + box-shadow: 0 0 0 1px var(--button-color-bg-primary-disabled), 0 0 0 3px var(--button-internal-faded-color-destructive); +} + +/** + * ------------------------------------------------------------ + * kind=secondary + */ + +/* secondary:resting */ +.secondary { + /* custom css variables */ + --secondary-border-color-default: var(--button-color-border-secondary-action); + --secondary-border-color-destructive: var(--button-color-border-secondary-critical); + --secondary-active-color-default: var(--button-color-bg-secondary-active-action); + --secondary-active-color-destructive: var(--button-color-bg-secondary-active-critical); + + /* base styles */ + background: var(--button-color-bg-secondary-default); + border-style: solid; + border-width: var(--button-border-width-secondary); + padding-left: var(--button-internal-size-padding-default); + padding-right: var(--button-internal-size-padding-default); +} + +/* kind=secondary, color=default, light=false, state=resting */ +.secondaryDefault { + color: var(--button-internal-color-default); + border-color: var(--secondary-border-color-default); +} + +/* kind=secondary, color=destructive, light=false, state=resting */ +.secondaryDestructive { + color: var(--button-internal-color-destructive); + border-color: var(--secondary-border-color-destructive); +} + +/* kind=secondary, color=default&destructive, light=true, state=resting */ +.secondaryLight { + background: var(--button-color-bg-secondary-inverse); + color: var(--button-color-text-inverse); + border-color: var(--button-color-border-secondary-inverse); +} + +/* secondary:focus */ +.secondaryFocus { + background: var(--button-color-bg-secondary-focus); + border-color: transparent; + outline-style: solid; + outline-width: var(--button-border-width-focused); +} + +/* kind=secondary, color=default, light=false, state=focus */ +.secondaryDefaultFocus { + outline-color: var(--button-internal-color-default); +} + +/* kind=secondary, color=destructive, light=false, state=focus */ +.secondaryDestructiveFocus { + outline-color: var(--button-internal-color-destructive); +} + +/* kind=secondary, color=default&destructive, light=true, state=focus */ +.secondaryLightFocus { + background: var(--button-color-bg-secondary-inverse); + outline-color: var(--button-color-border-primary-inverse); +} + +/* secondary:active */ +.secondaryActive { + border-color: transparent; + outline-style: solid; + outline-width: var(--button-border-width-focused); +} + +/* kind=secondary, color=default, light=false, state=active */ +.secondaryDefaultActive { + background: var(--secondary-active-color-default); + color: var(--button-internal-active-color-default); + outline-color: var(--button-internal-active-color-default); +} + +/* kind=secondary, color=destructive, light=false, state=active */ +.secondaryDestructiveActive { + background: var(--secondary-active-color-destructive); + color: var(--button-internal-active-color-destructive); + outline-color: var(--button-internal-active-color-destructive); +} + +/* kind=secondary, color=default, light=true, state=active */ +.secondaryDefaultLightActive { + background: var(--button-internal-active-color-default); + color: var(--button-internal-faded-color-default); + outline-color: var(--button-internal-faded-color-default); +} + +/* kind=secondary, color=destructive, light=true, state=active */ +.secondaryDestructiveLightActive { + background: var(--button-internal-active-color-destructive); + color: var(--button-internal-faded-color-destructive); + outline-color: var(--button-internal-faded-color-destructive); +} + +/* secondary:disabled */ +.secondaryDisabled { + color: var(--button-color-text-disabled); + outline-color: var(--button-color-border-disabled); + cursor: default; +} + +.secondaryDisabled:focus { + outline-color: var(--button-color-border-disabled); + outline-width: var(--button-border-width-disabled); +} + +/* kind=secondary, color=default, light=true, state=disabled */ +.secondaryDefaultLightDisabled { + color: var(--button-color-text-secondary-inverse); + outline-color: var(--button-internal-faded-color-default); +} + +.secondaryDestructiveLightDisabled { + color: var(--button-color-text-secondary-inverse); + outline-color: var(--button-internal-faded-color-destructive); +} + +.secondaryLightDisabled:focus { + outline-color: var(--button-color-border-secondary-inverse); +} + +/** + * ------------------------------------------------------------ + * kind=tertiary + */ + +/* tertiary:resting */ +.tertiary { + background: none; + padding-left: 0; + padding-right: 0; +} + +/* kind=tertiary, color=default, light=false, state=resting */ +.tertiaryDefault { + color: var(--button-internal-color-default); +} + +/* kind=tertiary, color=destructive, light=false, state=resting */ +.tertiaryDestructive { + color: var(--button-internal-color-destructive); +} + +/* kind=tertiary, color=default&destructive, light=true, state=resting */ +.tertiaryLight { + color: var(--button-color-text-inverse); +} + +/* tertiary:hover */ +.tertiaryHover:after { + content: ''; + position: absolute; + height: var(--button-size-height-tertiaryHover); + width: 100%; + right: 0; + bottom: 0; + border-radius: var(--button-border-radius-tertiary); +} + +/* kind=tertiary, color=default, light=false, state=hover */ +.tertiaryDefaultHover:after { + background: var(--button-internal-color-default); +} + +/* kind=tertiary, color=destructive, light=false, state=hover */ +.tertiaryDestructiveHover:after { + background: var(--button-internal-color-destructive); +} + +/* kind=tertiary, color=default&destructive, light=true, state=hover */ +.tertiaryLightHover:after { + background: var(--button-color-bg-tertiary-hover); +} + +/* tertiary:focus */ +.tertiaryFocus { + outline-style: solid; + outline-width: var(--button-border-width-focused); + border-radius: var(--button-border-radius-default); +} + +/* kind=tertiary, color=default, light=false, state=focus */ +.tertiaryDefaultFocus { + outline-color: var(--button-internal-color-default); +} + +/* kind=tertiary, color=destructive, light=false, state=focus */ +.tertiaryDestructiveFocus { + outline-color: var(--button-internal-color-destructive); +} + +/* kind=tertiary, color=default&destructive, light=true, state=focus */ +.tertiaryLightFocus { + outline-color: var(--button-color-border-tertiary-inverse); +} + +/* tertiary:active */ +.tertiaryActive:after { + height: 1; +} + +/* kind=tertiary, color=default, light=false, state=active */ +.tertiaryDefaultActive { + color: var(--button-internal-active-color-default); +} + +.tertiaryDefaultActive:after { + background: var(--button-internal-active-color-default); +} + +/* kind=tertiary, color=destructive, light=false, state=active */ +.tertiaryDestructiveActive { + color: var(--button-internal-active-color-destructive); +} + +.tertiaryDestructiveActive:after { + background: var(--button-internal-active-color-destructive); +} + +/* kind=tertiary, color=default, light=true, state=active */ +.tertiaryDefaultLightActive { + color: var(--button-internal-faded-color-default); +} + +.tertiaryDefaultLightActive:after { + background: var(--button-internal-faded-color-default); +} + +/* kind=tertiary, color=destructive, light=true, state=active */ +.tertiaryDestructiveLightActive { + color: var(--button-internal-faded-color-destructive); +} + +.tertiaryDestructiveLightActive:after { + background: var(--button-internal-faded-color-destructive); +} + +/* tertiary:disabled */ +.tertiaryDisabled { + color: var(--button-color-text-disabled); + cursor: default; +} + +/* kind=tertiary, color=default, light=true, state=disabled */ +.tertiaryDefaultLightDisabled { + color: var(--button-internal-faded-color-default); +} + +/* kind=tertiary, color=destructive, light=true, state=disabled */ +.tertiaryDestructiveLightDisabled { + color: var(--button-internal-faded-color-destructive); +} + +/* tertiary:disabled+focus */ +.tertiaryDisabledFocus { + outline-color: var(--button-color-border-disabled); +} + +.tertiaryLightDisabledFocus { + outline-color: var(--button-color-border-tertiary-inverse); +} \ No newline at end of file diff --git a/packages/wonder-blocks-button/src/themes/default.module.css b/packages/wonder-blocks-button/src/themes/default.module.css new file mode 100644 index 000000000..faa0eeaee --- /dev/null +++ b/packages/wonder-blocks-button/src/themes/default.module.css @@ -0,0 +1,105 @@ +.theme { + /** + * Background + */ + /* color="default" */ + --button-color-bg-action-default: var(--wb-color-blue); + --button-color-bg-action-active: var(--wb-color-activeBlue); + --button-color-bg-action-inverse: var(--wb-color-fadedBlue); + /* color="destructive */ + --button-color-bg-critical-default: var(--wb-color-red); + --button-color-bg-critical-active: var(--wb-color-activeRed); + --button-color-bg-critical-inverse: var(--wb-color-fadedRed); + + /* kind="primary" */ + --button-color-bg-primary-default: var(--wb-color-white); + --button-color-bg-primary-disabled: var(--wb-color-offBlack32); + --button-color-bg-primary-inverse: var(--wb-color-darkBlue); + + /* kind="secondary" */ + --button-color-bg-secondary-default: none; + --button-color-bg-secondary-inverse: none; + --button-color-bg-secondary-focus: var(--wb-color-white); + --button-color-bg-secondary-active-action: var(--wb-color-fadedBlue); + --button-color-bg-secondary-active-critical: var(--wb-color-fadedRed); + + /* kind="tertiary" */ + --button-color-bg-tertiary-hover: var(--wb-color-white); + + /* icons */ + --button-color-bg-icon-secondaryHover: transparent; + + /** + * Text color + */ + /* color="default" */ + --button-color-text-disabled: var(--wb-color-offBlack32); + --button-color-text-inverse: var(--wb-color-white); + + /* kind */ + --button-color-text-primary-disabled: var(--wb-color-white64); + --button-color-text-secondary-inverse: var(--wb-color-white50); + + /* icons */ + --button-color-text-icon-secondaryHover: var(--wb-color-blue); + + /** + * Border color + */ + /* color="default" */ + --button-color-border-disabled: var(--wb-color-offBlack32); + + /* kind */ + --button-color-border-primary-inverse: var(--wb-color-white); + --button-color-border-secondary-action: var(--wb-color-offBlack50); + --button-color-border-secondary-critical: var(--wb-color-offBlack50); + --button-color-border-secondary-inverse: var(--wb-color-white50); + --button-color-border-tertiary-inverse: var(--wb-color-white50); + + /** + * Border + */ + /* width */ + --button-border-width-secondary: var(--wb-border-width-hairline); + --button-border-width-focused: var(--wb-border-width-thin); + --button-border-width-disabled: var(--wb-border-width-thin); + + /* radius */ + --button-border-radius-default: var(--wb-border-radius-medium_4); + --button-border-radius-tertiary: var(--wb-border-radius-xSmall_2); + --button-border-radius-small: var(--wb-border-radius-medium_4); + --button-border-radius-large: var(--wb-border-radius-large_6); + --button-border-radius-icon: var(--wb-border-radius-full); + + /** + * Size + */ + --button-size-height-tertiaryHover: var(--wb-spacing-xxxxSmall_2); + --button-size-height-small: var(--wb-spacing-xLarge_32); + /* NOTE: These height tokens are specific to this component. */ + --button-size-height-medium: 40px; + --button-size-height-large: 56px; + + /** + * Margin + */ + --button-margin-icon-offset: calc(var(--wb-spacing-xxxxSmall_2)*-1); + + /** + * Padding + */ + --button-padding-xsmall: var(--wb-spacing-xxxxSmall_2); + --button-padding-small: var(--wb-spacing-xxSmall6); + --button-padding-medium: var(--wb-spacing-small_12); + --button-padding-large: var(--wb-spacing-medium_16); + --button-padding-xlarge: var(--wb-spacing-xLarge_32); + + /** + * Font + */ + + /* NOTE: This token is specific to this button size. */ + --button-font-size-large: 18; + --button-font-lineHeight-large: var(--wb-lineHeight-medium); + --button-font-weight-default: var(--wb-font-weight-bold); +} \ No newline at end of file diff --git a/packages/wonder-blocks-button/src/themes/khanmigo.module.css b/packages/wonder-blocks-button/src/themes/khanmigo.module.css new file mode 100644 index 000000000..159057bfc --- /dev/null +++ b/packages/wonder-blocks-button/src/themes/khanmigo.module.css @@ -0,0 +1,49 @@ +.theme { + /** + * Background + */ + /* kind="secondary" */ + --button-color-bg-secondary-default: var(--wb-color-offWhite); + --button-color-bg-secondary-active-action: var(--wb-color-fadedBlue8); + --button-color-bg-secondary-active-critical: var(--wb-color-fadedRed8); + --button-color-bg-secondary-focus: var(--wb-color-offWhite); + + /* kind="tertiary" */ + --button-color-bg-tertiary-hover: var(--wb-color-white); + + /* icons */ + --button-color-bg-icon-secondaryHover: var(--wb-color-fadedBlue16); + + /** + * Text color + */ + --button-color-text-icon-secondaryHover: var(--wb-color-blue); + + /** + * Border color + */ + /* kind */ + --button-color-border-secondary-action: var(--wb-color-fadedBlue); + --button-color-border-secondary-critical: var(--wb-color-fadedRed); + + /** + * Border + */ + /* width */ + --button-border-width-focused: var(--wb-border-width-hairline); + + /* radius */ + --button-border-radius-default: var(--wb-border-radius-xLarge_12); + --button-border-radius-small: var(--wb-border-radius-large_6); + --button-border-radius-large: var(--wb-border-radius-xLarge_12); + + /** + * Margin + */ + --button-margin-icon-offset: calc(var(--wb-spacing-xSmall_8) * -1); + + /** + * Font + */ + --button-font-weight-default: var(--wb-font-weight-regular); +} \ No newline at end of file diff --git a/packages/wonder-blocks-button/src/themes/themed-button.tsx b/packages/wonder-blocks-button/src/themes/themed-button.tsx index 0402516b8..b016a8a30 100644 --- a/packages/wonder-blocks-button/src/themes/themed-button.tsx +++ b/packages/wonder-blocks-button/src/themes/themed-button.tsx @@ -5,21 +5,21 @@ import { ThemeSwitcherContext, } from "@khanacademy/wonder-blocks-theming"; -import defaultTheme from "./default"; -import khanmigoTheme from "./khanmigo"; +import defaultTheme from "./default.module.css"; +import khanmigoTheme from "./khanmigo.module.css"; type Props = { children: React.ReactNode; }; -export type ButtonThemeContract = typeof defaultTheme; +// export type ButtonThemeContract = typeof defaultTheme; /** * The themes available to the Button component. */ -const themes: Themes = { - default: defaultTheme, - khanmigo: khanmigoTheme, +const themes: Themes = { + default: defaultTheme.theme, + khanmigo: khanmigoTheme.theme, }; /** @@ -34,7 +34,15 @@ export const ButtonThemeContext = createThemeContext(defaultTheme); export default function ThemedButton(props: Props) { const currentTheme = React.useContext(ThemeSwitcherContext); - const theme = themes[currentTheme] || defaultTheme; + const theme = + currentTheme !== "default" + ? // HACK(juan): There's no way to merge themes, so we're just + // concatenating the class names. This case is for when the button + // is using a different theme than the default one (like + // `khanmigo`). + themes.default + " " + themes[currentTheme] + : themes.default; + return ( {props.children} diff --git a/yarn.lock b/yarn.lock index 1a05e01b2..1e7f21c7a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6107,6 +6107,13 @@ cjs-module-lexer@^1.0.0: resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40" integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA== +class-variance-authority@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/class-variance-authority/-/class-variance-authority-0.7.0.tgz#1c3134d634d80271b1837452b06d821915954522" + integrity sha512-jFI8IQw4hczaL4ALINxqLEXQbWcNjoSkloa4IaufXCJr6QawJyw7tuRysRsrE8w2p/4gGaxKIt/hX3qz/IbD1A== + dependencies: + clsx "2.0.0" + classnames@^2.2.6: version "2.3.2" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924" @@ -6183,6 +6190,11 @@ clone@^1.0.2: version "1.0.4" resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" +clsx@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.0.0.tgz#12658f3fd98fafe62075595a5c30e43d18f3d00b" + integrity sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q== + co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"