diff --git a/modules/docs/package.json b/modules/docs/package.json
index 9a283be72d..a420b55b88 100644
--- a/modules/docs/package.json
+++ b/modules/docs/package.json
@@ -49,7 +49,7 @@
"@workday/canvas-kit-react": "^12.2.2",
"@workday/canvas-kit-styling": "^12.2.2",
"@workday/canvas-system-icons-web": "^3.0.0",
- "@workday/canvas-tokens-web": "^2.0.1",
+ "@workday/canvas-tokens-web": "^2.1.0",
"markdown-to-jsx": "^7.2.0",
"react-syntax-highlighter": "^15.5.0",
"ts-node": "^10.9.1"
diff --git a/modules/labs-react/package.json b/modules/labs-react/package.json
index e6d370e27a..c33011e16b 100644
--- a/modules/labs-react/package.json
+++ b/modules/labs-react/package.json
@@ -49,7 +49,7 @@
"@workday/canvas-kit-react": "^12.2.2",
"@workday/canvas-kit-styling": "^12.2.2",
"@workday/canvas-system-icons-web": "^3.0.0",
- "@workday/canvas-tokens-web": "^2.0.1",
+ "@workday/canvas-tokens-web": "^2.1.0",
"@workday/design-assets-types": "^0.2.8",
"chroma-js": "^2.2.0",
"lodash.flatten": "^4.4.0",
diff --git a/modules/preview-react/package.json b/modules/preview-react/package.json
index 1439470f0f..ff72c71fda 100644
--- a/modules/preview-react/package.json
+++ b/modules/preview-react/package.json
@@ -49,7 +49,7 @@
"@workday/canvas-kit-react": "^12.2.2",
"@workday/canvas-kit-styling": "^12.2.2",
"@workday/canvas-system-icons-web": "^3.0.0",
- "@workday/canvas-tokens-web": "^2.0.1",
+ "@workday/canvas-tokens-web": "^2.1.0",
"@workday/design-assets-types": "^0.2.8"
},
"devDependencies": {
diff --git a/modules/react/button/lib/PrimaryButton.tsx b/modules/react/button/lib/PrimaryButton.tsx
index 67164c933c..1a1d25ce79 100644
--- a/modules/react/button/lib/PrimaryButton.tsx
+++ b/modules/react/button/lib/PrimaryButton.tsx
@@ -23,38 +23,50 @@ const primaryButtonStencil = createStencil({
extends: buttonStencil,
base: {
// Base Styles
- [buttonStencil.vars.background]: brand.primary.base,
+ [buttonStencil.vars.background]: cssVar(brand.action.base, brand.primary.base),
[buttonStencil.vars.borderRadius]: system.shape.round,
- [buttonStencil.vars.label]: brand.primary.accent,
- [systemIconStencil.vars.color]: cssVar(buttonColorPropVars.default.icon, brand.primary.accent),
+ [buttonStencil.vars.label]: cssVar(brand.action.accent, brand.primary.accent),
+ [systemIconStencil.vars.color]: cssVar(
+ buttonColorPropVars.default.icon,
+ cssVar(brand.action.accent, brand.primary.accent)
+ ),
// Focus Styles
'&:focus-visible, &.focus': {
- [buttonStencil.vars.background]: brand.primary.base,
- [buttonStencil.vars.label]: brand.primary.accent,
+ [buttonStencil.vars.background]: cssVar(brand.action.base, brand.primary.base),
+ [buttonStencil.vars.label]: cssVar(brand.action.accent, brand.primary.accent),
[buttonStencil.vars.boxShadowInner]: system.color.border.inverse,
[buttonStencil.vars.boxShadowOuter]: brand.common.focusOutline,
- [systemIconStencil.vars.color]: cssVar(buttonColorPropVars.focus.icon, brand.primary.accent),
+ [systemIconStencil.vars.color]: cssVar(
+ buttonColorPropVars.focus.icon,
+ cssVar(brand.action.accent, brand.primary.accent)
+ ),
},
// Hover Styles
'&:hover, &.hover': {
- [buttonStencil.vars.background]: brand.primary.dark,
- [buttonStencil.vars.label]: brand.primary.accent,
- [systemIconStencil.vars.color]: cssVar(buttonColorPropVars.hover.icon, brand.primary.accent),
+ [buttonStencil.vars.background]: cssVar(brand.action.dark, brand.primary.dark),
+ [buttonStencil.vars.label]: cssVar(brand.action.accent, brand.primary.accent),
+ [systemIconStencil.vars.color]: cssVar(
+ buttonColorPropVars.hover.icon,
+ cssVar(brand.action.accent, brand.primary.accent)
+ ),
},
// Active Styles
'&:active, &.active': {
- [buttonStencil.vars.background]: brand.primary.darkest,
- [buttonStencil.vars.label]: brand.primary.accent,
- [systemIconStencil.vars.color]: cssVar(buttonColorPropVars.active.icon, brand.primary.accent),
+ [buttonStencil.vars.background]: cssVar(brand.action.darkest, brand.primary.darkest),
+ [buttonStencil.vars.label]: cssVar(brand.action.accent, brand.primary.accent),
+ [systemIconStencil.vars.color]: cssVar(
+ buttonColorPropVars.active.icon,
+ cssVar(brand.action.accent, brand.primary.accent)
+ ),
},
// Disabled Styles
'&:disabled, &.disabled': {
- [buttonStencil.vars.background]: brand.primary.base,
- [buttonStencil.vars.label]: brand.primary.accent,
+ [buttonStencil.vars.background]: cssVar(brand.action.base, brand.primary.base),
+ [buttonStencil.vars.label]: cssVar(brand.action.accent, brand.primary.accent),
[buttonStencil.vars.opacity]: system.opacity.disabled,
[systemIconStencil.vars.color]: cssVar(
buttonColorPropVars.disabled.icon,
- brand.primary.accent
+ cssVar(brand.action.accent, brand.primary.accent)
),
},
},
diff --git a/modules/react/button/stories/button/Button.mdx b/modules/react/button/stories/button/Button.mdx
index ee00a5b545..7cc19dabff 100644
--- a/modules/react/button/stories/button/Button.mdx
+++ b/modules/react/button/stories/button/Button.mdx
@@ -14,6 +14,7 @@ import { TertiaryInverse } from './examples/TertiaryInverse';
import { Delete } from './examples/Delete';
import { Grow } from './examples/Grow';
import { CustomStyles } from './examples/CustomStyles';
+import { ThemeOverrides } from './examples/ThemeOverrides';
import * as ButtonStories from './Button.stories';
@@ -94,6 +95,12 @@ or view the example below.
+### Theme Overrides
+
+The most common way to theme our buttons is to pass a `theme` object at the root level of the application via the `CanvasProvider`. In the example below, our buttons use our `brand.action.**` tokens with the fallback being `brand.primary.**`.
+
+
+
### Accessible Use of the `as` Prop
Like many of our components, Buttons accept an `as` prop, which lets you change the underlying
diff --git a/modules/react/button/stories/button/Button.stories.tsx b/modules/react/button/stories/button/Button.stories.tsx
index a863207de8..fc1305d7b3 100644
--- a/modules/react/button/stories/button/Button.stories.tsx
+++ b/modules/react/button/stories/button/Button.stories.tsx
@@ -9,6 +9,7 @@ import {Tertiary as TertiaryExample} from './examples/Tertiary';
import {TertiaryInverse as TertiaryInverseExample} from './examples/TertiaryInverse';
import {Delete as DeleteExample} from './examples/Delete';
import {CustomStyles as CustomStylesExample} from './examples/CustomStyles';
+import {ThemeOverrides as ThemeOverridesExample} from './examples/ThemeOverrides';
export default {
title: 'Components/Buttons',
@@ -44,3 +45,7 @@ export const Delete = {
export const CustomStyles = {
render: CustomStylesExample,
};
+
+export const ThemeOverrides = {
+ render: ThemeOverridesExample,
+};
diff --git a/modules/react/button/stories/button/examples/ThemeOverrides.tsx b/modules/react/button/stories/button/examples/ThemeOverrides.tsx
new file mode 100644
index 0000000000..3319ca50e3
--- /dev/null
+++ b/modules/react/button/stories/button/examples/ThemeOverrides.tsx
@@ -0,0 +1,73 @@
+import React from 'react';
+
+import {PrimaryButton} from '@workday/canvas-kit-react/button';
+import {Flex} from '@workday/canvas-kit-react/layout';
+import {
+ plusIcon,
+ relatedActionsVerticalIcon,
+ caretDownIcon,
+} from '@workday/canvas-system-icons-web';
+import {createStyles} from '@workday/canvas-kit-styling';
+import {system} from '@workday/canvas-tokens-web';
+import {CanvasProvider} from '@workday/canvas-kit-react/common';
+import {Heading} from '@workday/canvas-kit-react/text';
+
+const parentContainerStyles = createStyles({
+ gap: system.space.x4,
+ padding: system.space.x4,
+});
+
+export const ThemeOverrides = () => (
+
+
+ Override Primary Color
+
+
+
+ Primary
+
+ Primary
+
+
+ Primary
+
+
+
+
+
+ Override Action Color
+
+
+
+ Primary
+
+ Primary
+
+
+ Primary
+
+
+
+
+
+);
diff --git a/modules/react/button/stories/visual-testing/PrimaryButton.stories.tsx b/modules/react/button/stories/visual-testing/PrimaryButton.stories.tsx
index dad869830e..0acf0452d7 100644
--- a/modules/react/button/stories/visual-testing/PrimaryButton.stories.tsx
+++ b/modules/react/button/stories/visual-testing/PrimaryButton.stories.tsx
@@ -9,6 +9,7 @@ import {customColorTheme} from '../../../../../utils/storybook';
import {playCircleIcon, relatedActionsVerticalIcon} from '@workday/canvas-system-icons-web';
import {PrimaryButton} from '@workday/canvas-kit-react/button';
import {Container, stateTableColumnProps} from './utils';
+import {customColorThemeWithAction} from '../../../../../utils/storybook/customThemes';
export default {
title: 'Testing/Buttons/Button/Primary Button',
@@ -99,6 +100,10 @@ export const PrimaryButtonThemedStates = {
render: () => ,
};
+export const PrimaryButtonThemedActionStates = {
+ render: () => ,
+};
+
export const PrimaryIconButtonThemedStates = {
render: () => ,
};
diff --git a/modules/react/common/lib/CanvasProvider.tsx b/modules/react/common/lib/CanvasProvider.tsx
index 95c69ddc34..7ec9bd5622 100644
--- a/modules/react/common/lib/CanvasProvider.tsx
+++ b/modules/react/common/lib/CanvasProvider.tsx
@@ -44,6 +44,12 @@ const defaultBranding = createStyles({
[brand.primary.base]: base.blueberry400,
[brand.primary.light]: base.blueberry200,
[brand.primary.lightest]: base.blueberry100,
+ [brand.action.accent]: base.frenchVanilla100,
+ [brand.action.darkest]: base.blueberry600,
+ [brand.action.dark]: base.blueberry500,
+ [brand.action.base]: base.blueberry400,
+ [brand.action.light]: base.blueberry200,
+ [brand.action.lightest]: base.blueberry100,
[brand.gradient
.primary]: `linear-gradient(90deg, ${brand.primary.base} 0%, ${brand.primary.dark} 100%)`,
});
@@ -65,20 +71,22 @@ export const useCanvasThemeToCssVars = (
const className = (elemProps.className || '').split(' ').concat(defaultBranding).join(' ');
const style = elemProps.style || {};
const {palette} = filledTheme.canvas;
- (['common', 'primary', 'error', 'alert', 'success', 'neutral'] as const).forEach(color => {
- if (color === 'common') {
- // @ts-ignore
- style[brand.common.focusOutline] = palette.common.focusOutline;
- }
- (['lightest', 'light', 'main', 'dark', 'darkest', 'contrast'] as const).forEach(key => {
- // We only want to set custom colors if they do not match the default. The `defaultBranding` class will take care of the rest.
- // @ts-ignore
- if (palette[color][key] !== defaultCanvasTheme.palette[color][key]) {
+ (['common', 'primary', 'error', 'alert', 'success', 'neutral', 'action'] as const).forEach(
+ color => {
+ if (color === 'common') {
// @ts-ignore
- style[brand[color][mappedKeys[key]]] = palette[color][key];
+ style[brand.common.focusOutline] = palette.common.focusOutline;
}
- });
- });
+ (['lightest', 'light', 'main', 'dark', 'darkest', 'contrast'] as const).forEach(key => {
+ // We only want to set custom colors if they do not match the default. The `defaultBranding` class will take care of the rest.
+ // @ts-ignore
+ if (palette[color][key] !== defaultCanvasTheme.palette[color][key]) {
+ // @ts-ignore
+ style[brand[color][mappedKeys[key]]] = palette[color][key];
+ }
+ });
+ }
+ );
return {...elemProps, className, style};
};
diff --git a/modules/react/common/lib/theming/createCanvasTheme.ts b/modules/react/common/lib/theming/createCanvasTheme.ts
index f8cda7d6f6..6188586f95 100644
--- a/modules/react/common/lib/theming/createCanvasTheme.ts
+++ b/modules/react/common/lib/theming/createCanvasTheme.ts
@@ -80,12 +80,13 @@ function fillPalette(
function calculateCanvasTheme(partialTheme: PartialCanvasTheme): CanvasTheme {
const {palette = {}, breakpoints = {}, direction, ...extraFields} = partialTheme;
- const {primary, alert, error, success, neutral, common = {}} = palette!;
+ const {primary, alert, error, success, neutral, action, common = {}} = palette!;
const mergeable: PartialCanvasTheme = {
palette: {
common,
primary: fillPalette(defaultCanvasTheme.palette.primary, primary),
+ action: fillPalette(defaultCanvasTheme.palette.primary, action || primary),
alert: fillPalette(defaultCanvasTheme.palette.alert, alert),
error: fillPalette(defaultCanvasTheme.palette.error, error),
success: fillPalette(defaultCanvasTheme.palette.success, success),
diff --git a/modules/react/common/lib/theming/theme.ts b/modules/react/common/lib/theming/theme.ts
index f9b2fa97eb..eef779f1d5 100644
--- a/modules/react/common/lib/theming/theme.ts
+++ b/modules/react/common/lib/theming/theme.ts
@@ -12,6 +12,14 @@ export const defaultCanvasTheme: CanvasTheme = {
darkest: colors.blueberry600,
contrast: colors.frenchVanilla100,
},
+ action: {
+ lightest: colors.blueberry100,
+ light: colors.blueberry200,
+ main: colors.blueberry400,
+ dark: colors.blueberry500,
+ darkest: colors.blueberry600,
+ contrast: colors.frenchVanilla100,
+ },
alert: {
lightest: colors.cantaloupe100,
light: colors.cantaloupe300,
diff --git a/modules/react/common/lib/theming/types.ts b/modules/react/common/lib/theming/types.ts
index 8e7c8be176..8e7bc7c6ba 100644
--- a/modules/react/common/lib/theming/types.ts
+++ b/modules/react/common/lib/theming/types.ts
@@ -37,6 +37,7 @@ export interface CanvasTheme {
alert: CanvasThemePalette;
success: CanvasThemePalette;
neutral: CanvasThemePalette;
+ action: CanvasThemePalette;
};
/**
* ### Theme Breakpoints
diff --git a/modules/react/common/spec/createCanvasTheme.spec.tsx b/modules/react/common/spec/createCanvasTheme.spec.tsx
index 3a7200a479..cedecfe0b4 100644
--- a/modules/react/common/spec/createCanvasTheme.spec.tsx
+++ b/modules/react/common/spec/createCanvasTheme.spec.tsx
@@ -23,10 +23,20 @@ describe('createCanvasTheme', () => {
const input = {
palette: {
primary: palette,
+ action: palette,
},
};
const theme = createCanvasTheme(input);
+
const expected = {...defaultCanvasTheme};
+ expected.palette.action = {
+ lightest: 'orange',
+ light: 'orange',
+ main: 'orange',
+ dark: 'orange',
+ darkest: 'orange',
+ contrast: 'orange',
+ };
expected.palette.primary = palette;
expect(theme).toEqual(expected);
@@ -51,6 +61,15 @@ describe('createCanvasTheme', () => {
contrast: '#494949',
};
+ expected.palette.action = {
+ lightest: '#ffff7d',
+ light: '#ffd64a',
+ main: 'orange',
+ dark: '#c67600',
+ darkest: '#904a00',
+ contrast: '#494949',
+ };
+
expect(theme).toEqual(expected);
});
@@ -65,7 +84,40 @@ describe('createCanvasTheme', () => {
const theme = createCanvasTheme(input);
const expected = {...defaultCanvasTheme};
expected.palette.primary.dark = 'black';
+ expected.palette.action.dark = 'black';
+ expect(theme).toEqual(expected);
+ });
+
+ test('calling with a custom palette with action colors should keep the default primary color and only set action', () => {
+ const input = {
+ palette: {
+ primary: {
+ dark: 'black',
+ },
+ action: {
+ dark: 'navy',
+ },
+ },
+ };
+ const theme = createCanvasTheme(input);
+ const expected = {...defaultCanvasTheme};
+ expected.palette.primary.dark = 'black';
+ expected.palette.action.dark = 'navy';
+ expect(theme).toEqual(expected);
+ });
+ test('if not action color is defined, it should default to primary color', () => {
+ const input = {
+ palette: {
+ primary: {
+ dark: 'black',
+ },
+ },
+ };
+ const theme = createCanvasTheme(input);
+ const expected = {...defaultCanvasTheme};
+ expected.palette.primary.dark = 'black';
+ expected.palette.action.dark = 'black';
expect(theme).toEqual(expected);
});
diff --git a/modules/react/package.json b/modules/react/package.json
index 5d1f733e0c..ae8ca4a1e9 100644
--- a/modules/react/package.json
+++ b/modules/react/package.json
@@ -52,7 +52,7 @@
"@workday/canvas-kit-popup-stack": "^12.2.2",
"@workday/canvas-kit-styling": "^12.2.2",
"@workday/canvas-system-icons-web": "^3.0.0",
- "@workday/canvas-tokens-web": "^2.0.1",
+ "@workday/canvas-tokens-web": "^2.1.0",
"@workday/design-assets-types": "^0.2.8",
"chroma-js": "^2.2.0",
"csstype": "^3.0.2",
diff --git a/modules/styling-transform/package.json b/modules/styling-transform/package.json
index 0e206a202c..2ccbfb74dd 100644
--- a/modules/styling-transform/package.json
+++ b/modules/styling-transform/package.json
@@ -35,7 +35,7 @@
"dependencies": {
"@emotion/serialize": "^1.0.2",
"@workday/canvas-kit-styling": "^12.2.2",
- "@workday/canvas-tokens-web": "^2.0.1",
+ "@workday/canvas-tokens-web": "^2.1.0",
"stylis": "4.0.13",
"ts-node": "^10.9.1",
"typescript": "5.0"
diff --git a/modules/styling/package.json b/modules/styling/package.json
index 862d45087e..4b2b5591e8 100644
--- a/modules/styling/package.json
+++ b/modules/styling/package.json
@@ -55,7 +55,7 @@
"@emotion/styled": "^11.6.0",
"@workday/canvas-kit-react": "^12.2.2",
"@workday/canvas-system-icons-web": "^3.0.0",
- "@workday/canvas-tokens-web": "^2.0.1",
+ "@workday/canvas-tokens-web": "^2.1.0",
"typescript": "5.0"
}
}
diff --git a/package.json b/package.json
index 2465bb3188..34904cefea 100644
--- a/package.json
+++ b/package.json
@@ -138,7 +138,7 @@
"@workday/canvas-accent-icons-web": "^3.0.9",
"@workday/canvas-applet-icons-web": "^2.0.9",
"@workday/canvas-system-icons-web": "^3.0.0",
- "@workday/canvas-tokens-web": "^2.0.1",
+ "@workday/canvas-tokens-web": "^2.1.0",
"jest-environment-jsdom": "^29.7.0",
"ts-jest": "^29.2.4"
},
diff --git a/utils/storybook/customThemes.ts b/utils/storybook/customThemes.ts
index 16da8f120a..b93181a830 100644
--- a/utils/storybook/customThemes.ts
+++ b/utils/storybook/customThemes.ts
@@ -23,3 +23,30 @@ export const customColorTheme: PartialCanvasTheme = {
},
},
};
+
+export const customColorThemeWithAction: PartialCanvasTheme = {
+ palette: {
+ primary: {
+ main: 'purple',
+ contrast: 'turquoise',
+ },
+ action: {
+ main: 'teal',
+ },
+ alert: {
+ main: 'coral',
+ },
+ error: {
+ main: 'crimson',
+ },
+ success: {
+ main: 'aquamarine',
+ },
+ neutral: {
+ main: 'gray',
+ },
+ common: {
+ focusOutline: 'turquoise',
+ },
+ },
+};
diff --git a/yarn.lock b/yarn.lock
index 34fcc999f0..32ecdfef83 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6158,10 +6158,10 @@
dependencies:
"@workday/design-assets-types" "0.2.8"
-"@workday/canvas-tokens-web@^2.0.1":
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/@workday/canvas-tokens-web/-/canvas-tokens-web-2.0.1.tgz#b7d3df6849a869b0adf57e9b9ab924c6a4f778da"
- integrity sha512-PRfhnqFEe08kjn0vQPicfHh5zCCIYgunnCLYfSpVkwBX1OZHEFvCpB0pmz67P3313sty0/BmYWgzPmv3VT9mkA==
+"@workday/canvas-tokens-web@^2.1.0":
+ version "2.1.0"
+ resolved "https://registry.npmjs.org/@workday/canvas-tokens-web/-/canvas-tokens-web-2.1.0.tgz#3fa68416c003dd84a217909db556ab385360a34e"
+ integrity sha512-qDkhronvKRmautzHWLAlGYwI0UsrXmRN07EFCHPROFAbVglMdd1D3LIeP2ECtQ76qOcEVJM79vQJdwai4B9K7g==
"@workday/design-assets-types@0.2.10":
version "0.2.10"
@@ -15684,6 +15684,7 @@ string-replace-loader@^3.1.0:
schema-utils "^3.0.0"
"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.2.3:
+ name string-width-cjs
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -16987,6 +16988,7 @@ wordwrap@^1.0.0:
integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
+ name wrap-ansi-cjs
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==