From eb69b11fa1b6007e94a10aa87e2913edf5021cba Mon Sep 17 00:00:00 2001 From: Lane Sawyer Date: Wed, 29 Nov 2023 11:08:44 -0800 Subject: [PATCH 1/9] Storybook dark mode plumbing in place --- .gitignore | 9 ++ .storybook/preview.js | 23 +++- .../src/core/Alert/index.stories.tsx | 10 +- packages/components/src/core/Alert/style.ts | 3 +- .../src/core/ButtonIcon/index.stories.tsx | 4 +- packages/components/src/core/Callout/style.ts | 4 +- .../core/LoadingIndicator/index.stories.tsx | 4 +- .../components/src/core/Notification/style.ts | 3 +- .../src/core/styles/common/defaultTheme.ts | 121 ++++++++++++++---- 9 files changed, 141 insertions(+), 40 deletions(-) diff --git a/.gitignore b/.gitignore index aca0f173b..b88528d50 100644 --- a/.gitignore +++ b/.gitignore @@ -108,3 +108,12 @@ storybook-static # OS Files .DS_Store + +# Yarn 3 files +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks +!.yarn/versions diff --git a/.storybook/preview.js b/.storybook/preview.js index 10b92e954..ac00226f4 100644 --- a/.storybook/preview.js +++ b/.storybook/preview.js @@ -2,11 +2,14 @@ import { defaultTheme } from "../packages/components/src/core/styles"; import { ThemeProvider } from "@mui/material/styles"; export const decorators = [ - (Story) => ( - + (Story, context) => { + const { theme } = context.globals; + + return ( + - ), + )}, ]; /** @@ -17,6 +20,20 @@ export const decorators = [ const preview = { globalTypes: { pseudo: {}, + theme: { + description: 'Global theme for components', + defaultValue: 'light', + toolbar: { + title: 'Theme', + icon: 'circlehollow', + dynamicTitle: true, + items: [ + { value: 'light', left: '☀️', title: 'Light mode' }, + { value: 'dark', left: '🌙', title: 'Dark mode' }, + ], + }, + }, }, }; + export default preview; diff --git a/packages/components/src/core/Alert/index.stories.tsx b/packages/components/src/core/Alert/index.stories.tsx index f4cbaab93..433cf5b82 100644 --- a/packages/components/src/core/Alert/index.stories.tsx +++ b/packages/components/src/core/Alert/index.stories.tsx @@ -4,11 +4,11 @@ import { styled } from "@mui/material/styles"; import { Args, Meta } from "@storybook/react"; import React from "react"; import Button from "../Button"; -import { defaultTheme } from "../styles/common/defaultTheme"; import Alert from "./index"; -const DismissButton = styled(Button)` - margin-left: -${defaultTheme.spacing(3)}px; +const DismissButton = styled(Button)( + ({ theme }) => ` + margin-left: -${theme.spacing(3)}px; padding-bottom: 0; font-size: 12px; line-height: 18px; @@ -16,8 +16,8 @@ const DismissButton = styled(Button)` font-weight: 600; &:hover { background: none; - } -`; + }` +); const Demo = (props: Args): JSX.Element => { const { text } = props; diff --git a/packages/components/src/core/Alert/style.ts b/packages/components/src/core/Alert/style.ts index 851980c15..67393679a 100644 --- a/packages/components/src/core/Alert/style.ts +++ b/packages/components/src/core/Alert/style.ts @@ -1,7 +1,6 @@ import { Alert } from "@mui/material"; import { styled } from "@mui/material/styles"; import { getColors, getShadows, getSpaces } from "../styles"; -import { defaultTheme } from "../styles/common/defaultTheme"; export const StyledAlert = styled(Alert)` ${(props) => { @@ -17,7 +16,7 @@ export const StyledAlert = styled(Alert)` return ` background-color: ${backgroundColor}; margin: ${spacings?.m}px 0; - color: ${defaultTheme.palette.text.primary}; + color: ${props.theme.palette.text.primary}; padding: ${spacings?.l}px ${spacings?.l}px ${spacings?.l}px 9px; background-color: ${alertColor}; diff --git a/packages/components/src/core/ButtonIcon/index.stories.tsx b/packages/components/src/core/ButtonIcon/index.stories.tsx index 8b3026e6d..793b0eb35 100644 --- a/packages/components/src/core/ButtonIcon/index.stories.tsx +++ b/packages/components/src/core/ButtonIcon/index.stories.tsx @@ -1,6 +1,6 @@ import { Args, Meta } from "@storybook/react"; import React from "react"; -import { defaultAppTheme } from "../styles"; +import { sharedAppTheme } from "../styles"; import RawButtonIcon from "./index"; import { ButtonIconExtraProps, ButtonIconSizeToTypes } from "./style"; @@ -95,7 +95,7 @@ export const Default = { // Live Preview const LivePreviewDemo = (): JSX.Element => { - const spacings = defaultAppTheme?.spacing; + const spacings = sharedAppTheme?.spacing; const livePreviewStyles: React.CSSProperties = { alignItems: "center", diff --git a/packages/components/src/core/Callout/style.ts b/packages/components/src/core/Callout/style.ts index e62df563f..7b337bcf1 100644 --- a/packages/components/src/core/Callout/style.ts +++ b/packages/components/src/core/Callout/style.ts @@ -8,7 +8,6 @@ import { getIconSizes, getSpaces, } from "../styles"; -import { defaultTheme } from "../styles/common/defaultTheme"; interface CalloutExtraProps extends CommonThemeProps { collapsed?: boolean; @@ -39,12 +38,13 @@ export const StyledCallout = styled(Alert, { // any buttom margin const titleBottomMargin = props.collapsed ? "margin-bottom: 0;" : ""; + // TODO: Theme shouldn't be saying it might be null return ` background-color: ${backgroundColor}; width: 360px; margin: ${spacings?.m}px 0; border-radius: ${corners?.m}px; - color: ${defaultTheme.palette.text.primary}; + color: ${props.theme?.palette.text.primary}; padding: ${spacings?.m}px; background-color: ${calloutColor}; diff --git a/packages/components/src/core/LoadingIndicator/index.stories.tsx b/packages/components/src/core/LoadingIndicator/index.stories.tsx index 13f670c97..47c080ec9 100644 --- a/packages/components/src/core/LoadingIndicator/index.stories.tsx +++ b/packages/components/src/core/LoadingIndicator/index.stories.tsx @@ -1,6 +1,6 @@ import { Args, Meta } from "@storybook/react"; import React from "react"; -import { defaultAppTheme } from "../styles"; +import { sharedAppTheme } from "../styles"; import RawLoadingIndicator from "./index"; const LoadingIndicator = (props: Args): JSX.Element => { @@ -30,7 +30,7 @@ export const Default = { // Live Preview const LivePreviewDemo = (props: Args): JSX.Element => { - const spacings = defaultAppTheme?.spacing; + const spacings = sharedAppTheme?.spacing; const livePreviewStyles = { display: "grid", diff --git a/packages/components/src/core/Notification/style.ts b/packages/components/src/core/Notification/style.ts index 7c1fd25aa..f88ed504b 100644 --- a/packages/components/src/core/Notification/style.ts +++ b/packages/components/src/core/Notification/style.ts @@ -8,7 +8,6 @@ import { getShadows, getSpaces, } from "../styles"; -import { defaultTheme } from "../styles/common/defaultTheme"; const fontBodyXs = fontBody("xs"); @@ -33,7 +32,7 @@ export const StyledNotification = styled(Alert)` box-sizing: border-box; margin: ${spacings?.m}px 0; border-radius: ${corners?.m}px; - color: ${defaultTheme.palette.text.primary}; + color: ${props.theme.palette.text.primary}; padding: ${spacings?.l}px; align-items: flex-start; background-color: ${notificationColor}; diff --git a/packages/components/src/core/styles/common/defaultTheme.ts b/packages/components/src/core/styles/common/defaultTheme.ts index d5b6b161f..fe64c5d7b 100644 --- a/packages/components/src/core/styles/common/defaultTheme.ts +++ b/packages/components/src/core/styles/common/defaultTheme.ts @@ -18,7 +18,7 @@ enum FontWeight { semibold = 600, } -const defaultThemeColors = { +const lightThemeColors = { beta: { "100": "#F4F0F9", "200": "#F0EBF6", @@ -75,8 +75,64 @@ const defaultThemeColors = { }, }; -export const defaultAppTheme: AppTheme = { - colors: defaultThemeColors, +const darkThemeColors = { + beta: { + "100": "#1E1B26", + "200": "#23212D", + "400": "#946314", + "500": "#8A5D0C", + "600": "#7F5C1E", + }, + error: { + "100": "#5E1313", + "200": "#612626", + "400": "#FF5468", + "500": "#E84551", + "600": "#FF2D46", + }, + gray: { + "100": "#121212", + "200": "#1F1F1F", + "300": "#3C3C3C", + "400": "#666666", + "500": "#8A8A8A", + "600": "#AAAAAA", + }, + info: { + "100": "#1A1F29", + "200": "#1C1F29", + "400": "#6792FF", + "500": "#4A70A5", + "600": "#396388", + }, + primary: { + "100": "#181A23", + "200": "#1A1F29", + "300": "#5A6F92", + "400": "#6792FF", + "500": "#4A70A5", + "600": "#396388", + }, + secondary: { + "400": "#6D8E3A", + }, + success: { + "100": "#193A26", + "200": "#197C5F", + "400": "#72B07E", + "500": "#5F8F6B", + "600": "#345B3E", + }, + warning: { + "100": "#302B23", + "200": "#382E24", + "400": "#F5A623", + "500": "#D8921F", + "600": "#946314", + }, +}; + +export const sharedAppTheme: Omit = { corners: { l: 20, m: 4, @@ -97,6 +153,7 @@ export const defaultAppTheme: AppTheme = { xl: { height: 32, width: 32 }, xs: { height: 10, width: 10 }, }, + // TODO: Dark mode? shadows: { l: "0 2px 12px 0 rgba(0,0,0, 0.3)", m: "0 2px 4px 0 rgba(0,0,0, 0.15), 0 2px 10px 0 rgba(0,0,0, 0.15)", @@ -239,40 +296,52 @@ export const defaultAppTheme: AppTheme = { }, }; +const lightAppTheme: AppTheme = { + colors: lightThemeColors, + mode: "light", + ...sharedAppTheme, +}; + +const darkAppTheme: AppTheme = { + colors: darkThemeColors, + mode: "dark", + ...sharedAppTheme, +}; + // (mlila) whenever our theme uses colors, we need to make sure we allow consuming // applications to override those colors using their own custom theme. // By defining borders using defaultAppTheme.colors instead of defaultThemeColors, // we allow other apps to specify their colors once, and have them apply -// throughtout the application, such as in borders, etc without having to manually +// throughout the application, such as in borders, etc without having to manually // override every theme property that makes use of colors. -defaultAppTheme.borders = { +lightAppTheme.borders = { error: { - "400": `1px solid ${defaultAppTheme.colors.error[400]}`, + "400": `1px solid ${lightAppTheme.colors.error[400]}`, }, gray: { - "100": `1px solid ${defaultAppTheme.colors.gray[100]}`, - "200": `1px solid ${defaultAppTheme.colors.gray[200]}`, - "300": `1px solid ${defaultAppTheme.colors.gray[300]}`, - "400": `1px solid ${defaultAppTheme.colors.gray[400]}`, - "500": `1px solid ${defaultAppTheme.colors.gray[500]}`, - dashed: `2px dashed ${defaultAppTheme.colors.gray[400]}`, + "100": `1px solid ${lightAppTheme.colors.gray[100]}`, + "200": `1px solid ${lightAppTheme.colors.gray[200]}`, + "300": `1px solid ${lightAppTheme.colors.gray[300]}`, + "400": `1px solid ${lightAppTheme.colors.gray[400]}`, + "500": `1px solid ${lightAppTheme.colors.gray[500]}`, + dashed: `2px dashed ${lightAppTheme.colors.gray[400]}`, }, link: { dashed: `1px dashed`, solid: `1px solid`, }, primary: { - "300": `1px solid ${defaultAppTheme.colors.primary[300]}`, - "400": `1px solid ${defaultAppTheme.colors.primary[400]}`, - "500": `1px solid ${defaultAppTheme.colors.primary[500]}`, - "600": `1px solid${defaultAppTheme.colors.primary[600]}`, - dashed: `2px dashed ${defaultAppTheme.colors.primary[400]}`, + "300": `1px solid ${lightAppTheme.colors.primary[300]}`, + "400": `1px solid ${lightAppTheme.colors.primary[400]}`, + "500": `1px solid ${lightAppTheme.colors.primary[500]}`, + "600": `1px solid${lightAppTheme.colors.primary[600]}`, + dashed: `2px dashed ${lightAppTheme.colors.primary[400]}`, }, success: { - "400": `1px solid ${defaultAppTheme.colors.success[400]}`, + "400": `1px solid ${lightAppTheme.colors.success[400]}`, }, warning: { - "400": `1px solid ${defaultAppTheme.colors.warning[400]}`, + "400": `1px solid ${lightAppTheme.colors.warning[400]}`, }, }; @@ -316,7 +385,7 @@ export function makeThemeOptions(appTheme: AppTheme): SDSThemeOptions { light: appTheme.colors.info[200], main: appTheme.colors.info[400], }, - mode: "light", + mode: appTheme.mode, primary: { dark: appTheme.colors.primary[600], light: appTheme.colors.primary[300], @@ -418,7 +487,12 @@ export function makeThemeOptions(appTheme: AppTheme): SDSThemeOptions { }; } -const defaultThemeOptions: SDSThemeOptions = makeThemeOptions(defaultAppTheme); +const chooseTheme = (theme: "light" | "dark") => { + if (theme === "dark") { + return darkAppTheme; + } + return lightAppTheme; +}; export interface SDSTheme extends Theme { app?: AppTheme; @@ -437,6 +511,8 @@ interface AppTheme { shadows: Shadows; spacing: Spacings; typography: Typography; + // TODO: Do I like this approach? + mode: "light" | "dark"; } export interface Shadows { @@ -557,4 +633,5 @@ export interface Borders { warning: Border; } -export const defaultTheme = createThemeAdaptor(defaultThemeOptions); +export const defaultTheme = (theme: "light" | "dark") => + createThemeAdaptor(makeThemeOptions(chooseTheme(theme))); From eb7080fe6e5e8739809484ca251b3cefd4df3a52 Mon Sep 17 00:00:00 2001 From: Lane Sawyer Date: Wed, 29 Nov 2023 13:09:35 -0800 Subject: [PATCH 2/9] Light and dark theme palette --- .storybook/preview.js | 13 +- .../src/core/styles/common/defaultTheme.ts | 194 +++++++++++++----- 2 files changed, 153 insertions(+), 54 deletions(-) diff --git a/.storybook/preview.js b/.storybook/preview.js index ac00226f4..1d7476096 100644 --- a/.storybook/preview.js +++ b/.storybook/preview.js @@ -1,15 +1,16 @@ -import { defaultTheme } from "../packages/components/src/core/styles"; +import { theme } from "../packages/components/src/core/styles"; import { ThemeProvider } from "@mui/material/styles"; export const decorators = [ (Story, context) => { - const { theme } = context.globals; + const { theme: storybookTheme } = context.globals; return ( - - - - )}, + + + + ); + }, ]; /** diff --git a/packages/components/src/core/styles/common/defaultTheme.ts b/packages/components/src/core/styles/common/defaultTheme.ts index fe64c5d7b..f6bc49d05 100644 --- a/packages/components/src/core/styles/common/defaultTheme.ts +++ b/packages/components/src/core/styles/common/defaultTheme.ts @@ -153,7 +153,7 @@ export const sharedAppTheme: Omit = { xl: { height: 32, width: 32 }, xs: { height: 10, width: 10 }, }, - // TODO: Dark mode? + // TODO: Dark mode for shadows? shadows: { l: "0 2px 12px 0 rgba(0,0,0, 0.3)", m: "0 2px 4px 0 rgba(0,0,0, 0.15), 0 2px 10px 0 rgba(0,0,0, 0.15)", @@ -345,6 +345,131 @@ lightAppTheme.borders = { }, }; +// TODO: Colors +darkAppTheme.borders = { + error: { + "400": `1px solid ${lightAppTheme.colors.error[400]}`, + }, + gray: { + "100": `1px solid ${lightAppTheme.colors.gray[100]}`, + "200": `1px solid ${lightAppTheme.colors.gray[200]}`, + "300": `1px solid ${lightAppTheme.colors.gray[300]}`, + "400": `1px solid ${lightAppTheme.colors.gray[400]}`, + "500": `1px solid ${lightAppTheme.colors.gray[500]}`, + dashed: `2px dashed ${lightAppTheme.colors.gray[400]}`, + }, + link: { + dashed: `1px dashed`, + solid: `1px solid`, + }, + primary: { + "300": `1px solid ${lightAppTheme.colors.primary[300]}`, + "400": `1px solid ${lightAppTheme.colors.primary[400]}`, + "500": `1px solid ${lightAppTheme.colors.primary[500]}`, + "600": `1px solid${lightAppTheme.colors.primary[600]}`, + dashed: `2px dashed ${lightAppTheme.colors.primary[400]}`, + }, + success: { + "400": `1px solid ${lightAppTheme.colors.success[400]}`, + }, + warning: { + "400": `1px solid ${lightAppTheme.colors.warning[400]}`, + }, +}; + +const lightThemePallette = (appTheme: AppTheme) => ({ + divider: appTheme.colors.gray[200], + error: { + dark: appTheme.colors.error[600], + light: appTheme.colors.error[200], + main: appTheme.colors.error[400], + }, + grey: { + "100": appTheme.colors.gray[100], + "200": appTheme.colors.gray[200], + "300": appTheme.colors.gray[300], + "400": appTheme.colors.gray[400], + "500": appTheme.colors.gray[500], + "600": appTheme.colors.gray[600], + }, + info: { + dark: appTheme.colors.info[600], + light: appTheme.colors.info[200], + main: appTheme.colors.info[400], + }, + mode: appTheme.mode, + primary: { + dark: appTheme.colors.primary[600], + light: appTheme.colors.primary[300], + main: appTheme.colors.primary[400], + }, + secondary: { + main: appTheme.colors.secondary[400], + }, + success: { + dark: appTheme.colors.success[600], + light: appTheme.colors.success[200], + main: appTheme.colors.success[400], + }, + text: { + disabled: appTheme.colors.gray[300], + primary: common.black, + secondary: appTheme.colors.gray[500], + }, + warning: { + dark: appTheme.colors.warning[600], + light: appTheme.colors.warning[200], + main: appTheme.colors.warning[400], + }, +}); + +// TODO: Colors +const darkThemePalette = (appTheme: AppTheme) => ({ + divider: appTheme.colors.gray[200], + error: { + dark: appTheme.colors.error[600], + light: appTheme.colors.error[200], + main: appTheme.colors.error[400], + }, + grey: { + "100": appTheme.colors.gray[100], + "200": appTheme.colors.gray[200], + "300": appTheme.colors.gray[300], + "400": appTheme.colors.gray[400], + "500": appTheme.colors.gray[500], + "600": appTheme.colors.gray[600], + }, + info: { + dark: appTheme.colors.info[600], + light: appTheme.colors.info[200], + main: appTheme.colors.info[400], + }, + mode: appTheme.mode, + primary: { + dark: appTheme.colors.primary[600], + light: appTheme.colors.primary[300], + main: appTheme.colors.primary[400], + }, + secondary: { + main: appTheme.colors.secondary[400], + }, + success: { + dark: appTheme.colors.success[600], + light: appTheme.colors.success[200], + main: appTheme.colors.success[400], + }, + text: { + disabled: appTheme.colors.gray[300], + primary: common.black, + secondary: appTheme.colors.gray[500], + }, + warning: { + dark: appTheme.colors.warning[600], + light: appTheme.colors.warning[200], + main: appTheme.colors.warning[400], + }, +}); + export function makeThemeOptions(appTheme: AppTheme): SDSThemeOptions { return { app: appTheme, @@ -365,51 +490,9 @@ export function makeThemeOptions(appTheme: AppTheme): SDSThemeOptions { }, }, }, - palette: { - divider: appTheme.colors.gray[200], - error: { - dark: appTheme.colors.error[600], - light: appTheme.colors.error[200], - main: appTheme.colors.error[400], - }, - grey: { - "100": appTheme.colors.gray[100], - "200": appTheme.colors.gray[200], - "300": appTheme.colors.gray[300], - "400": appTheme.colors.gray[400], - "500": appTheme.colors.gray[500], - "600": appTheme.colors.gray[600], - }, - info: { - dark: appTheme.colors.info[600], - light: appTheme.colors.info[200], - main: appTheme.colors.info[400], - }, - mode: appTheme.mode, - primary: { - dark: appTheme.colors.primary[600], - light: appTheme.colors.primary[300], - main: appTheme.colors.primary[400], - }, - secondary: { - main: appTheme.colors.secondary[400], - }, - success: { - dark: appTheme.colors.success[600], - light: appTheme.colors.success[200], - main: appTheme.colors.success[400], - }, - text: { - disabled: appTheme.colors.gray[300], - primary: common.black, - secondary: appTheme.colors.gray[500], - }, - warning: { - dark: appTheme.colors.warning[600], - light: appTheme.colors.warning[200], - main: appTheme.colors.warning[400], - }, - }, + palette: appTheme.mode + ? lightThemePallette(appTheme) + : darkThemePalette(appTheme), shadows: [ appTheme.shadows.none, appTheme.shadows.s, @@ -633,5 +716,20 @@ export interface Borders { warning: Border; } -export const defaultTheme = (theme: "light" | "dark") => - createThemeAdaptor(makeThemeOptions(chooseTheme(theme))); +/** + * Default theme, uses light mode with no option to change it. + * + * @deprecated Use the `theme` to get a flexible light/dark mode theme function + */ +export const defaultTheme = createThemeAdaptor( + makeThemeOptions(chooseTheme("light")) +); + +/** + * Theme adaptor with light/dark mode support. + * + * @param t The theme to use. Currently supports a light and dark variant. + * @returns The selected theme object to be used in the ThemeProvider + */ +export const theme = (t: "light" | "dark") => + createThemeAdaptor(makeThemeOptions(chooseTheme(t))); From 0e701633a785b4dec0e96d98da09c56ef4eb3ff0 Mon Sep 17 00:00:00 2001 From: Lane Sawyer Date: Wed, 29 Nov 2023 13:42:37 -0800 Subject: [PATCH 3/9] Comments and TODOs --- README.md | 2 +- .../core/LoadingIndicator/index.stories.tsx | 6 +++-- .../src/core/styles/common/defaultTheme.ts | 27 ++++++++++++++----- 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 15fe574f8..95343816b 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ export const Title - styled(Typography)` ```ts import { css, SerializedStyles } from "@emotion/react"; import { styled } from '@mui/material/styles'; -import { getColors, getCorners } from "@czi-sds/components"; +import { getColors, getSpaces } from "@czi-sds/components"; export const Tag = styled("div")` // This is a callback function that returns more CSS rules, but the only way diff --git a/packages/components/src/core/LoadingIndicator/index.stories.tsx b/packages/components/src/core/LoadingIndicator/index.stories.tsx index 47c080ec9..59bebe777 100644 --- a/packages/components/src/core/LoadingIndicator/index.stories.tsx +++ b/packages/components/src/core/LoadingIndicator/index.stories.tsx @@ -1,6 +1,7 @@ +import { useTheme } from "@mui/material/styles"; import { Args, Meta } from "@storybook/react"; import React from "react"; -import { sharedAppTheme } from "../styles"; +import { SDSTheme } from "../styles"; import RawLoadingIndicator from "./index"; const LoadingIndicator = (props: Args): JSX.Element => { @@ -30,7 +31,8 @@ export const Default = { // Live Preview const LivePreviewDemo = (props: Args): JSX.Element => { - const spacings = sharedAppTheme?.spacing; + const theme: SDSTheme = useTheme(); + const spacings = theme.app?.spacing; const livePreviewStyles = { display: "grid", diff --git a/packages/components/src/core/styles/common/defaultTheme.ts b/packages/components/src/core/styles/common/defaultTheme.ts index f6bc49d05..7a38f3475 100644 --- a/packages/components/src/core/styles/common/defaultTheme.ts +++ b/packages/components/src/core/styles/common/defaultTheme.ts @@ -7,6 +7,7 @@ import { } from "@mui/material/styles"; const createThemeAdaptor = createTheme; +type ThemeVariants = "light" | "dark"; const { common } = colors; @@ -132,7 +133,15 @@ const darkThemeColors = { }, }; -export const sharedAppTheme: Omit = { +/** + * Base app theme for properties shared between light and dark mode. Generally, if a theme + * property doesn't deal with colors it belongs here, otherwise it'll have its specific + * theme variant defined in `lightAppTheme` or `darkAppTheme`. + * + * `colors` and `mode` are ommitted because they must be defined by the `lightAppTheme` and + * `darkAppTheme` objects before use in the `makeThemeOptions` function. + */ +const sharedAppTheme: Omit = { corners: { l: 20, m: 4, @@ -153,7 +162,6 @@ export const sharedAppTheme: Omit = { xl: { height: 32, width: 32 }, xs: { height: 10, width: 10 }, }, - // TODO: Dark mode for shadows? shadows: { l: "0 2px 12px 0 rgba(0,0,0, 0.3)", m: "0 2px 4px 0 rgba(0,0,0, 0.15), 0 2px 10px 0 rgba(0,0,0, 0.15)", @@ -345,7 +353,7 @@ lightAppTheme.borders = { }, }; -// TODO: Colors +// TODO: Dark mode colors darkAppTheme.borders = { error: { "400": `1px solid ${lightAppTheme.colors.error[400]}`, @@ -570,7 +578,13 @@ export function makeThemeOptions(appTheme: AppTheme): SDSThemeOptions { }; } -const chooseTheme = (theme: "light" | "dark") => { +/** + * Helper function to select the appropriate theme settings. + * + * @param theme The theme to choose from. Currently supports a light and dark variant. + * @returns The appropriate app theme for the variant. + */ +const chooseTheme = (theme: ThemeVariants): AppTheme => { if (theme === "dark") { return darkAppTheme; } @@ -594,8 +608,7 @@ interface AppTheme { shadows: Shadows; spacing: Spacings; typography: Typography; - // TODO: Do I like this approach? - mode: "light" | "dark"; + mode: ThemeVariants; } export interface Shadows { @@ -731,5 +744,5 @@ export const defaultTheme = createThemeAdaptor( * @param t The theme to use. Currently supports a light and dark variant. * @returns The selected theme object to be used in the ThemeProvider */ -export const theme = (t: "light" | "dark") => +export const theme = (t: ThemeVariants) => createThemeAdaptor(makeThemeOptions(chooseTheme(t))); From 589c5cd524ff223c77cb30bbb634754e00d1a32a Mon Sep 17 00:00:00 2001 From: Lane Sawyer Date: Wed, 29 Nov 2023 13:47:44 -0800 Subject: [PATCH 4/9] Strongly type some objects, new TODO --- .storybook/preview.js | 1 + packages/components/src/core/styles/common/defaultTheme.ts | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.storybook/preview.js b/.storybook/preview.js index 1d7476096..27198a5b5 100644 --- a/.storybook/preview.js +++ b/.storybook/preview.js @@ -5,6 +5,7 @@ export const decorators = [ (Story, context) => { const { theme: storybookTheme } = context.globals; + // TODO: Make the background work with light/dark theme return ( diff --git a/packages/components/src/core/styles/common/defaultTheme.ts b/packages/components/src/core/styles/common/defaultTheme.ts index 7a38f3475..818cff021 100644 --- a/packages/components/src/core/styles/common/defaultTheme.ts +++ b/packages/components/src/core/styles/common/defaultTheme.ts @@ -1,6 +1,7 @@ import { colors } from "@mui/material"; import { createTheme, + PaletteOptions, Theme, ThemeOptions, TypographyStyle, @@ -385,7 +386,7 @@ darkAppTheme.borders = { }, }; -const lightThemePallette = (appTheme: AppTheme) => ({ +const lightThemePallette = (appTheme: AppTheme): PaletteOptions => ({ divider: appTheme.colors.gray[200], error: { dark: appTheme.colors.error[600], @@ -432,7 +433,7 @@ const lightThemePallette = (appTheme: AppTheme) => ({ }); // TODO: Colors -const darkThemePalette = (appTheme: AppTheme) => ({ +const darkThemePalette = (appTheme: AppTheme): PaletteOptions => ({ divider: appTheme.colors.gray[200], error: { dark: appTheme.colors.error[600], @@ -580,7 +581,7 @@ export function makeThemeOptions(appTheme: AppTheme): SDSThemeOptions { /** * Helper function to select the appropriate theme settings. - * + * * @param theme The theme to choose from. Currently supports a light and dark variant. * @returns The appropriate app theme for the variant. */ From 8166de184ece57887eb6afb9dc4d2632b9b32f58 Mon Sep 17 00:00:00 2001 From: Lane Sawyer Date: Wed, 29 Nov 2023 14:02:14 -0800 Subject: [PATCH 5/9] Fix up reading from the theme in a storybook story --- packages/components/src/core/ButtonIcon/index.stories.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/components/src/core/ButtonIcon/index.stories.tsx b/packages/components/src/core/ButtonIcon/index.stories.tsx index 793b0eb35..0270346e3 100644 --- a/packages/components/src/core/ButtonIcon/index.stories.tsx +++ b/packages/components/src/core/ButtonIcon/index.stories.tsx @@ -1,6 +1,7 @@ +import { useTheme } from "@mui/material/styles"; import { Args, Meta } from "@storybook/react"; import React from "react"; -import { sharedAppTheme } from "../styles"; +import { SDSTheme } from "../styles"; import RawButtonIcon from "./index"; import { ButtonIconExtraProps, ButtonIconSizeToTypes } from "./style"; @@ -95,7 +96,8 @@ export const Default = { // Live Preview const LivePreviewDemo = (): JSX.Element => { - const spacings = sharedAppTheme?.spacing; + const theme: SDSTheme = useTheme(); + const spacings = theme.app?.spacing; const livePreviewStyles: React.CSSProperties = { alignItems: "center", From dcbdbfbb76c9988d522055c301f03c215164be9e Mon Sep 17 00:00:00 2001 From: Lane Sawyer Date: Wed, 29 Nov 2023 14:07:36 -0800 Subject: [PATCH 6/9] Use MUI type for theme, remove mode from AppTheme --- .../src/core/styles/common/defaultTheme.ts | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/components/src/core/styles/common/defaultTheme.ts b/packages/components/src/core/styles/common/defaultTheme.ts index 818cff021..d47eaeda0 100644 --- a/packages/components/src/core/styles/common/defaultTheme.ts +++ b/packages/components/src/core/styles/common/defaultTheme.ts @@ -1,4 +1,4 @@ -import { colors } from "@mui/material"; +import { colors, PaletteMode } from "@mui/material"; import { createTheme, PaletteOptions, @@ -8,7 +8,6 @@ import { } from "@mui/material/styles"; const createThemeAdaptor = createTheme; -type ThemeVariants = "light" | "dark"; const { common } = colors; @@ -307,13 +306,11 @@ const sharedAppTheme: Omit = { const lightAppTheme: AppTheme = { colors: lightThemeColors, - mode: "light", ...sharedAppTheme, }; const darkAppTheme: AppTheme = { colors: darkThemeColors, - mode: "dark", ...sharedAppTheme, }; @@ -406,7 +403,7 @@ const lightThemePallette = (appTheme: AppTheme): PaletteOptions => ({ light: appTheme.colors.info[200], main: appTheme.colors.info[400], }, - mode: appTheme.mode, + mode: "light", primary: { dark: appTheme.colors.primary[600], light: appTheme.colors.primary[300], @@ -453,7 +450,7 @@ const darkThemePalette = (appTheme: AppTheme): PaletteOptions => ({ light: appTheme.colors.info[200], main: appTheme.colors.info[400], }, - mode: appTheme.mode, + mode: "dark", primary: { dark: appTheme.colors.primary[600], light: appTheme.colors.primary[300], @@ -479,7 +476,10 @@ const darkThemePalette = (appTheme: AppTheme): PaletteOptions => ({ }, }); -export function makeThemeOptions(appTheme: AppTheme): SDSThemeOptions { +export function makeThemeOptions( + appTheme: AppTheme, + mode: PaletteMode +): SDSThemeOptions { return { app: appTheme, components: { @@ -499,9 +499,10 @@ export function makeThemeOptions(appTheme: AppTheme): SDSThemeOptions { }, }, }, - palette: appTheme.mode - ? lightThemePallette(appTheme) - : darkThemePalette(appTheme), + palette: + mode === "light" + ? lightThemePallette(appTheme) + : darkThemePalette(appTheme), shadows: [ appTheme.shadows.none, appTheme.shadows.s, @@ -585,7 +586,7 @@ export function makeThemeOptions(appTheme: AppTheme): SDSThemeOptions { * @param theme The theme to choose from. Currently supports a light and dark variant. * @returns The appropriate app theme for the variant. */ -const chooseTheme = (theme: ThemeVariants): AppTheme => { +const chooseTheme = (theme: PaletteMode): AppTheme => { if (theme === "dark") { return darkAppTheme; } @@ -609,7 +610,6 @@ interface AppTheme { shadows: Shadows; spacing: Spacings; typography: Typography; - mode: ThemeVariants; } export interface Shadows { @@ -736,7 +736,7 @@ export interface Borders { * @deprecated Use the `theme` to get a flexible light/dark mode theme function */ export const defaultTheme = createThemeAdaptor( - makeThemeOptions(chooseTheme("light")) + makeThemeOptions(chooseTheme("light"), "light") ); /** @@ -745,5 +745,5 @@ export const defaultTheme = createThemeAdaptor( * @param t The theme to use. Currently supports a light and dark variant. * @returns The selected theme object to be used in the ThemeProvider */ -export const theme = (t: ThemeVariants) => - createThemeAdaptor(makeThemeOptions(chooseTheme(t))); +export const theme = (t: PaletteMode) => + createThemeAdaptor(makeThemeOptions(chooseTheme(t), t)); From cf8b16ce6d2cafea5fedb10693dd6b987f5ed921 Mon Sep 17 00:00:00 2001 From: Lane Sawyer Date: Wed, 29 Nov 2023 14:27:18 -0800 Subject: [PATCH 7/9] Light/dark backgrounds automatically in Storybook --- .storybook/preview.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.storybook/preview.js b/.storybook/preview.js index 27198a5b5..9ba515f10 100644 --- a/.storybook/preview.js +++ b/.storybook/preview.js @@ -1,3 +1,4 @@ +import CssBaseline from "@mui/material/CssBaseline"; import { theme } from "../packages/components/src/core/styles"; import { ThemeProvider } from "@mui/material/styles"; @@ -5,9 +6,10 @@ export const decorators = [ (Story, context) => { const { theme: storybookTheme } = context.globals; - // TODO: Make the background work with light/dark theme return ( + {/* CssBaseline provides light/dark background MUI theme for all stories */} + ); @@ -20,6 +22,10 @@ export const decorators = [ * https://github.com/chromaui/storybook-addon-pseudo-states/issues/59#issuecomment-1498182067 */ const preview = { + parameters: { + // Removes the change background button since it's controlled by the theme toggle + backgrounds: { disable: true }, + }, globalTypes: { pseudo: {}, theme: { From 1e6b38f9235fc5e3976f574ddd44e177bb3d9291 Mon Sep 17 00:00:00 2001 From: hthomas-czi <72052788+hthomas-czi@users.noreply.github.com> Date: Wed, 29 Nov 2023 14:34:45 -0800 Subject: [PATCH 8/9] feat: update defaultTheme.ts colors - Updated DialogTitle to use palette.text colors - Updated Tabs to use palette.text colors --- .../components/src/core/DialogTitle/style.ts | 8 +- packages/components/src/core/Tabs/style.tsx | 16 ++-- .../src/core/styles/common/defaultTheme.ts | 88 ++++++------------- 3 files changed, 40 insertions(+), 72 deletions(-) diff --git a/packages/components/src/core/DialogTitle/style.ts b/packages/components/src/core/DialogTitle/style.ts index ce8416e6d..0eba4946f 100644 --- a/packages/components/src/core/DialogTitle/style.ts +++ b/packages/components/src/core/DialogTitle/style.ts @@ -24,7 +24,11 @@ export const StyledDialogTitle = styled(DialogTitle)` export const Title = styled(Typography)` ${fontHeaderXl} - color: black; + ${(props) => { + return ` + color: ${props.theme.palette.text.primary}; + `; + }} `; export const Subtitle = styled(Typography)` @@ -34,7 +38,7 @@ export const Subtitle = styled(Typography)` const colors = getColors(props); return ` - color: ${colors?.gray[500]}; + color: ${props.theme.palette.text.secondary}; `; }} `; diff --git a/packages/components/src/core/Tabs/style.tsx b/packages/components/src/core/Tabs/style.tsx index 2e678026a..3bc5a6411 100644 --- a/packages/components/src/core/Tabs/style.tsx +++ b/packages/components/src/core/Tabs/style.tsx @@ -51,7 +51,9 @@ export const StyledTabs = styled(TempTabs, { return ` margin-top: ${isLarge ? spaces?.l : spaces?.m}px; margin-bottom: ${isLarge ? spaces?.xl : spaces?.m}px; - border-bottom: ${underlined ? `2px solid ${colors?.gray[200]};` : "none"}; + border-bottom: ${ + underlined ? `2px solid ${props.theme.palette.divider};` : "none" + }; `; }} `; @@ -89,26 +91,26 @@ export const StyledTab = styled(RawTab, { // (thuang): Large Tab height is 30px, the offset is 4px height: ${isLarge ? 26 : 22}px; - color: ${colors?.gray[500]}; + color: ${props.theme.palette.text.secondary}; &:hover, :focus { - color: black; + color: ${props.theme.palette.text.primary}; } &.Mui-selected { - color: black; + color: ${props.theme.palette.text.primary}; &:hover { - color: black; + color: ${props.theme.palette.text.primary}; } } &:active { - color: black; + color: ${props.theme.palette.text.primary}; } &:disabled { - color: ${colors?.gray[200]}; + color: ${props.theme.palette.text.disabled}; } `; }} diff --git a/packages/components/src/core/styles/common/defaultTheme.ts b/packages/components/src/core/styles/common/defaultTheme.ts index d47eaeda0..dd2d139c3 100644 --- a/packages/components/src/core/styles/common/defaultTheme.ts +++ b/packages/components/src/core/styles/common/defaultTheme.ts @@ -38,9 +38,12 @@ const lightThemeColors = { "100": "#F8F8F8", "200": "#EAEAEA", "300": "#CCCCCC", - "400": "#999999", + "400": "#959595", "500": "#767676", "600": "#545454", + "700": "#404040", + "800": "#1F1F1F", + "900": "#121212", }, info: { "100": "#EFF2FC", @@ -76,62 +79,7 @@ const lightThemeColors = { }, }; -const darkThemeColors = { - beta: { - "100": "#1E1B26", - "200": "#23212D", - "400": "#946314", - "500": "#8A5D0C", - "600": "#7F5C1E", - }, - error: { - "100": "#5E1313", - "200": "#612626", - "400": "#FF5468", - "500": "#E84551", - "600": "#FF2D46", - }, - gray: { - "100": "#121212", - "200": "#1F1F1F", - "300": "#3C3C3C", - "400": "#666666", - "500": "#8A8A8A", - "600": "#AAAAAA", - }, - info: { - "100": "#1A1F29", - "200": "#1C1F29", - "400": "#6792FF", - "500": "#4A70A5", - "600": "#396388", - }, - primary: { - "100": "#181A23", - "200": "#1A1F29", - "300": "#5A6F92", - "400": "#6792FF", - "500": "#4A70A5", - "600": "#396388", - }, - secondary: { - "400": "#6D8E3A", - }, - success: { - "100": "#193A26", - "200": "#197C5F", - "400": "#72B07E", - "500": "#5F8F6B", - "600": "#345B3E", - }, - warning: { - "100": "#302B23", - "200": "#382E24", - "400": "#F5A623", - "500": "#D8921F", - "600": "#946314", - }, -}; +const darkThemeColors = lightThemeColors; /** * Base app theme for properties shared between light and dark mode. Generally, if a theme @@ -383,8 +331,12 @@ darkAppTheme.borders = { }, }; -const lightThemePallette = (appTheme: AppTheme): PaletteOptions => ({ +const lightThemePalette = (appTheme: AppTheme): PaletteOptions => ({ divider: appTheme.colors.gray[200], + background: { + default: common.white, + secondary: appTheme.colors.gray[100], + }, error: { dark: appTheme.colors.error[600], light: appTheme.colors.error[200], @@ -431,7 +383,11 @@ const lightThemePallette = (appTheme: AppTheme): PaletteOptions => ({ // TODO: Colors const darkThemePalette = (appTheme: AppTheme): PaletteOptions => ({ - divider: appTheme.colors.gray[200], + divider: appTheme.colors.gray[700], + background: { + default: appTheme.colors.gray[900], + secondary: appTheme.colors.gray[800], + }, error: { dark: appTheme.colors.error[600], light: appTheme.colors.error[200], @@ -444,6 +400,9 @@ const darkThemePalette = (appTheme: AppTheme): PaletteOptions => ({ "400": appTheme.colors.gray[400], "500": appTheme.colors.gray[500], "600": appTheme.colors.gray[600], + "700": appTheme.colors.gray[700], + "800": appTheme.colors.gray[800], + "900": appTheme.colors.gray[900], }, info: { dark: appTheme.colors.info[600], @@ -465,9 +424,9 @@ const darkThemePalette = (appTheme: AppTheme): PaletteOptions => ({ main: appTheme.colors.success[400], }, text: { - disabled: appTheme.colors.gray[300], - primary: common.black, - secondary: appTheme.colors.gray[500], + disabled: appTheme.colors.gray[700], + primary: appTheme.colors.gray[100], + secondary: appTheme.colors.gray[400], }, warning: { dark: appTheme.colors.warning[600], @@ -501,7 +460,7 @@ export function makeThemeOptions( }, palette: mode === "light" - ? lightThemePallette(appTheme) + ? lightThemePalette(appTheme) : darkThemePalette(appTheme), shadows: [ appTheme.shadows.none, @@ -678,6 +637,9 @@ export interface Spaces { export type Spacings = Spaces; export interface Color { + 900?: string; + 800?: string; + 700?: string; 600?: string; 500?: string; 400: string; From 1c71d4feace9df4a22a0ccbc508e88887a749fac Mon Sep 17 00:00:00 2001 From: Lane Sawyer Date: Fri, 19 Jan 2024 14:39:14 -0800 Subject: [PATCH 9/9] Addressing PR feedback --- packages/components/src/core/Callout/style.ts | 1 - .../components/src/core/DialogTitle/style.ts | 9 ------- .../components/src/core/Tooltip/index.tsx | 2 +- .../src/core/TooltipCondensed/index.tsx | 2 +- .../src/core/styles/common/defaultTheme.ts | 26 +++++++++---------- 5 files changed, 14 insertions(+), 26 deletions(-) diff --git a/packages/components/src/core/Callout/style.ts b/packages/components/src/core/Callout/style.ts index 7b337bcf1..a863e9ca7 100644 --- a/packages/components/src/core/Callout/style.ts +++ b/packages/components/src/core/Callout/style.ts @@ -38,7 +38,6 @@ export const StyledCallout = styled(Alert, { // any buttom margin const titleBottomMargin = props.collapsed ? "margin-bottom: 0;" : ""; - // TODO: Theme shouldn't be saying it might be null return ` background-color: ${backgroundColor}; width: 360px; diff --git a/packages/components/src/core/DialogTitle/style.ts b/packages/components/src/core/DialogTitle/style.ts index 0eba4946f..18346dff6 100644 --- a/packages/components/src/core/DialogTitle/style.ts +++ b/packages/components/src/core/DialogTitle/style.ts @@ -4,7 +4,6 @@ import { CommonThemeProps as DialogTitleExtraProps, fontBodyXs, fontHeaderXl, - getColors, getSpaces, } from "src/core/styles"; @@ -23,20 +22,12 @@ export const StyledDialogTitle = styled(DialogTitle)` export const Title = styled(Typography)` ${fontHeaderXl} - - ${(props) => { - return ` - color: ${props.theme.palette.text.primary}; - `; - }} `; export const Subtitle = styled(Typography)` ${fontBodyXs} ${(props) => { - const colors = getColors(props); - return ` color: ${props.theme.palette.text.secondary}; `; diff --git a/packages/components/src/core/Tooltip/index.tsx b/packages/components/src/core/Tooltip/index.tsx index fcffd4171..d780f88ed 100644 --- a/packages/components/src/core/Tooltip/index.tsx +++ b/packages/components/src/core/Tooltip/index.tsx @@ -67,7 +67,7 @@ const Tooltip = forwardRef(function Tooltip( inverted, sdsStyle, theme, - width, + width /* stylelint-enable property-no-unknown -- false positive */ }; diff --git a/packages/components/src/core/TooltipCondensed/index.tsx b/packages/components/src/core/TooltipCondensed/index.tsx index 5a1f15d03..8b46a437a 100644 --- a/packages/components/src/core/TooltipCondensed/index.tsx +++ b/packages/components/src/core/TooltipCondensed/index.tsx @@ -25,7 +25,7 @@ const TooltipCondensed = forwardRef(function TooltipCondensed( /* stylelint-disable property-no-unknown -- false positive */ indicator, indicatorColor, - theme, + theme /* stylelint-enable property-no-unknown -- false positive */ }; diff --git a/packages/components/src/core/styles/common/defaultTheme.ts b/packages/components/src/core/styles/common/defaultTheme.ts index dd2d139c3..6690a67f1 100644 --- a/packages/components/src/core/styles/common/defaultTheme.ts +++ b/packages/components/src/core/styles/common/defaultTheme.ts @@ -253,13 +253,13 @@ const sharedAppTheme: Omit = { }; const lightAppTheme: AppTheme = { - colors: lightThemeColors, ...sharedAppTheme, + colors: lightThemeColors, }; const darkAppTheme: AppTheme = { - colors: darkThemeColors, ...sharedAppTheme, + colors: darkThemeColors, }; // (mlila) whenever our theme uses colors, we need to make sure we allow consuming @@ -332,11 +332,11 @@ darkAppTheme.borders = { }; const lightThemePalette = (appTheme: AppTheme): PaletteOptions => ({ - divider: appTheme.colors.gray[200], background: { default: common.white, secondary: appTheme.colors.gray[100], }, + divider: appTheme.colors.gray[200], error: { dark: appTheme.colors.error[600], light: appTheme.colors.error[200], @@ -381,13 +381,13 @@ const lightThemePalette = (appTheme: AppTheme): PaletteOptions => ({ }, }); -// TODO: Colors +// TODO: Dark mode colors const darkThemePalette = (appTheme: AppTheme): PaletteOptions => ({ - divider: appTheme.colors.gray[700], background: { default: appTheme.colors.gray[900], secondary: appTheme.colors.gray[800], }, + divider: appTheme.colors.gray[700], error: { dark: appTheme.colors.error[600], light: appTheme.colors.error[200], @@ -692,15 +692,6 @@ export interface Borders { warning: Border; } -/** - * Default theme, uses light mode with no option to change it. - * - * @deprecated Use the `theme` to get a flexible light/dark mode theme function - */ -export const defaultTheme = createThemeAdaptor( - makeThemeOptions(chooseTheme("light"), "light") -); - /** * Theme adaptor with light/dark mode support. * @@ -709,3 +700,10 @@ export const defaultTheme = createThemeAdaptor( */ export const theme = (t: PaletteMode) => createThemeAdaptor(makeThemeOptions(chooseTheme(t), t)); + +/** + * Default theme, uses light mode with no option to change it. + * + * Use the `theme` export to get a flexible light/dark mode theme function + */ +export const defaultTheme = theme("light");