Skip to content

Commit

Permalink
ThemeProvider: add component & add cambio versions of Button / IconBu…
Browse files Browse the repository at this point in the history
…tton & LinkButton (#317)

* ThemeProvider: add component & base cambio theme

* Add theme selector

* Button / IconButton / LinkButton: add Cambio variants

* Remove xl size

* Update colors & sizes

* Add tests + fallback colors
  • Loading branch information
christianvuerings committed Mar 1, 2024
1 parent 611e0c4 commit 0bbf4b1
Show file tree
Hide file tree
Showing 32 changed files with 2,000 additions and 321 deletions.
7 changes: 7 additions & 0 deletions .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
3 changes: 2 additions & 1 deletion apps/storybook/.storybook/main.ts
Expand Up @@ -13,9 +13,10 @@ const config: StorybookConfig = {
actions: false,
},
},
"@storybook/addon-a11y",
"@storybook/addon-actions",
"@storybook/addon-toolbars",
"storybook-addon-designs",
"@storybook/addon-a11y",
"storybook-addon-rtl",
],
staticDirs: ["../public"],
Expand Down
31 changes: 28 additions & 3 deletions apps/storybook/.storybook/preview.tsx
@@ -1,7 +1,32 @@
import React from "react";
import "@cambly/syntax-design-tokens/dist/css/variables.css";
import { initializeRTL } from "storybook-addon-rtl";
import { Preview } from "@storybook/react";
import ThemeProvider from "../../../packages/syntax-core/src/ThemeProvider/ThemeProvider";

initializeRTL();
const preview: Preview = {
globalTypes: {
theme: {
description: "Global theme for components",
defaultValue: "classic",
toolbar: {
title: "Theme",
icon: "circlehollow",
items: ["classic", "cambio"],
dynamicTitle: true,
},
},
},
decorators: [
(Story, context) => {
const theme = context.globals.theme;
return (
<ThemeProvider themeName={theme}>
<Story />
</ThemeProvider>
);
},
],
};

export default preview;

