diff --git a/.changeset/salt-provider-next-accent.md b/.changeset/salt-provider-next-accent.md new file mode 100644 index 00000000000..3d7956e6559 --- /dev/null +++ b/.changeset/salt-provider-next-accent.md @@ -0,0 +1,11 @@ +--- +"@salt-ds/core": minor +--- + +Added a new `accent` prop to `UNSTABLE_SaltProviderNext` with `"blue"` or `"teal"` option. To try it out, use + +``` + +``` + +Refer to [documentation](https://storybook.saltdesignsystem.com/?path=/docs/experimental-theme-next--docs) for more information. diff --git a/.changeset/strong-parents-march.md b/.changeset/strong-parents-march.md index 46da781e9e0..b1e90f4e18a 100644 --- a/.changeset/strong-parents-march.md +++ b/.changeset/strong-parents-march.md @@ -5,3 +5,5 @@ Switched to use new color palette in theme next when using `UNSTABLE_SaltProviderNext`. Refer to [documentation](https://storybook.saltdesignsystem.com/?path=/docs/experimental-theme-next--docs) for more information. + +Closes #3394 diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index 161c287a460..40e16dab2ff 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -138,6 +138,16 @@ export const globalTypes: GlobalTypes = { title: "Heading font", }, }, + accent: { + name: "Experimental accent", + description: "Switch accent to blue / teal", + defaultValue: "blue", + toolbar: { + icon: "beaker", + items: ["blue", "teal"], + title: "Accent", + }, + }, }; export const argTypes: ArgTypes = { diff --git a/docs/decorators/withTheme.tsx b/docs/decorators/withTheme.tsx index 5559a0ba4c8..9044b8b21a1 100644 --- a/docs/decorators/withTheme.tsx +++ b/docs/decorators/withTheme.tsx @@ -65,8 +65,15 @@ function SetBackground({ viewMode, id }: { viewMode: string; id: string }) { } export const withTheme: Decorator = (StoryFn, context) => { - const { density, mode, styleInjection, themeNext, corner, headingFont } = - context.globals; + const { + density, + mode, + styleInjection, + themeNext, + corner, + headingFont, + accent, + } = context.globals; const Provider = themeNext === "enable" ? UNSTABLE_SaltProviderNext : SaltProvider; @@ -97,6 +104,7 @@ export const withTheme: Decorator = (StoryFn, context) => { enableStyleInjection={styleInjection === "enable"} corner={corner} headingFont={headingFont} + accent={accent} > @@ -115,6 +123,7 @@ export const withTheme: Decorator = (StoryFn, context) => { enableStyleInjection={styleInjection === "enable"} corner={corner} headingFont={headingFont} + accent={accent} > diff --git a/packages/core/src/__tests__/__e2e__/salt-provider/SaltProvider.cy.tsx b/packages/core/src/__tests__/__e2e__/salt-provider/SaltProvider.cy.tsx index 8c9df8be9ba..930c843b89e 100644 --- a/packages/core/src/__tests__/__e2e__/salt-provider/SaltProvider.cy.tsx +++ b/packages/core/src/__tests__/__e2e__/salt-provider/SaltProvider.cy.tsx @@ -19,7 +19,8 @@ const TestComponent = ({ className?: string; }) => { const density = useDensity(); - const { theme, mode, UNSTABLE_corner, themeNext } = useTheme(); + const { theme, mode, UNSTABLE_corner, UNSTABLE_accent, themeNext } = + useTheme(); const { announce } = useAriaAnnouncer(); const announcerPresent = typeof announce === "function"; @@ -32,6 +33,7 @@ const TestComponent = ({ data-mode={mode} data-announcer={announcerPresent} data-corner={UNSTABLE_corner} + data-accent={UNSTABLE_accent} data-themeNext={themeNext} /> ); @@ -274,6 +276,7 @@ describe("Given a SaltProviderNext", () => { .should("exist") .and("have.attr", "data-mode", "light") .and("have.attr", "data-corner", "sharp") + .and("have.attr", "data-accent", "blue") .and("have.class", "salt-theme-next") .and("have.class", "salt-density-medium"); }); @@ -289,6 +292,7 @@ describe("Given a SaltProviderNext", () => { .and("have.attr", "data-mode", "light") .and("have.attr", "data-announcer", "true") .and("have.attr", "data-corner", "sharp") + .and("have.attr", "data-accent", "blue") .and("have.attr", "data-themeNext", "true"); cy.get("[aria-live]").should("exist"); }); @@ -297,7 +301,12 @@ describe("Given a SaltProviderNext", () => { describe("when nested", () => { it("should inherit values not passed as props", () => { mount( - + @@ -313,6 +322,7 @@ describe("Given a SaltProviderNext", () => { .and("have.attr", "data-density", "high") .and("have.attr", "data-mode", "dark") .and("have.attr", "data-corner", "rounded") + .and("have.attr", "data-accent", "teal") .and("have.attr", "data-announcer", "true"); cy.get("#test-2") @@ -320,13 +330,23 @@ describe("Given a SaltProviderNext", () => { .and("have.attr", "data-density", "medium") .and("have.attr", "data-mode", "dark") .and("have.attr", "data-corner", "rounded") + .and("have.attr", "data-accent", "teal") .and("have.attr", "data-announcer", "true"); }); it("should take different values set as props", () => { mount( - + - + @@ -340,6 +360,7 @@ describe("Given a SaltProviderNext", () => { .and("have.attr", "data-density", "high") .and("have.attr", "data-mode", "dark") .and("have.attr", "data-corner", "rounded") + .and("have.attr", "data-accent", "teal") .and("have.attr", "data-announcer", "true"); cy.get("#test-2") @@ -347,6 +368,7 @@ describe("Given a SaltProviderNext", () => { .and("have.attr", "data-density", "medium") .and("have.attr", "data-mode", "dark") .and("have.attr", "data-corner", "sharp") + .and("have.attr", "data-accent", "blue") .and("have.attr", "data-announcer", "true"); }); }); diff --git a/packages/core/src/salt-provider/SaltProvider.tsx b/packages/core/src/salt-provider/SaltProvider.tsx index 9307ed4d61c..c2bcf065757 100644 --- a/packages/core/src/salt-provider/SaltProvider.tsx +++ b/packages/core/src/salt-provider/SaltProvider.tsx @@ -26,6 +26,7 @@ import { } from "@salt-ds/styles"; import { UNSTABLE_Corner } from "../theme/Corner"; import { UNSTABLE_HeadingFont } from "../theme/HeadingFont"; +import { UNSTABLE_Accent } from "../theme/Accent"; export const DEFAULT_DENSITY = "medium"; @@ -35,6 +36,7 @@ const UNSTABLE_ADDITIONAL_THEME_NAME = "salt-theme-next"; const DEFAULT_MODE = "light"; const DEFAULT_CORNER: UNSTABLE_Corner = "sharp"; const DEFAULT_HEADING_FONT: UNSTABLE_HeadingFont = "Open Sans"; +const DEFAULT_ACCENT: UNSTABLE_Accent = "blue"; export interface ThemeContextProps { theme: ThemeName; mode: Mode; @@ -43,6 +45,7 @@ export interface ThemeContextProps { themeNext: boolean; UNSTABLE_corner: UNSTABLE_Corner; UNSTABLE_headingFont: UNSTABLE_HeadingFont; + UNSTABLE_accent: UNSTABLE_Accent; } export const DensityContext = createContext(DEFAULT_DENSITY); @@ -53,6 +56,7 @@ export const ThemeContext = createContext({ themeNext: false, UNSTABLE_corner: DEFAULT_CORNER, UNSTABLE_headingFont: DEFAULT_HEADING_FONT, + UNSTABLE_accent: DEFAULT_ACCENT, }); export const BreakpointContext = @@ -88,6 +92,7 @@ const createThemedChildren = ({ themeNext, corner, headingFont, + accent, }: { children: ReactNode; themeName: ThemeName; @@ -100,6 +105,7 @@ const createThemedChildren = ({ const themeNextProps = { "data-corner": corner, "data-heading-font": headingFont, + "data-accent": accent, }; if (applyClassesTo === "root") { return children; @@ -181,6 +187,7 @@ function InternalSaltProvider({ themeNext, corner: cornerProp, headingFont: headingFontProp, + accent: accentProp, }: Omit< SaltProviderProps & ThemeNextProps & UNSTABLE_SaltProviderNextProps, "enableStyleInjection" @@ -192,6 +199,7 @@ function InternalSaltProvider({ window: inheritedWindow, UNSTABLE_corner: inheritedCorner, UNSTABLE_headingFont: inheritedHeadingFont, + UNSTABLE_accent: inheritedAccent, } = useContext(ThemeContext); const isRootProvider = inheritedTheme === undefined || inheritedTheme === ""; @@ -203,6 +211,7 @@ function InternalSaltProvider({ const corner = cornerProp ?? inheritedCorner ?? DEFAULT_CORNER; const headingFont = headingFontProp ?? inheritedHeadingFont ?? DEFAULT_HEADING_FONT; + const accent = accentProp ?? inheritedAccent ?? DEFAULT_ACCENT; const applyClassesTo = applyClassesToProp ?? (isRootProvider ? "root" : "scope"); @@ -222,8 +231,9 @@ function InternalSaltProvider({ themeNext: Boolean(themeNext), UNSTABLE_corner: corner, UNSTABLE_headingFont: headingFont, + UNSTABLE_accent: accent, }), - [themeName, mode, targetWindow, themeNext, corner, headingFont] + [themeName, mode, targetWindow, themeNext, corner, headingFont, accent] ); const themedChildren = createThemedChildren({ @@ -235,6 +245,7 @@ function InternalSaltProvider({ themeNext, corner: corner, headingFont, + accent, }); useIsomorphicLayoutEffect(() => { @@ -252,6 +263,7 @@ function InternalSaltProvider({ targetWindow.document.documentElement.dataset.corner = corner; targetWindow.document.documentElement.dataset.headingFont = headingFont; + targetWindow.document.documentElement.dataset.accent = accent; } } else { console.warn( @@ -270,6 +282,7 @@ function InternalSaltProvider({ if (themeNext) { delete targetWindow.document.documentElement.dataset.corner; delete targetWindow.document.documentElement.dataset.headingFont; + delete targetWindow.document.documentElement.dataset.accent; } } }; @@ -283,6 +296,7 @@ function InternalSaltProvider({ themeNext, corner, headingFont, + accent, ]); const matchedBreakpoints = useMatchedBreakpoints(breakpoints); @@ -320,6 +334,7 @@ export function SaltProvider({ interface UNSTABLE_SaltProviderNextAdditionalProps { corner?: UNSTABLE_Corner; headingFont?: UNSTABLE_HeadingFont; + accent?: UNSTABLE_Accent; } export type UNSTABLE_SaltProviderNextProps = SaltProviderProps & diff --git a/packages/core/src/theme/Accent.ts b/packages/core/src/theme/Accent.ts new file mode 100644 index 00000000000..50a12136c6c --- /dev/null +++ b/packages/core/src/theme/Accent.ts @@ -0,0 +1,3 @@ +export const UNSTABLE_AccentValues = ["blue", "teal"] as const; + +export type UNSTABLE_Accent = (typeof UNSTABLE_AccentValues)[number]; diff --git a/packages/core/src/theme/index.ts b/packages/core/src/theme/index.ts index bb1c5f267b6..9c373c2d10b 100644 --- a/packages/core/src/theme/index.ts +++ b/packages/core/src/theme/index.ts @@ -1,3 +1,4 @@ +export * from "./Accent"; export * from "./Density"; export * from "./HeadingFont"; export * from "./Theme"; diff --git a/packages/core/stories/salt-provider/salt-provider-next.mdx b/packages/core/stories/salt-provider/salt-provider-next.mdx index 8b5ba9546f1..26d37cfc5f1 100644 --- a/packages/core/stories/salt-provider/salt-provider-next.mdx +++ b/packages/core/stories/salt-provider/salt-provider-next.mdx @@ -12,16 +12,19 @@ import { Banner, BannerContent } from "@salt-ds/core"; # Salt Provider Next -The new Salt Provider introduces Salt's next phase theming capability with more styling options, including -rounded corners, balanced color palettes, etc. Features will be introduced gradually over time and we're -encouraging teams needing this to test to give us feedback. +The new Salt Provider introduces Salt's next phase theming capability with more +styling options, including rounded corners, balanced color palettes, etc. +Features will be introduced gradually over time and we're encouraging teams +needing this to test to give us feedback. -It extends existing [Salt Provider](/?path=/docs/documentation-core-salt-provider--docs) so existing options -are still available (e.g. light/dark mode, high/medium/low/touch density). Additional props will be made available -to switch between different look. +It extends existing +[Salt Provider](/?path=/docs/documentation-core-salt-provider--docs) so existing +options are still available (e.g. light/dark mode, high/medium/low/touch +density). Additional props will be made available to switch between different +look. -To use the new Salt Provider, you need to swap any existing `SaltProvider` to use the new `UNSTABLE_SaltProviderNext`, -and also import a new theme CSS. +To use the new Salt Provider, you need to swap any existing `SaltProvider` to +use the new `UNSTABLE_SaltProviderNext`, and also import a new theme CSS. ```js static // Swap out existing SaltProvider @@ -33,18 +36,61 @@ import { UNSTABLE_SaltProviderNext } from "@salt-ds/core"; import "@salt-ds/theme/css/theme-next.css"; ``` -The new provider adds additional `.salt-theme-next` to either the root or provider element, together with -any additional data attributes (e.g. `data-corner`) needed for new props. +The new provider adds additional `.salt-theme-next` to either the root or +provider element, together with any additional data attributes (e.g. +`data-corner`) needed for new props. -All values within `theme-next.css` will be scoped to `.salt-theme-next` to make sure any replacement values -(like new color palettes) could override existing values in `.salt-theme`. +All values within `theme-next.css` will be scoped to `.salt-theme-next` to make +sure any replacement values (like new color palettes) could override existing +values in `.salt-theme`. + +## Unified Color Palette + +A completely new color palette is used, featuring balanced color ramps for each +color group. + +You will find 9 colors in each color group, ensuring that the midpoint (500 +colors) achieves at least a 4.5 contrast ratio against both light and dark +primary backgrounds. Primary and secondary background colors are no longer +selected from the color ramps but are instead chosen from additional foundation +colors (snow & marble in light, jet & granite in dark). + +Refer to Figma file +([UNSTABLE Salt Colors (variables)]()) +for color values and mappings. + +### Migration note: + +If you're referring any Salt foundation colors (i.e. +`--salt-color-*`) directly in your code, you'll need to revisit those values. We +do not recommend directly consume foundation colors directly. Instead, try to +find a characteristics color that will work better in different modes. + +## New Theme Palette Structure + +A new structure of palette layer is introduced to help making color changes +easier for custom themes, namely + +- Accent +- Background +- Foreground +- Neutral +- Info +- Negative +- Positive +- Warning + +All existing characteristics are re-wired through above new palette layers to +use the new colors, which is scoped to `.salt-theme-next`. ## Corner radius -Although default Salt design language stays sharp corners to serve our institutional clients, -rounded corners can be made available to many components to achieve a softer, consumer friendly feel. +Although default Salt design language stays sharp corners to serve our +institutional clients, rounded corners can be made available to many components +to achieve a softer, consumer friendly feel. -The `corner` prop can be used to toggle between `"sharp"` (default) and `"rounded"`. +The `corner` prop can be used to toggle between `"sharp"` (default) and +`"rounded"`. ```tsx @@ -73,11 +119,13 @@ A new set of palette token (corner) is added with below mapping ### Components -Following components have received corner options, and more components will be added from feedback. +Following components have received corner options, and more components will be +added from feedback. #### Interactive (inner) -Interactive components sit within other interactive components use `--salt-palette-corner-weaker` token. +Interactive components sit within other interactive components use +`--salt-palette-corner-weaker` token. - Checkbox - Pill @@ -114,13 +162,15 @@ These components use `--salt-palette-corner-strongest` token ## Heading font switch -A new `headingFont` prop is added to switch display and heading font family between Open Sans and Amplitude. +A new `headingFont` prop is added to switch display and heading font family +between Open Sans and Amplitude. ``` ``` -You'll need to install Amplitude font to your application to make sure every user will see the font correctly, e.g., +You'll need to install Amplitude font to your application to make sure every +user will see the font correctly, e.g., ```css @font-face { @@ -132,34 +182,32 @@ You'll need to install Amplitude font to your application to make sure every use } ``` -## Unified Color Palette - -A completely new color palette is used with balanced color ramps for each color group. +## Accent color switch -You will find 9 colors in each color group, which makes sure middle point (500 colors) will achieve -at least 4.5 contrast ratio against both light and dark primary background. Primary and secondary -background colors are no longer picked from the color ramps, but rather from additional foundation -colors (snow & marble in light, jet & granite in dark). - -Refer to Figma file ([UNSTABLE Salt Colors (variables)]()) -for color values and mappings. +A new `accent` prop is introduced with options `"blue"` (default) and `"teal"`. +This enables the possiblity of switching all components that use accent colors to +either blue or teal color palette, such as CTA button, checkbox, radio button, +etc. -Migration note: If you're referring any Salt foundation colors (i.e. `--salt-color-*`) directly in your code, -you'll need to revisit those values. We do not recommend directly consume foundation colors directly, instead -try to find a characteristics color instead which will work better in different modes. - -## New Theme Palette Structure +```tsx + +``` -A new structure of palette layer is introduced to help making color changes easier for custom themes, namely +Under the hood, a new `data-accent` attribute is added alongside other style option +attributes. Variables below are being switched to different colors -- Accent -- Background -- Foreground -- Neutral -- Info -- Negative -- Positive -- Warning - -All existing characteristics are re-wired through above new palette layers to use the new colors, -which is scoped to `.salt-theme-next`. +``` +--salt-palette-accent +--salt-palette-accent-disabled +--salt-palette-accent-strong +--salt-palette-accent-strong-disabled +--salt-palette-accent-stronger +--salt-palette-accent-stronger-disabled +--salt-palette-accent-strongest +--salt-palette-accent-weak +--salt-palette-accent-weaker +--salt-palette-accent-weaker-disabled +--salt-palette-accent-weakest +--salt-palette-accent-action-hover +--salt-palette-accent-action-active +``` diff --git a/packages/theme/css/foundations/fade-next.css b/packages/theme/css/foundations/fade-next.css index 0bf6bc3229b..9088a2808c0 100644 --- a/packages/theme/css/foundations/fade-next.css +++ b/packages/theme/css/foundations/fade-next.css @@ -26,6 +26,13 @@ con: If we offer these values, then it could misleading that we're offering all --salt-color-blue-600-40a: rgba(var(--salt-color-blue-600-rgb), 0.4); --salt-color-blue-700-40a: rgba(var(--salt-color-blue-700-rgb), 0.4); --salt-color-blue-800-40a: rgba(var(--salt-color-blue-800-rgb), 0.4); + --salt-color-teal-200-40a: rgba(var(--salt-color-teal-200-rgb), 0.4); + --salt-color-teal-300-40a: rgba(var(--salt-color-teal-300-rgb), 0.4); + --salt-color-teal-400-40a: rgba(var(--salt-color-teal-400-rgb), 0.4); + --salt-color-teal-500-40a: rgba(var(--salt-color-teal-500-rgb), 0.4); + --salt-color-teal-600-40a: rgba(var(--salt-color-teal-600-rgb), 0.4); + --salt-color-teal-700-40a: rgba(var(--salt-color-teal-700-rgb), 0.4); + --salt-color-teal-800-40a: rgba(var(--salt-color-teal-800-rgb), 0.4); --salt-color-background-snow-40a: rgba(var(--salt-color-background-snow-rgb), 0.4); --salt-color-background-marble-40a: rgba(var(--salt-color-background-marble-rgb), 0.4); --salt-color-background-limestone-40a: rgba(var(--salt-color-background-limestone-rgb), 0.4); diff --git a/packages/theme/css/palette/accent-next.css b/packages/theme/css/palette/accent-next.css index f7e2cedcbac..400fad72c33 100644 --- a/packages/theme/css/palette/accent-next.css +++ b/packages/theme/css/palette/accent-next.css @@ -1,4 +1,4 @@ -.salt-theme.salt-theme-next[data-mode="light"] { +.salt-theme.salt-theme-next[data-mode="light"][data-accent="blue"] { --salt-palette-accent: var(--salt-color-blue-500); --salt-palette-accent-disabled: var(--salt-color-blue-500-40a); --salt-palette-accent-strong: var(--salt-color-blue-600); @@ -14,7 +14,7 @@ --salt-palette-accent-action-active: var(--salt-color-blue-800); } -.salt-theme.salt-theme-next[data-mode="dark"] { +.salt-theme.salt-theme-next[data-mode="dark"][data-accent="blue"] { --salt-palette-accent: var(--salt-color-blue-500); --salt-palette-accent-disabled: var(--salt-color-blue-500-40a); --salt-palette-accent-strong: var(--salt-color-blue-400); @@ -29,3 +29,35 @@ --salt-palette-accent-action-hover: var(--salt-color-blue-600); --salt-palette-accent-action-active: var(--salt-color-blue-800); } + +.salt-theme.salt-theme-next[data-mode="light"][data-accent="teal"] { + --salt-palette-accent: var(--salt-color-teal-500); + --salt-palette-accent-disabled: var(--salt-color-teal-500-40a); + --salt-palette-accent-strong: var(--salt-color-teal-600); + --salt-palette-accent-strong-disabled: var(--salt-color-teal-600-40a); + --salt-palette-accent-stronger: var(--salt-color-teal-700); + --salt-palette-accent-stronger-disabled: var(--salt-color-teal-700-40a); + --salt-palette-accent-strongest: var(--salt-color-teal-800); + --salt-palette-accent-weak: var(--salt-color-teal-400); + --salt-palette-accent-weaker: var(--salt-color-teal-200); + --salt-palette-accent-weaker-disabled: var(--salt-color-teal-200-40a); + --salt-palette-accent-weakest: var(--salt-color-teal-100); + --salt-palette-accent-action-hover: var(--salt-color-teal-600); + --salt-palette-accent-action-active: var(--salt-color-teal-800); +} + +.salt-theme.salt-theme-next[data-mode="dark"][data-accent="teal"] { + --salt-palette-accent: var(--salt-color-teal-500); + --salt-palette-accent-disabled: var(--salt-color-teal-500-40a); + --salt-palette-accent-strong: var(--salt-color-teal-400); + --salt-palette-accent-strong-disabled: var(--salt-color-teal-400-40a); + --salt-palette-accent-stronger: var(--salt-color-teal-300); + --salt-palette-accent-stronger-disabled: var(--salt-color-teal-300-40a); + --salt-palette-accent-strongest: var(--salt-color-teal-200); + --salt-palette-accent-weak: var(--salt-color-teal-600); + --salt-palette-accent-weaker: var(--salt-color-teal-800); + --salt-palette-accent-weaker-disabled: var(--salt-color-teal-800-40a); + --salt-palette-accent-weakest: var(--salt-color-teal-900); + --salt-palette-accent-action-hover: var(--salt-color-teal-600); + --salt-palette-accent-action-active: var(--salt-color-teal-800); +}