From 3c442f72c1396084becc4972a7822732fe512dea Mon Sep 17 00:00:00 2001 From: Christian Vuerings Date: Mon, 26 Feb 2024 07:58:10 -0800 Subject: [PATCH] Button / IconButton / LinkButton: add Cambio variants --- .changeset/dull-pandas-build.md | 7 + apps/storybook/stories/Colors.tsx | 14 +- .../syntax-core/src/Button/Button.module.css | 18 ++ .../syntax-core/src/Button/Button.stories.tsx | 5 +- packages/syntax-core/src/Button/Button.tsx | 103 ++++++++-- .../src/Button/constants/classicSize.ts | 5 + .../syntax-core/src/Button/constants/color.ts | 66 ++++++ .../src/Button/constants/loadingIconSize.ts | 6 +- .../src/Button/constants/textVariant.ts | 7 +- .../src/ButtonGroup/ButtonGroup.tsx | 18 +- .../src/IconButton/IconButton.module.css | 53 ++++- .../src/IconButton/IconButton.stories.tsx | 7 +- .../syntax-core/src/IconButton/IconButton.tsx | 115 +++++++++-- .../src/LinkButton/LinkButton.stories.tsx | 6 +- .../syntax-core/src/LinkButton/LinkButton.tsx | 106 ++++++++-- .../src/ThemeProvider/ThemeProvider.test.tsx | 30 +++ .../syntax-core/src/colors/backgroundColor.ts | 28 ++- .../syntax-core/src/colors/colors.module.css | 193 ++++++++++++++++++ .../syntax-core/src/colors/foregroundColor.ts | 22 +- packages/syntax-core/src/constants.ts | 20 +- .../tokens/color/base.json | 35 ++++ 21 files changed, 780 insertions(+), 84 deletions(-) create mode 100644 .changeset/dull-pandas-build.md create mode 100644 packages/syntax-core/src/Button/constants/classicSize.ts create mode 100644 packages/syntax-core/src/Button/constants/color.ts create mode 100644 packages/syntax-core/src/ThemeProvider/ThemeProvider.test.tsx diff --git a/.changeset/dull-pandas-build.md b/.changeset/dull-pandas-build.md new file mode 100644 index 00000000..86c8ef73 --- /dev/null +++ b/.changeset/dull-pandas-build.md @@ -0,0 +1,7 @@ +--- +"@cambly/syntax-design-tokens": minor +"@cambly/syntax-core": minor +"@syntax/storybook": minor +--- + +Add ThemeProvider & styles for Button / IconButton & LinkButton diff --git a/apps/storybook/stories/Colors.tsx b/apps/storybook/stories/Colors.tsx index 886c689b..e0d764c6 100644 --- a/apps/storybook/stories/Colors.tsx +++ b/apps/storybook/stories/Colors.tsx @@ -4,7 +4,10 @@ import variables from "../../../packages/syntax-design-tokens/dist/json/variable export default function Colors() { const groupColors = Object.entries(variables).reduce( (acc, [key, value]) => { - if ( + if (key.includes("cambio")) { + // @ts-expect-error + acc["Cambio"].push({ key, value }); + } else if ( key.includes("black") || key.includes("white") || key.includes("gray") @@ -25,6 +28,7 @@ export default function Colors() { return acc; }, { + Cambio: [], Grayscale: [], Primary: [], Destructive: [], @@ -46,6 +50,14 @@ export default function Colors() { style={{ color: [ + "color-cambio-white", + "color-cambio-gray-88", + "color-cambio-gray-96", + "color-cambio-cream", + "color-cambio-pink", + "color-cambio-sky", + "color-cambio-yellow", + "color-cambio-transparent-full", "color-base-gray-10", "color-base-gray-30", "color-base-white", diff --git a/packages/syntax-core/src/Button/Button.module.css b/packages/syntax-core/src/Button/Button.module.css index c1b0fd16..19810620 100644 --- a/packages/syntax-core/src/Button/Button.module.css +++ b/packages/syntax-core/src/Button/Button.module.css @@ -90,6 +90,12 @@ border-radius: 100px; } +.xlCambio { + height: 80px; + padding: 0 40px; + border-radius: 100px; +} + .icon { color: inherit; } @@ -123,6 +129,18 @@ border: 1px solid var(--color-base-destructive-300); } +.cambioSecondaryBorder { + border: 1px solid var(--color-cambio-black); +} + +.cambioSecondaryDestructiveBorder { + border: 1px solid var(--color-cambio-destructive); +} + +.cambioSecondarySuccessBorder { + border: 1px solid var(--color-cambio-success); +} + @keyframes syntaxButtonLoadingRotate { 0% { transform-origin: 50% 50%; diff --git a/packages/syntax-core/src/Button/Button.stories.tsx b/packages/syntax-core/src/Button/Button.stories.tsx index 74f5df43..f1d156e5 100644 --- a/packages/syntax-core/src/Button/Button.stories.tsx +++ b/packages/syntax-core/src/Button/Button.stories.tsx @@ -17,17 +17,20 @@ export default { "primary", "secondary", "tertiary", + "quaternary", "destructive-primary", "destructive-secondary", "destructive-tertiary", "success", + "success-primary", + "success-secondary", "branded", "inverse", ], control: { type: "radio" }, }, size: { - options: ["sm", "md", "lg"], + options: ["sm", "md", "lg", "xl"], control: { type: "radio" }, }, disabled: { diff --git a/packages/syntax-core/src/Button/Button.tsx b/packages/syntax-core/src/Button/Button.tsx index 71df4f00..8817684f 100644 --- a/packages/syntax-core/src/Button/Button.tsx +++ b/packages/syntax-core/src/Button/Button.tsx @@ -1,19 +1,24 @@ import React, { forwardRef } from "react"; import classNames from "classnames"; - -import backgroundColor from "../colors//backgroundColor"; -import foregroundColor from "../colors/foregroundColor"; -import foregroundTypographyColor from "../colors/foregroundTypographyColor"; import { type Size } from "../constants"; import Typography from "../Typography/Typography"; import Box from "../Box/Box"; - import iconSize from "./constants/iconSize"; import textVariant from "./constants/textVariant"; import loadingIconSize from "./constants/loadingIconSize"; import styles from "./Button.module.css"; import useIsHydrated from "../useIsHydrated"; import { useTheme } from "../ThemeProvider/ThemeProvider"; +import { classicColor, cambioColor } from "./constants/color"; +import { + classicBackgroundColor, + cambioBackgroundColor, +} from "../colors/backgroundColor"; +import { + classicForegroundColor, + cambioForegroundColor, +} from "../colors/foregroundColor"; +import classicSize from "./constants/classicSize"; type ButtonProps = { /** @@ -31,25 +36,46 @@ type ButtonProps = { /** * The color of the button * + * Classic only: + * * `success-primary` + * * `success-secondary` + * * `inverse` + * + * Cambio only: + * * `quaternary` + * * `destructive-tertiary` + * * `success-primary` + * * `success-secondary` + * * @defaultValue "primary" */ color?: | "primary" | "secondary" | "tertiary" + | "quaternary" | "destructive-primary" | "destructive-secondary" | "destructive-tertiary" | "branded" | "success" + | "success-primary" + | "success-secondary" | "inverse"; /** * The size of the button * + * Classic: * * `sm`: 32px * * `md`: 40px * * `lg`: 48px * + * Cambio: + * * `sm`: 32px + * * `md`: 48px + * * `lg`: 64px + * * `xl`: 80px + * * @defaultValue "md" */ size?: (typeof Size)[number]; @@ -77,10 +103,12 @@ type ButtonProps = { fullWidth?: boolean; /** * The icon to be displayed at the start of the button. Please use a [Rounded Material Icon](https://material.io/resources/icons/?style=round) + * Note: startIcon is not supported in the Cambio theme */ startIcon?: React.ComponentType<{ className?: string }>; /** * The icon to be displayed at the end of the button. Please use a [Rounded Material Icon](https://material.io/resources/icons/?style=round) + * Note: endIcon is not supported in the Cambio theme */ endIcon?: React.ComponentType<{ className?: string }>; /** @@ -123,6 +151,16 @@ const Button = forwardRef( const isHydrated = useIsHydrated(); const { themeName } = useTheme(); + const foregroundColorClass = + themeName === "classic" + ? classicForegroundColor(classicColor(color)) + : cambioForegroundColor(cambioColor(color)); + + const backgroundColorClass = + themeName === "classic" + ? classicBackgroundColor(classicColor(color)) + : cambioBackgroundColor(cambioColor(color)); + return ( ); }, diff --git a/packages/syntax-core/src/LinkButton/LinkButton.stories.tsx b/packages/syntax-core/src/LinkButton/LinkButton.stories.tsx index 9a435ded..0b2a4ae7 100644 --- a/packages/syntax-core/src/LinkButton/LinkButton.stories.tsx +++ b/packages/syntax-core/src/LinkButton/LinkButton.stories.tsx @@ -40,16 +40,20 @@ export default { "primary", "secondary", "tertiary", + "quaternary", "destructive-primary", "destructive-secondary", "destructive-tertiary", "success", + "success-primary", + "success-secondary", "branded", + "inverse", ], control: { type: "radio" }, }, size: { - options: ["sm", "md", "lg"], + options: ["sm", "md", "lg", "xl"], control: { type: "radio" }, }, fullWidth: { diff --git a/packages/syntax-core/src/LinkButton/LinkButton.tsx b/packages/syntax-core/src/LinkButton/LinkButton.tsx index 9481438a..9037087b 100644 --- a/packages/syntax-core/src/LinkButton/LinkButton.tsx +++ b/packages/syntax-core/src/LinkButton/LinkButton.tsx @@ -1,18 +1,25 @@ import { forwardRef, type HtmlHTMLAttributes } from "react"; import classNames from "classnames"; -import backgroundColor from "../colors/backgroundColor"; -import foregroundColor from "../colors/foregroundColor"; -import foregroundTypographyColor from "../colors/foregroundTypographyColor"; import React from "react"; import { type Size } from "../constants"; import Typography from "../Typography/Typography"; - import buttonStyles from "../Button/Button.module.css"; import iconSize from "../Button/constants/iconSize"; import textVariant from "../Button/constants/textVariant"; - import styles from "./LinkButton.module.css"; +import { classicColor, cambioColor } from "../Button/constants/color"; +import { + classicBackgroundColor, + cambioBackgroundColor, +} from "../colors//backgroundColor"; +import { + classicForegroundColor, + cambioForegroundColor, +} from "../colors/foregroundColor"; +import { useTheme } from "../ThemeProvider/ThemeProvider"; +import classicSize from "../Button/constants/classicSize"; + type LinkButtonProps = { /** * Test id for the button @@ -40,24 +47,46 @@ type LinkButtonProps = { /** * The color of the button * + * Classic only: + * * `success-primary` + * * `success-secondary` + * * `inverse` + * + * Cambio only: + * * `quaternary` + * * `destructive-tertiary` + * * `success-primary` + * * `success-secondary` + * * @defaultValue "primary" */ color?: | "primary" | "secondary" | "tertiary" + | "quaternary" | "destructive-primary" | "destructive-secondary" | "destructive-tertiary" | "branded" - | "success"; + | "success" + | "success-primary" + | "success-secondary" + | "inverse"; /** * The size of the button * + * Classic: * * `sm`: 32px * * `md`: 40px * * `lg`: 48px * + * Cambio: + * * `sm`: 32px + * * `md`: 48px + * * `lg`: 64px + * * `xl`: 80px + * * @defaultValue "md" */ size?: (typeof Size)[number]; @@ -69,10 +98,12 @@ type LinkButtonProps = { fullWidth?: boolean; /** * The icon to be displayed at the start of the button. Please use a [Rounded Material Icon](https://material.io/resources/icons/?style=round) + * Note: startIcon is not supported in the Cambio theme */ startIcon?: React.ComponentType<{ className?: string }>; /** * The icon to be displayed at the end of the button. Please use a [Rounded Material Icon](https://material.io/resources/icons/?style=round) + * Note: endIcon is not supported in the Cambio theme */ endIcon?: React.ComponentType<{ className?: string }>; /** @@ -101,6 +132,18 @@ const LinkButton = forwardRef( }: LinkButtonProps, ref, ) => { + const { themeName } = useTheme(); + + const foregroundColorClass = + themeName === "classic" + ? classicForegroundColor(classicColor(color)) + : cambioForegroundColor(cambioColor(color)); + + const backgroundColorClass = + themeName === "classic" + ? classicBackgroundColor(classicColor(color)) + : cambioBackgroundColor(cambioColor(color)); + return ( ( className={classNames( styles.linkButton, buttonStyles.button, - foregroundColor(color), - backgroundColor(color), - buttonStyles[size], + foregroundColorClass, + backgroundColorClass, + themeName === "classic" + ? buttonStyles[classicSize(size)] + : buttonStyles[`${size}Cambio`], { [buttonStyles.fullWidth]: fullWidth, [styles.fitContent]: !fullWidth, - [buttonStyles.buttonGap]: size === "lg" || size === "md", - [buttonStyles.secondaryBorder]: color === "secondary", + [buttonStyles.buttonGap]: + themeName === "classic" && (size === "lg" || size === "md"), + [buttonStyles.secondaryBorder]: + themeName === "classic" && color === "secondary", [buttonStyles.secondaryDestructiveBorder]: - color === "destructive-secondary", + themeName === "classic" && color === "destructive-secondary", + [buttonStyles.cambioSecondaryBorder]: + themeName === "cambio" && color === "secondary", + [buttonStyles.cambioSecondaryDestructiveBorder]: + themeName === "cambio" && + (color === "destructive-secondary" || + color === "destructive-tertiary"), + [buttonStyles.cambioSecondarySuccessBorder]: + themeName === "cambio" && color === "success-secondary", }, )} onClick={onClick} > - {StartIcon && ( + {StartIcon && themeName === "classic" && ( )} - {text} + + {text} + - {EndIcon && ( + {EndIcon && themeName === "classic" && ( )} diff --git a/packages/syntax-core/src/ThemeProvider/ThemeProvider.test.tsx b/packages/syntax-core/src/ThemeProvider/ThemeProvider.test.tsx new file mode 100644 index 00000000..b471a7c5 --- /dev/null +++ b/packages/syntax-core/src/ThemeProvider/ThemeProvider.test.tsx @@ -0,0 +1,30 @@ +import { render, screen } from "@testing-library/react"; +import ThemeProvider, { useTheme } from "./ThemeProvider"; + +function ExampleComponent() { + const { themeName } = useTheme(); + return
{themeName}
; +} + +describe("themeProvider", () => { + it("renders children with the provided themeName - classic", () => { + const themeName = "classic"; + render( + + + , + ); + + expect(screen.getByTestId("themeName")).toHaveTextContent(themeName); + }); + it("renders children with the provided themeName - cambio", () => { + const themeName = "cambio"; + render( + + + , + ); + + expect(screen.getByTestId("themeName")).toHaveTextContent(themeName); + }); +}); diff --git a/packages/syntax-core/src/colors/backgroundColor.ts b/packages/syntax-core/src/colors/backgroundColor.ts index ee81aaaf..f960ac4d 100644 --- a/packages/syntax-core/src/colors/backgroundColor.ts +++ b/packages/syntax-core/src/colors/backgroundColor.ts @@ -1,7 +1,7 @@ -import { type Color } from "../constants"; +import { type CambioColor, type Color } from "../constants"; import styles from "./colors.module.css"; -export default function backgroundColor(color: (typeof Color)[number]): string { +export function classicBackgroundColor(color: (typeof Color)[number]): string { switch (color) { case "secondary": return styles.primary100BackgroundColor; @@ -22,3 +22,27 @@ export default function backgroundColor(color: (typeof Color)[number]): string { return styles.primary700BackgroundColor; } } + +export function cambioBackgroundColor( + color: (typeof CambioColor)[number], +): string { + switch (color) { + case "primary": + return styles.cambioBlackBackgroundColor; + case "secondary": + case "tertiary": + case "success-secondary": + case "destructive-secondary": + return styles.cambioTransparentFullBackgroundColor; + case "quaternary": + return styles.cambioTransparentGray54BackgroundColor; + case "branded": + return styles.cambioYellowBackgroundColor; + case "success-primary": + return styles.cambioSuccessBackgroundColor; + case "destructive-primary": + return styles.cambioDestructiveBackgroundColor; + default: + return styles.cambioBlackBackgroundColor; + } +} diff --git a/packages/syntax-core/src/colors/colors.module.css b/packages/syntax-core/src/colors/colors.module.css index 4df08d8f..5824476d 100644 --- a/packages/syntax-core/src/colors/colors.module.css +++ b/packages/syntax-core/src/colors/colors.module.css @@ -219,3 +219,196 @@ .yellow900BackgroundColor { background-color: var(--color-base-yellow-900); } + +/* Cambio colors */ +.cambioBlackColor { + color: var(--color-cambio-black); +} + +.cambioWhiteColor { + color: var(--color-cambio-white); +} + +.cambioGray18Color { + color: var(--color-cambio-gray-18); +} + +.cambioGray36Color { + color: var(--color-cambio-gray-36); +} + +.cambioGray54Color { + color: var(--color-cambio-gray-54); +} + +.cambioGray74Color { + color: var(--color-cambio-gray-74); +} + +.cambioGray88Color { + color: var(--color-cambio-gray-88); +} + +.cambioGray96Color { + color: var(--color-cambio-gray-96); +} + +.cambioDestructiveColor { + color: var(--color-cambio-destructive); +} + +.cambioSuccessColor { + color: var(--color-cambio-success); +} + +.cambioRedColor { + color: var(--color-cambio-red); +} + +.cambioOrangeColor { + color: var(--color-cambio-orange); +} + +.cambioTanColor { + color: var(--color-cambio-tan); +} + +.cambioCreamColor { + color: var(--color-cambio-cream); +} + +.cambioPurpleColor { + color: var(--color-cambio-purple); +} + +.cambioLilacColor { + color: var(--color-cambio-lilac); +} + +.cambioThistleColor { + color: var(--color-cambio-thistle); +} + +.cambioPinkColor { + color: var(--color-cambio-pink); +} + +.cambioNavyColor { + color: var(--color-cambio-navy); +} + +.cambioTealColor { + color: var(--color-cambio-teal); +} + +.cambioSlateColor { + color: var(--color-cambio-slate); +} + +.cambioSkyColor { + color: var(--color-cambio-sky); +} + +.cambioYellowColor { + color: var(--color-cambio-yellow); +} + +.cambioTransparentFullBackgroundColor { + background-color: var(--color-cambio-transparent-full); +} + +.cambioTransparentGray54BackgroundColor { + background-color: var(--color-cambio-transparent-gray-54); +} + +.cambioBlackBackgroundColor { + background-color: var(--color-cambio-black); +} + +.cambioWhiteBackgroundColor { + background-color: var(--color-cambio-white); +} + +.cambioGray18BackgroundColor { + background-color: var(--color-cambio-gray-18); +} + +.cambioGray36BackgroundColor { + background-color: var(--color-cambio-gray-36); +} + +.cambioGray54BackgroundColor { + background-color: var(--color-cambio-gray-54); +} + +.cambioGray74BackgroundColor { + background-color: var(--color-cambio-gray-74); +} + +.cambioGray88BackgroundColor { + background-color: var(--color-cambio-gray-88); +} + +.cambioGray96BackgroundColor { + background-color: var(--color-cambio-gray-96); +} + +.cambioDestructiveBackgroundColor { + background-color: var(--color-cambio-destructive); +} + +.cambioSuccessBackgroundColor { + background-color: var(--color-cambio-success); +} + +.cambioRedBackgroundColor { + background-color: var(--color-cambio-red); +} + +.cambioOrangeBackgroundColor { + background-color: var(--color-cambio-orange); +} + +.cambioTanBackgroundColor { + background-color: var(--color-cambio-tan); +} + +.cambioCreamBackgroundColor { + background-color: var(--color-cambio-cream); +} + +.cambioPurpleBackgroundColor { + background-color: var(--color-cambio-purple); +} + +.cambioLilacBackgroundColor { + background-color: var(--color-cambio-lilac); +} + +.cambioThistleBackgroundColor { + background-color: var(--color-cambio-thistle); +} + +.cambioPinkBackgroundColor { + background-color: var(--color-cambio-pink); +} + +.cambioNavyBackgroundColor { + background-color: var(--color-cambio-navy); +} + +.cambioTealBackgroundColor { + background-color: var(--color-cambio-teal); +} + +.cambioSlateBackgroundColor { + background-color: var(--color-cambio-slate); +} + +.cambioSkyBackgroundColor { + background-color: var(--color-cambio-sky); +} + +.cambioYellowBackgroundColor { + background-color: var(--color-cambio-yellow); +} diff --git a/packages/syntax-core/src/colors/foregroundColor.ts b/packages/syntax-core/src/colors/foregroundColor.ts index 90957da1..c807b25b 100644 --- a/packages/syntax-core/src/colors/foregroundColor.ts +++ b/packages/syntax-core/src/colors/foregroundColor.ts @@ -1,7 +1,7 @@ -import { type Color } from "../constants"; +import { type CambioColor, type Color } from "../constants"; import styles from "./colors.module.css"; -export default function foregroundColor(color: (typeof Color)[number]): string { +export function classicForegroundColor(color: (typeof Color)[number]): string { switch (color) { case "secondary": case "tertiary": @@ -17,3 +17,21 @@ export default function foregroundColor(color: (typeof Color)[number]): string { return styles.whiteColor; } } + +export function cambioForegroundColor( + color: (typeof CambioColor)[number], +): string { + switch (color) { + case "primary": + case "quaternary": + case "success-primary": + case "destructive-primary": + return styles.cambioWhiteColor; + case "success-secondary": + return styles.cambioSuccessColor; + case "destructive-secondary": + return styles.cambioDestructiveColor; + default: + return styles.cambioBlackColor; + } +} diff --git a/packages/syntax-core/src/constants.ts b/packages/syntax-core/src/constants.ts index d69f4fac..5e5cb603 100644 --- a/packages/syntax-core/src/constants.ts +++ b/packages/syntax-core/src/constants.ts @@ -16,4 +16,22 @@ export const Color = [ "inherit", ] as const; -export const Size = ["sm", "md", "lg"] as const; +export const CambioColor = [ + "primary", + "secondary", + "tertiary", + "quaternary", + "branded", + "success-primary", + "success-secondary", + "destructive-primary", + "destructive-secondary", +]; + +export const Size = [ + "sm", + "md", + "lg", + // xl is Cambio only + "xl", +] as const; diff --git a/packages/syntax-design-tokens/tokens/color/base.json b/packages/syntax-design-tokens/tokens/color/base.json index 0ce6465f..b82544e7 100644 --- a/packages/syntax-design-tokens/tokens/color/base.json +++ b/packages/syntax-design-tokens/tokens/color/base.json @@ -87,6 +87,41 @@ "900": { "value": "#3B3009" } }, "white": { "value": "#ffffff" } + }, + "cambio": { + "black": { "value": "#050500" }, + "white": { "value": "#ffffff" }, + "gray": { + "18": { "value": "#312b23" }, + "36": { "value": "#5c554d" }, + "54": { "value": "#888077" }, + "74": { "value": "#beb4ab" }, + "88": { "value": "#e4dbd3" }, + "96": { "value": "#faf4eb" } + }, + "destructive": { "value": "#c93f32" }, + "success": { "value": "#3c7f20" }, + "red": { "value": "#f56e56" }, + "orange": { "value": "#ff8f57" }, + "tan": { "value": "#ffb47d" }, + "cream": { "value": "#fffad1" }, + "purple": { "value": "#6840a8" }, + "lilac": { "value": "#b59ef0" }, + "thistle": { "value": "#d69ca4" }, + "pink": { "value": "#ffccea" }, + "navy": { "value": "#191142" }, + "teal": { "value": "#44a6cf" }, + "slate": { "value": "#7c9fc6" }, + "sky": { "value": "#b1e8fc" }, + "yellow": { "value": "#ffe733" }, + "transparent": { + "full": { + "value": "rgba(0, 0, 0, 0)" + }, + "gray": { + "54": { "value": "rgba(136, 128, 119, 0.5)" } + } + } } } }