diff --git a/package.json b/package.json index 4be46dba03..0eb525c280 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 d57734d1aa..9816817eaf 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 c02543fc9d..771a0b7e57 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,60 @@ 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 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 +570,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 +633,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 +656,13 @@ 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 themedSharedStyles: ThemedStylesFn = (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 = {}; +// 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; +// // 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}`; +// const buttonType = `${color}-${kind}-${light}-${size}-${themeName}`; - if (styles[buttonType]) { - return styles[buttonType]; - } +// 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; +// 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; +// 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.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, - }, +// 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"); - } +// 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]; -}; +// 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 0000000000..849bf43b59 --- /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 0000000000..faa0eeaeed --- /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 0000000000..159057bfc9 --- /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 0402516b8d..b016a8a30d 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 1a05e01b2f..1e7f21c7a8 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"