export const decorators = [(Story) => <Story />];
2 changes: 2 additions & 0 deletions apps/storybook/package.json
Expand Up @@ -27,6 +27,7 @@
"@storybook/addon-docs": "7.0.7",
"@storybook/addon-essentials": "7.0.7",
"@storybook/addon-links": "7.0.7",
"@storybook/addon-toolbars": "^7.6.17",
"@storybook/addons": "7.0.7",
"@storybook/react": "7.0.7",
"@storybook/react-vite": "7.0.7",
Expand All @@ -36,6 +37,7 @@
"serve": "14.2.0",
"storybook": "7.0.7",
"storybook-addon-designs": "7.0.0-beta.2",
"storybook-addon-rtl": "^1.0.0",
"storybook-dark-mode": "3.0.0",
"typescript": "5.0.4",
"vite": "4.3.4"
Expand Down
13 changes: 12 additions & 1 deletion apps/storybook/stories/Colors.tsx
Expand Up @@ -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")
Expand All @@ -25,6 +28,7 @@ export default function Colors() {
return acc;
},
{
Cambio: [],
Grayscale: [],
Primary: [],
Destructive: [],
Expand All @@ -46,8 +50,15 @@ export default function Colors() {
style={{
color:
[
"color-cambio-white",
"color-cambio-cream",
"color-cambio-pink",
"color-cambio-sky",
"color-cambio-yellow-700",
"color-cambio-transparent-full",
"color-base-gray-10",
"color-base-gray-30",
"color-base-yellow-700",
"color-base-white",
].includes(key) ||
(number && parseInt(number) < 400)
Expand Down
1 change: 0 additions & 1 deletion package.json
Expand Up @@ -26,7 +26,6 @@
"glob": "10.3.10",
"jsdom": "22.0.0",
"prettier": "2.8.8",
"storybook-addon-rtl": "^0.5.0",
"stylelint": "15.6.0",
"stylelint-config-css-modules": "4.2.0",
"stylelint-config-standard": "33.0.0",
Expand Down
21 changes: 21 additions & 0 deletions packages/syntax-core/src/Button/Button.module.css
Expand Up @@ -72,6 +72,27 @@
border-radius: 16px;
}

.smCambio {
min-width: 52px;
height: 32px;
padding: 0 16px;
border-radius: 100px;
}

.mdCambio {
min-width: 78px;
height: 48px;
padding: 0 24px;
border-radius: 100px;
}

.lgCambio {
min-width: 104px;
height: 64px;
padding: 0 32px;
border-radius: 100px;
}

.icon {
color: inherit;
}
Expand Down
3 changes: 3 additions & 0 deletions packages/syntax-core/src/Button/Button.stories.tsx
Expand Up @@ -21,6 +21,9 @@ export default {
"destructive-secondary",
"destructive-tertiary",
"success",
"success-primary",
"success-secondary",
"success-tertiary",
"branded",
"inverse",
],
Expand Down
77 changes: 60 additions & 17 deletions packages/syntax-core/src/Button/Button.tsx
@@ -1,18 +1,23 @@
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";

type ButtonProps = {
/**
Expand All @@ -30,6 +35,15 @@ type ButtonProps = {
/**
* The color of the button
*
* Classic only:
* * `inverse`
* * `success`
*
* Cambio only:
* * `success-primary`
* * `success-secondary`
* * `success-tertiary`
*
* @defaultValue "primary"
*/
color?:
Expand All @@ -41,14 +55,23 @@ type ButtonProps = {
| "destructive-tertiary"
| "branded"
| "success"
| "success-primary"
| "success-secondary"
| "success-tertiary"
| "inverse";
/**
* The size of the button
*
* Classic:
* * `sm`: 32px
* * `md`: 40px
* * `lg`: 48px
*
* Cambio:
* * `sm`: 32px
* * `md`: 48px
* * `lg`: 64px
*
* @defaultValue "md"
*/
size?: (typeof Size)[number];
Expand Down Expand Up @@ -76,10 +99,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 }>;
/**
Expand Down Expand Up @@ -120,6 +145,17 @@ const Button = forwardRef<HTMLButtonElement, ButtonProps>(
ref,
) => {
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 (
<button
Expand All @@ -132,39 +168,46 @@ const Button = forwardRef<HTMLButtonElement, ButtonProps>(
onClick={onClick}
className={classNames(
styles.button,
foregroundColor(color),
backgroundColor(color),
styles[size],
foregroundColorClass,
backgroundColorClass,
themeName === "classic" ? styles[size] : styles[`${size}Cambio`],
{
[styles.fullWidth]: fullWidth,
[styles.buttonGap]: size === "lg" || size === "md",
[styles.secondaryBorder]: color === "secondary",
[styles.buttonGap]:
themeName === "classic" && (size === "lg" || size === "md"),
[styles.secondaryBorder]:
themeName === "classic" && color === "secondary",
[styles.secondaryDestructiveBorder]:
color === "destructive-secondary",
themeName === "classic" && color === "destructive-secondary",
},
)}
>
{!loading && StartIcon && (
{!loading && StartIcon && themeName === "classic" && (
<StartIcon className={classNames(styles.icon, iconSize[size])} />
)}
{((loading && loadingText) || (!loading && text)) && (
<Box paddingX={1}>
<Typography
size={textVariant[size]}
color={foregroundTypographyColor(color)}
size={
themeName === "classic" ? textVariant[size] : textVariant[size]
}
>
<span style={{ fontWeight: 500 }}>
<span
// Temporary - until we have cambio colors on Typogrphay
className={foregroundColorClass}
style={{ fontWeight: 500 }}
>
{loading ? loadingText : text}
</span>
</Typography>
</Box>
)}
{!loading && EndIcon && (
{!loading && EndIcon && themeName === "classic" && (
<EndIcon className={classNames(styles.icon, iconSize[size])} />
)}
{loading && (
<svg
className={classNames(styles.loading, foregroundColor(color))}
className={classNames(styles.loading, foregroundColorClass)}
viewBox="22 22 44 44"
width={loadingIconSize[size]}
height={loadingIconSize[size]}
Expand Down
5 changes: 5 additions & 0 deletions packages/syntax-core/src/Button/constants/classicSize.ts
@@ -0,0 +1,5 @@
export default function classicSize(
size: "sm" | "md" | "lg" | "xl",
): "sm" | "md" | "lg" {
return size === "xl" ? "lg" : size;
}
69 changes: 69 additions & 0 deletions packages/syntax-core/src/Button/constants/color.ts
@@ -0,0 +1,69 @@
export function classicColor(
color:
| "primary"
| "secondary"
| "tertiary"
| "destructive-primary"
| "destructive-secondary"
| "destructive-tertiary"
| "branded"
| "success"
| "success-primary"
| "success-secondary"
| "success-tertiary"
| "inverse",
):
| "primary"
| "secondary"
| "tertiary"
| "destructive-primary"
| "destructive-secondary"
| "branded"
| "success"
| "inverse" {
if (color === "destructive-tertiary") {
return "destructive-secondary";
} else if (
color === "success-primary" ||
color === "success-secondary" ||
color === "success-tertiary"
) {
return "success";
}
return color;
}

export function cambioColor(
color:
| "primary"
| "secondary"
| "tertiary"
| "destructive-primary"
| "destructive-secondary"
| "destructive-tertiary"
| "branded"
| "success"
| "success-primary"
| "success-secondary"
| "success-tertiary"
| "inverse",
):
| "primary"
| "secondary"
| "tertiary"
| "destructive-primary"
| "destructive-secondary"
| "destructive-tertiary"
| "branded"
| "success-primary"
| "success-secondary"
| "success-tertiary" {
if (color === "success") {
return "success-primary";
}
// TODO - validate with AJ
else if (color === "inverse") {
return "secondary";
}
return color;
}

0 comments on commit 0bbf4b1

Please sign in to comment.