` element.
---
-#### `onClick: (React.SyntheticEvent) => void`
+#### `as: 'div'`
-> A `click` event handler for this component.
+> Option to display the avatar as a div instead of a button.
diff --git a/modules/avatar/react/index.ts b/modules/avatar/react/index.ts
index 4233c96db5..38f9c1492f 100644
--- a/modules/avatar/react/index.ts
+++ b/modules/avatar/react/index.ts
@@ -2,5 +2,4 @@ import Avatar from './lib/Avatar';
export default Avatar;
export {Avatar};
-export {default as AvatarButton} from './lib/AvatarButton';
export * from './lib/Avatar';
diff --git a/modules/avatar/react/lib/Avatar.tsx b/modules/avatar/react/lib/Avatar.tsx
index d88414230d..3be7b389ae 100644
--- a/modules/avatar/react/lib/Avatar.tsx
+++ b/modules/avatar/react/lib/Avatar.tsx
@@ -1,5 +1,5 @@
-import * as React from 'react';
-import styled, {CSSObject} from '@emotion/styled';
+import React, {useState} from 'react';
+import {styled, focusRing, hideMouseFocus} from '@workday/canvas-kit-react-common';
import isPropValid from '@emotion/is-prop-valid';
import {borderRadius, colors} from '@workday/canvas-kit-react-core';
import {SystemIconCircle, SystemIconCircleSize} from '@workday/canvas-kit-react-icon';
@@ -10,80 +10,149 @@ export enum AvatarVariant {
Dark,
}
-export interface AvatarLocalProps {
+export interface AvatarProps extends React.ButtonHTMLAttributes
{
/**
* The variant of the Avatar default state. Accepts `Light` or `Dark`.
* @default AvatarVariant.Light
*/
- variant: AvatarVariant;
+ variant?: AvatarVariant;
/**
* The size of the Avatar.
* @default SystemIconCircleSize.m
*/
- size: SystemIconCircleSize | number;
+ size?: SystemIconCircleSize | number;
/**
- * The alt text of the Avatar image.
+ * The alt text of the Avatar image. This prop is also used for the aria-label
* @default Avatar
*/
- altText: string;
+ altText?: string;
/**
* The url of the Avatar image.
*/
url?: string;
+ /**
+ * The alternative container type for the button. Uses Emotion's special `as` prop.
+ * Will render an `div` tag instead of a `button` when defined.
+ */
+ as?: 'div';
}
-export interface AvatarProps extends AvatarLocalProps, React.HTMLAttributes {}
+/**
+ * Used to get the props of the div version of an avatar
+ */
+type AvatarDivProps = Omit> &
+ React.HTMLAttributes;
-export const avatarStyles: CSSObject = {
- background: colors.soap200,
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'center',
- padding: 0,
- border: 0,
- borderRadius: borderRadius.circle,
- boxSizing: 'border-box',
- overflow: 'hidden',
- '& img': {
- width: '100%',
- height: '100%',
- },
+/**
+ * Returns an overloaded functional component that uses button props by default.
+ */
+type AvatarOverload = {
+ (props: {as: 'div'} & AvatarDivProps & {ref?: React.Ref}): React.ReactElement;
+ (props: Omit & {ref?: React.Ref}): React.ReactElement;
+ Variant: typeof AvatarVariant;
+ Size: typeof SystemIconCircleSize;
};
-const StyledContainer = styled('div', {
+const StyledContainer = styled('button', {
shouldForwardProp: prop => isPropValid(prop) && prop !== 'size',
-})>(
+})>(
{
- ...avatarStyles,
+ background: colors.soap200,
+ position: 'relative',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ padding: 0,
+ border: 0,
+ boxSizing: 'border-box',
+ overflow: 'hidden',
+ borderRadius: borderRadius.circle,
+ '&:not([disabled])': {
+ '&:focus': {
+ outline: 'none',
+ ...focusRing({separation: 2}),
+ },
+ },
+ ...hideMouseFocus,
},
({size}) => ({
height: size,
width: size,
+ }),
+ ({onClick}) => ({
+ cursor: onClick ? 'pointer' : 'default',
})
);
-export default class Avatar extends React.Component {
- static Variant = AvatarVariant;
- static Size = SystemIconCircleSize;
+const StyledStack = styled('span')>(
+ {
+ position: 'absolute',
+ top: 0,
+ left: 0,
+ },
+ ({size}) => ({
+ height: size,
+ width: size,
+ })
+);
- static defaultProps = {
- variant: AvatarVariant.Light,
- size: SystemIconCircleSize.m,
- altText: 'Avatar',
- };
+const StyledImage = styled('img')<{isLoaded: boolean}>(
+ {
+ width: '100%',
+ height: '100%',
+ borderRadius: borderRadius.circle,
+ transition: 'opacity 150ms linear',
+ },
+ ({isLoaded}) => ({
+ opacity: isLoaded ? 1 : 0,
+ })
+);
- render() {
- const {variant, altText, size, url, ...elemProps} = this.props;
+const Avatar: AvatarOverload = React.forwardRef(
+ (
+ {
+ variant = AvatarVariant.Light,
+ size = SystemIconCircleSize.m,
+ altText = 'Avatar',
+ url,
+ onClick,
+ ...elemProps
+ }: AvatarProps,
+ ref: React.Ref
+ ) => {
+ const [imageLoaded, setImageLoaded] = useState(false);
+
+ const loadImage = () => {
+ if (!imageLoaded) {
+ setImageLoaded(true);
+ }
+ };
const background = variant === AvatarVariant.Dark ? colors.blueberry400 : colors.soap300;
+
return (
-
- {url ? (
-
- ) : (
+
+
+
+ {url && (
+
+
+
)}
);
}
-}
+) as any; // AvatarProps and forwardRef signatures are incompatible, so we must force cast
+
+Avatar.Variant = AvatarVariant;
+Avatar.Size = SystemIconCircleSize;
+
+export default Avatar;
diff --git a/modules/avatar/react/lib/AvatarButton.tsx b/modules/avatar/react/lib/AvatarButton.tsx
deleted file mode 100644
index 6d16163397..0000000000
--- a/modules/avatar/react/lib/AvatarButton.tsx
+++ /dev/null
@@ -1,75 +0,0 @@
-import * as React from 'react';
-import styled from '@emotion/styled';
-import {AvatarVariant, AvatarLocalProps, avatarStyles} from './Avatar';
-import {colors} from '@workday/canvas-kit-react-core';
-import {focusRing, hideMouseFocus} from '@workday/canvas-kit-react-common';
-import {SystemIconCircle, SystemIconCircleSize} from '@workday/canvas-kit-react-icon';
-import {userIcon} from '@workday/canvas-system-icons-web';
-
-export interface AvatarButtonProps
- extends AvatarLocalProps,
- React.ButtonHTMLAttributes {
- /**
- * The function called when the AvatarButton is clicked.
- */
- onClick?: React.MouseEventHandler;
- /**
- * The ref to the button that the styled component renders.
- */
- buttonRef?: React.Ref;
-}
-
-const AvatarAsButton = styled('button')(
- {...avatarStyles},
- ({size}) => ({
- height: size,
- width: size,
- }),
- ({variant, onClick}) => ({
- cursor: onClick ? 'pointer' : 'default',
- '&:not([disabled])': {
- '&:focus': {
- outline: 'none',
- ...(variant === AvatarVariant.Dark ? focusRing(2, 2) : focusRing(2)),
- },
- },
- ...hideMouseFocus,
- })
-);
-
-export default class AvatarButton extends React.Component {
- static Variant = AvatarVariant;
- static Size = SystemIconCircleSize;
-
- static defaultProps = {
- variant: AvatarVariant.Light,
- size: SystemIconCircleSize.m,
- altText: 'Avatar',
- };
-
- render() {
- const {buttonRef, variant, altText, size, url, onClick, ...elemProps} = this.props;
-
- const background = variant === AvatarVariant.Dark ? colors.blueberry400 : colors.soap300;
-
- return (
-
- {url ? (
-
- ) : (
-
- )}
-
- );
- }
-}
diff --git a/modules/avatar/react/package.json b/modules/avatar/react/package.json
index 6a027f2536..468c0b8660 100644
--- a/modules/avatar/react/package.json
+++ b/modules/avatar/react/package.json
@@ -1,6 +1,6 @@
{
"name": "@workday/canvas-kit-react-avatar",
- "version": "3.9.0",
+ "version": "4.0.0",
"description": "A circular user photo (or a default)",
"author": "Workday, Inc. (https://www.workday.com)",
"license": "Apache-2.0",
@@ -15,16 +15,25 @@
"files": [
"dist/",
"lib/",
- "index.ts"
+ "index.ts",
+ "ts3.5/**/*"
],
+ "typesVersions": {
+ "<=3.5": {
+ "*": [
+ "ts3.5/*"
+ ]
+ }
+ },
"scripts": {
"watch": "yarn build:es6 -w",
"test": "echo \"Error: no test specified\" && exit 1",
- "clean": "rimraf dist && rimraf .build-info && mkdirp dist",
+ "clean": "rimraf dist && rimraf ts3.5 && rimraf .build-info && mkdirp dist && mkdirp ts3.5/dist",
"build:cjs": "tsc -p tsconfig.cjs.json",
"build:es6": "tsc -p tsconfig.es6.json",
"build:rebuild": "npm-run-all clean build",
- "build": "npm-run-all --parallel build:cjs build:es6",
+ "build:downlevel-dts": "yarn run downlevel-dts dist ts3.5/dist",
+ "build": "npm-run-all --parallel build:cjs build:es6 --sequential build:downlevel-dts",
"depcheck": "node ../../../utils/check-dependencies-exist.js"
},
"keywords": [
@@ -41,13 +50,12 @@
"dependencies": {
"@emotion/core": "^10.0.28",
"@emotion/is-prop-valid": "^0.8.2",
- "@emotion/styled": "^10.0.27",
- "@workday/canvas-kit-react-common": "^3.9.0",
- "@workday/canvas-kit-react-core": "^3.9.0",
- "@workday/canvas-kit-react-icon": "^3.9.0",
+ "@workday/canvas-kit-react-common": "^4.0.0",
+ "@workday/canvas-kit-react-core": "^4.0.0",
+ "@workday/canvas-kit-react-icon": "^4.0.0",
"@workday/canvas-system-icons-web": "^1.0.20"
},
"devDependencies": {
- "@workday/canvas-kit-labs-react-core": "^3.9.0"
+ "@workday/canvas-kit-labs-react-core": "^4.0.0"
}
}
diff --git a/modules/avatar/react/spec/Avatar.spec.tsx b/modules/avatar/react/spec/Avatar.spec.tsx
index b32f8594bb..ef9a7d1d6a 100644
--- a/modules/avatar/react/spec/Avatar.spec.tsx
+++ b/modules/avatar/react/spec/Avatar.spec.tsx
@@ -1,43 +1,51 @@
import * as React from 'react';
import Avatar from '../lib/Avatar';
-import {mount} from 'enzyme';
-import ReactDOMServer from 'react-dom/server';
-import {axe} from 'jest-axe';
+import {render, fireEvent} from '@testing-library/react';
describe('Avatar', () => {
- test('render a div with id', () => {
- const component = mount( );
- const container = component.at(0).getDOMNode();
- expect(container.getAttribute('id')).toBe('myAvatar');
- component.unmount();
+ it('should render a button element', () => {
+ const screen = render( );
+
+ expect(screen.getByRole('button')).toBeInTheDocument();
});
- test('Avatar should spread extra props', () => {
- const component = mount( );
- const container = component.at(0).getDOMNode();
- expect(container.getAttribute('data-propspread')).toBe('test');
- component.unmount();
+ it('should forward extra attributes to the container', () => {
+ const screen = render( );
+
+ expect(screen.getByRole('button')).toHaveAttribute('id', 'myAvatar');
+ });
+
+ it('should set the aria-label of the button with the altText prop', () => {
+ const screen = render( );
+
+ expect(screen.getByRole('button')).toHaveAttribute('aria-label', 'My alt text');
});
-});
-describe('Avatar Accessibility', () => {
- test('Avatar should be using tag', () => {
- const component = mount(
);
- expect(component.getDOMNode().tagName.toLowerCase()).toEqual('div');
- component.unmount();
+ it('should set the url of the image when passed the url prop', () => {
+ const screen = render(
);
+
+ expect(screen.getByRole('img')).toHaveAttribute('src', 'https://example.com/image.png');
+ expect(screen.getByRole('img')).toHaveAttribute('alt', 'My alt text');
+ });
+
+ it('should forward ref to the button element', () => {
+ const ref = React.createRef
();
+ const screen = render( );
+
+ expect(ref.current).toEqual(screen.getByRole('button'));
});
- test('Avatar should pass axe DOM accessibility guidelines', async () => {
- const html = ReactDOMServer.renderToString( );
- expect(await axe(html)).toHaveNoViolations();
+ it('should call onClick callback when clicked', () => {
+ const fn = jest.fn();
+ const screen = render( );
+
+ fireEvent.click(screen.getByRole('button'));
+ expect(fn).toBeCalled();
});
- test('Avatar with image should pass axe DOM accessibility guidelines', async () => {
- const html = ReactDOMServer.renderToString(
-
- );
- expect(await axe(html)).toHaveNoViolations();
+ it('should render a div when as prop is specified', () => {
+ const screen = render( );
+
+ expect(screen.getByTestId('test').tagName.toLowerCase()).toEqual('div');
});
});
diff --git a/modules/avatar/react/spec/AvatarButton.spec.tsx b/modules/avatar/react/spec/AvatarButton.spec.tsx
deleted file mode 100644
index 861774f2f4..0000000000
--- a/modules/avatar/react/spec/AvatarButton.spec.tsx
+++ /dev/null
@@ -1,83 +0,0 @@
-import * as React from 'react';
-import AvatarButton from '../lib/AvatarButton';
-import {mount} from 'enzyme';
-import ReactDOMServer from 'react-dom/server';
-import {axe} from 'jest-axe';
-
-describe('AvatarButton', () => {
- const cb = jest.fn();
- afterEach(() => {
- cb.mockReset();
- });
-
- test('render a button with id', () => {
- const component = mount( );
- expect(component.find('button').props().id).toBe('myAvatarButton');
- component.unmount();
- });
-
- test('AvatarButton without onClick props should have disabled attribute set', () => {
- const component = mount( );
- expect(component.find('button').props().id).toBe('myAvatarButton');
- expect(
- component
- .find('button')
- .getDOMNode()
- .hasAttribute('disabled')
- ).toEqual(true);
- component.unmount();
- });
-
- test('should call a callback function', () => {
- const component = mount( );
- const avatar = component.find('button');
- avatar.simulate('click');
- expect(cb.mock.calls.length).toBe(1);
- component.unmount();
- });
-
- test('AvatarButton with onClick props should NOT have disabled attribute set', () => {
- const component = mount( );
- expect(
- component
- .find('button')
- .getDOMNode()
- .hasAttribute('disabled')
- ).toEqual(false);
- component.unmount();
- });
-
- test('AvatarButton should spread extra props', () => {
- const component = mount( );
- const container = component.at(0).getDOMNode();
- expect(container.getAttribute('data-propspread')).toBe('test');
- component.unmount();
- });
-});
-
-describe('AvatarButton Accessibility', () => {
- const cb = jest.fn();
- afterEach(() => {
- cb.mockReset();
- });
-
- test('AvatarButton should be using HTML5 tag', () => {
- const component = mount( );
- expect(component.getDOMNode().tagName.toLowerCase()).toEqual('button');
- component.unmount();
- });
-
- test('AvatarButton should pass axe DOM accessibility guidelines', async () => {
- const html = ReactDOMServer.renderToString( );
- expect(await axe(html)).toHaveNoViolations();
- });
-
- test('AvatarButton with image should pass axe DOM accessibility guidelines', async () => {
- const html = ReactDOMServer.renderToString(
-
- );
- expect(await axe(html)).toHaveNoViolations();
- });
-});
diff --git a/modules/avatar/react/stories/stories.tsx b/modules/avatar/react/stories/stories.tsx
index 38ed1237b9..331fa2cea9 100644
--- a/modules/avatar/react/stories/stories.tsx
+++ b/modules/avatar/react/stories/stories.tsx
@@ -3,16 +3,14 @@ import * as React from 'react';
import {storiesOf} from '@storybook/react';
import withReadme from 'storybook-readme/with-readme';
-import Avatar, {AvatarButton} from '../index';
+import Avatar from '../index';
import README from '../README.md';
import {withKnobs} from '@storybook/addon-knobs';
-
+import {action} from '@storybook/addon-actions';
// @ts-ignore: Cannot find module error
import testAvatar from './test-avatar.png';
-const handleAvatarButtonClick = (e: React.SyntheticEvent) => {
- console.log('AvatarButton clicked');
-};
+const handleAvatarButtonClick = action('AvatarButton clicked');
storiesOf('Components|Indicators/Avatar/React/Default', module)
.addDecorator(withReadme(README))
@@ -21,108 +19,108 @@ storiesOf('Components|Indicators/Avatar/React/Default', module)
.add('Light', () => (
Extra-Extra Large
-
+
Extra Large
-
+
Large
-
+
Medium
-
+
Small
-
+
Extra Small
-
+
))
.add('Dark', () => (
Extra-Extra Large
-
+
Extra Large
-
+
Large
-
+
Medium
-
+
Small
-
+
Extra Small
-
+
))
.add('Image', () => (
Extra-Extra Large
-
+
Extra Large
-
+
Large
-
+
Medium
-
+
Small
-
+
Extra Small
-
+
));
storiesOf('Components|Indicators/Avatar/React/Avatar Button', module)
.addDecorator(withReadme(README))
.addDecorator(withKnobs)
- .addParameters({component: AvatarButton})
+ .addParameters({component: Avatar})
.add('Light', () => (
Extra-Extra Large
-
+
Extra Large
-
+
Large
-
+
Medium
-
+
Small
-
+
Extra Small
-
+
))
.add('Dark', () => (
Extra-Extra Large
-
Extra Large
-
Large
-
Medium
-
Small
-
Extra Small
-
@@ -130,28 +128,18 @@ storiesOf('Components|Indicators/Avatar/React/Avatar Button', module)
.add('Image', () => (
Extra-Extra Large
-
+
Extra Large
-
+
Large
-
+
Medium
-
+
Small
-
+
Extra Small
-
+
+
Broken Link
+
));
diff --git a/modules/avatar/react/stories/stories_visualTesting.tsx b/modules/avatar/react/stories/stories_visualTesting.tsx
index 4e481f8826..2fc73c6894 100644
--- a/modules/avatar/react/stories/stories_visualTesting.tsx
+++ b/modules/avatar/react/stories/stories_visualTesting.tsx
@@ -7,7 +7,7 @@ import {
permutateProps,
withSnapshotsEnabled,
} from '../../../../utils/storybook';
-import {Avatar, AvatarButton} from '../index';
+import {Avatar} from '../index';
// @ts-ignore: Cannot find module error
import testAvatar from './test-avatar.png';
@@ -51,10 +51,13 @@ export const AvatarStates = () => (
{label: 'XL', value: Avatar.Size.xl},
{label: 'XXL', value: Avatar.Size.xxl},
],
- url: [{value: undefined, label: 'Placeholder'}, {value: testAvatar, label: 'Image'}],
+ url: [
+ {value: undefined, label: 'Placeholder'},
+ {value: testAvatar, label: 'Image'},
+ ],
})}
>
- {props => }
+ {props => }
);
@@ -85,10 +88,13 @@ export const AvatarButtonStates = () => (
{label: 'XL', value: Avatar.Size.xl},
{label: 'XXL', value: Avatar.Size.xxl},
],
- url: [{value: undefined, label: 'Placeholder'}, {value: testAvatar, label: 'Image'}],
+ url: [
+ {value: undefined, label: 'Placeholder'},
+ {value: testAvatar, label: 'Image'},
+ ],
})}
>
- {props => }
+ {props => }
);
diff --git a/modules/badge/css/package.json b/modules/badge/css/package.json
index fd058cdd97..d0772e1caa 100644
--- a/modules/badge/css/package.json
+++ b/modules/badge/css/package.json
@@ -1,6 +1,6 @@
{
"name": "@workday/canvas-kit-css-badge",
- "version": "3.8.0",
+ "version": "4.0.0",
"description": "provides a summary indicator with dynamic values",
"author": "Workday, Inc. (https://www.workday.com)",
"license": "Apache-2.0",
diff --git a/modules/badge/react/package.json b/modules/badge/react/package.json
index 66727634c1..3c1b481ec2 100644
--- a/modules/badge/react/package.json
+++ b/modules/badge/react/package.json
@@ -1,6 +1,6 @@
{
"name": "@workday/canvas-kit-react-badge",
- "version": "3.9.0",
+ "version": "4.0.0",
"description": "provides a summary indicator with dynamic values",
"author": "Workday, Inc. (https://www.workday.com)",
"license": "Apache-2.0",
@@ -44,6 +44,6 @@
"dependencies": {
"@emotion/core": "^10.0.28",
"@emotion/styled": "^10.0.27",
- "@workday/canvas-kit-react-core": "^3.9.0"
+ "@workday/canvas-kit-react-core": "^4.0.0"
}
}
diff --git a/modules/badge/react/stories/stories_visualTesting.tsx b/modules/badge/react/stories/stories_visualTesting.tsx
index cfb0351625..56cd3a7876 100644
--- a/modules/badge/react/stories/stories_visualTesting.tsx
+++ b/modules/badge/react/stories/stories_visualTesting.tsx
@@ -25,7 +25,10 @@ export const CountBadgeStates = () => {
props: {count: 1000},
},
]}
- rowProps={[{label: 'Default', props: {}}, {label: 'Inverse', props: {variant: 'inverse'}}]}
+ rowProps={[
+ {label: 'Default', props: {}},
+ {label: 'Inverse', props: {variant: 'inverse'}},
+ ]}
>
{props => }
diff --git a/modules/banner/css/package.json b/modules/banner/css/package.json
index a47de1f879..8f494531a0 100644
--- a/modules/banner/css/package.json
+++ b/modules/banner/css/package.json
@@ -1,6 +1,6 @@
{
"name": "@workday/canvas-kit-css-banner",
- "version": "3.8.0",
+ "version": "4.0.0",
"description": "Errors and alerts for canvas-kit-css",
"author": "Workday, Inc. (https://www.workday.com)",
"license": "Apache-2.0",
@@ -16,7 +16,7 @@
"url": "http://github.com/Workday/canvas-kit/tree/master/modules/banner/css"
},
"dependencies": {
- "@workday/canvas-kit-css-core": "^3.8.0",
+ "@workday/canvas-kit-css-core": "^4.0.0",
"@workday/canvas-system-icons-web": "^1.0.20"
},
"scripts": {
diff --git a/modules/banner/react/README.md b/modules/banner/react/README.md
index 4b47819059..cf7606d36e 100644
--- a/modules/banner/react/README.md
+++ b/modules/banner/react/README.md
@@ -49,7 +49,7 @@ import Banner from '@workday/canvas-kit-react-banner';
### Optional
-#### `onClick: (e: React.SyntheticEvent) => void`
+#### `onClick: (e: React.MouseEvent) => void`
> Function called when the user click on the banner
diff --git a/modules/banner/react/lib/Banner.tsx b/modules/banner/react/lib/Banner.tsx
index 44b3937c5c..1dfa18b6b7 100644
--- a/modules/banner/react/lib/Banner.tsx
+++ b/modules/banner/react/lib/Banner.tsx
@@ -2,8 +2,7 @@ import * as React from 'react';
import {colors, spacing, borderRadius, type} from '@workday/canvas-kit-react-core';
import {SystemIcon} from '@workday/canvas-kit-react-icon';
import {exclamationCircleIcon, exclamationTriangleIcon} from '@workday/canvas-system-icons-web';
-import {ErrorType, focusRing} from '@workday/canvas-kit-react-common';
-import {styled, Themeable} from '@workday/canvas-kit-labs-react-core';
+import {ErrorType, focusRing, styled, Themeable} from '@workday/canvas-kit-react-common';
export enum BannerVariant {
Full,
@@ -14,7 +13,7 @@ export interface BannerProps extends Themeable, React.ButtonHTMLAttributes void;
+ onClick?: (e: React.MouseEvent) => void;
/**
* The label of the Banner.
*/
@@ -30,7 +29,7 @@ export interface BannerProps extends Themeable, React.ButtonHTMLAttributes(
transition: 'background-color 120ms',
'&:focus': {
outline: 'none',
- ...focusRing(2, 2),
+ ...focusRing({separation: 2}),
},
'&:hover': {
cursor: 'pointer',
@@ -91,14 +90,15 @@ export default class Banner extends React.Component {
static Variant = BannerVariant;
static ErrorType = ErrorType;
- public static defaultProps = {
- actionText: 'View All',
- error: ErrorType.Alert,
- variant: BannerVariant.Full,
- };
-
public render() {
- const {label, onClick, actionText, variant, error, ...props} = this.props;
+ const {
+ actionText = 'View All',
+ variant = BannerVariant.Full,
+ error = ErrorType.Alert,
+ label,
+ onClick,
+ ...props
+ } = this.props;
const bannerIcon = error === ErrorType.Error ? exclamationCircleIcon : exclamationTriangleIcon;
const iconColor = error === ErrorType.Error ? colors.frenchVanilla100 : colors.blackPepper400;
diff --git a/modules/banner/react/package.json b/modules/banner/react/package.json
index 8becbbb831..ef7b25ce27 100644
--- a/modules/banner/react/package.json
+++ b/modules/banner/react/package.json
@@ -1,6 +1,6 @@
{
"name": "@workday/canvas-kit-react-banner",
- "version": "3.9.0",
+ "version": "4.0.0",
"description": "Common Canvas banner component for React",
"author": "Workday, Inc. (https://www.workday.com)",
"license": "Apache-2.0",
@@ -15,16 +15,25 @@
"files": [
"dist/",
"lib/",
- "index.ts"
+ "index.ts",
+ "ts3.5/**/*"
],
+ "typesVersions": {
+ "<=3.5": {
+ "*": [
+ "ts3.5/*"
+ ]
+ }
+ },
"scripts": {
"watch": "yarn build:es6 -w",
"test": "echo \"Error: no test specified\" && exit 1",
- "clean": "rimraf dist && rimraf .build-info && mkdirp dist",
+ "clean": "rimraf dist && rimraf ts3.5 && rimraf .build-info && mkdirp dist && mkdirp ts3.5/dist",
"build:cjs": "tsc -p tsconfig.cjs.json",
"build:es6": "tsc -p tsconfig.es6.json",
"build:rebuild": "npm-run-all clean build",
- "build": "npm-run-all --parallel build:cjs build:es6",
+ "build:downlevel-dts": "yarn run downlevel-dts dist ts3.5/dist",
+ "build": "npm-run-all --parallel build:cjs build:es6 --sequential build:downlevel-dts",
"depcheck": "node ../../../utils/check-dependencies-exist.js"
},
"keywords": [
@@ -39,10 +48,9 @@
"react": ">= 16.8 < 17"
},
"dependencies": {
- "@workday/canvas-kit-labs-react-core": "^3.9.0",
- "@workday/canvas-kit-react-common": "^3.9.0",
- "@workday/canvas-kit-react-core": "^3.9.0",
- "@workday/canvas-kit-react-icon": "^3.9.0",
+ "@workday/canvas-kit-react-common": "^4.0.0",
+ "@workday/canvas-kit-react-core": "^4.0.0",
+ "@workday/canvas-kit-react-icon": "^4.0.0",
"@workday/canvas-system-icons-web": "^1.0.20"
}
}
diff --git a/modules/banner/react/spec/Banner.spec.tsx b/modules/banner/react/spec/Banner.spec.tsx
index de2474cbc2..5ad42202fd 100644
--- a/modules/banner/react/spec/Banner.spec.tsx
+++ b/modules/banner/react/spec/Banner.spec.tsx
@@ -16,6 +16,12 @@ describe('Banner', () => {
describe('Banner Accessibility', () => {
test('Banner should pass axe DOM accessibility guidelines', async () => {
const html = ReactDOMServer.renderToString( );
- expect(await axe(html)).toHaveNoViolations();
+ expect(
+ await axe(html, {
+ rules: {
+ region: {enabled: false},
+ },
+ })
+ ).toHaveNoViolations();
});
});
diff --git a/modules/banner/react/stories/stories.tsx b/modules/banner/react/stories/stories.tsx
index b1c6c3afe2..458fa20225 100644
--- a/modules/banner/react/stories/stories.tsx
+++ b/modules/banner/react/stories/stories.tsx
@@ -7,7 +7,7 @@ import withReadme from 'storybook-readme/with-readme';
import Banner from '../index';
import README from '../README.md';
-const handleBannerClick = (e: React.SyntheticEvent) => {
+const handleBannerClick = () => {
alert(`onClick triggered`);
};
diff --git a/modules/button/css/README.md b/modules/button/css/README.md
index 86705b69c7..9a7cde98c6 100644
--- a/modules/button/css/README.md
+++ b/modules/button/css/README.md
@@ -47,10 +47,10 @@ to use the class name `wdc-btn-deprecated` for the old styling. Note: this will
in a future release.
```html
-Primary
+Secondary
Primary
-Primary
-Primary
+Delete
+Split
```
---
@@ -284,7 +284,9 @@ Apply primary styling to the button by adding `.wdc-btn-primary` to the buttons
```
### Text Button
-> Use by adding `.wdc-btn-text` when you want text to behave like a button. If you want display text on a dark background you can use `.wdc-btn-text-inverse`.
+
+> Use by adding `.wdc-btn-text` when you want text to behave like a button. If you want display text
+> on a dark background you can use `.wdc-btn-text-inverse`.
```html
Text Button
@@ -293,7 +295,10 @@ Apply primary styling to the button by adding `.wdc-btn-primary` to the buttons
```
### Inline Icon
-You can use a [System Icon](../../icon/css/README.md) either before or after the text of Primary, Secondary, Delete and Text buttons. The class `.wdc-icon-position-left` (default) or `.wdc-icon-position-right` adjust the margin of the icon.
+
+You can use a [System Icon](../../icon/css/README.md) either before or after the text of Primary,
+Secondary, Delete and Text buttons. The class `.wdc-icon-position-left` (default) or
+`.wdc-icon-position-right` adjust the margin of the icon.
`System Icon` requires a JavaScript import for the injector:
@@ -309,11 +314,11 @@ initializeIcons();
- Continue
+ Continue
+
```
-
**Accessibility note**: When an attached menu is displayed, add `aria-expanded="true"` to the
button.
diff --git a/modules/button/css/lib/text-button.scss b/modules/button/css/lib/text-button.scss
index 2a799c0d5d..7d4aa68c98 100644
--- a/modules/button/css/lib/text-button.scss
+++ b/modules/button/css/lib/text-button.scss
@@ -25,17 +25,23 @@
}
&:hover:not([disabled]) {
- background-color: transparent;
+ background-color: $wdc-color-soap-200;
text-decoration: underline;
color: $wdc-btn-text-hover;
@include wdc-icon-color($wdc-btn-text-hover);
}
- &:focus {
+ &:focus:not([disabled]) {
background-color: transparent;
@include wdc-icon-color($wdc-btn-text-hover);
}
+ &:active:not([disabled]) {
+ color: $wdc-btn-text-hover;
+ background-color: $wdc-color-soap-300;
+ @include wdc-icon-color($wdc-btn-text-hover);
+ }
+
&.wdc-btn-disabled,
&.wdc-btn-disabled:hover,
&:disabled,
@@ -69,9 +75,16 @@
@include wdc-icon-color(rgba(255, 255, 255, 0.5));
}
- &:hover:not([disabled]) {
- color: $wdc-btn-text-color-inverse;
- @include wdc-icon-color($wdc-btn-text-color-inverse);
+ &:not([disabled]) {
+ &:hover {
+ background-color: $wdc-color-french-vanilla-100;
+ color: $wdc-color-black-pepper-400;
+ @include wdc-icon-color($wdc-btn-text-color-inverse);
+ }
+ &:active {
+ color: $wdc-color-black-pepper-400;
+ background-color: $wdc-color-soap-200;
+ }
}
}
}
diff --git a/modules/button/css/package.json b/modules/button/css/package.json
index 8693167804..490d100428 100644
--- a/modules/button/css/package.json
+++ b/modules/button/css/package.json
@@ -1,6 +1,6 @@
{
"name": "@workday/canvas-kit-css-button",
- "version": "3.8.0",
+ "version": "4.0.0",
"description": "The button css for canvas-kit-css",
"author": "Workday, Inc. (https://www.workday.com)",
"license": "Apache-2.0",
@@ -16,7 +16,7 @@
"url": "http://github.com/Workday/canvas-kit/tree/master/modules/button/css"
},
"dependencies": {
- "@workday/canvas-kit-css-core": "^3.8.0",
+ "@workday/canvas-kit-css-core": "^4.0.0",
"@workday/canvas-system-icons-web": "^1.0.20"
},
"scripts": {
diff --git a/modules/button/react/README.md b/modules/button/react/README.md
index e43fe5fcd4..80b83f8f12 100644
--- a/modules/button/react/README.md
+++ b/modules/button/react/README.md
@@ -22,15 +22,13 @@ yarn add @workday/canvas-kit-react-button
> primary, and accompanying secondary, and delete). These are still avialable, but will be removed
> in the first major release after they are available for all Workday customers. The biggest change
> is with regards to colors and styling, but the behavior should remain the same.
-
-### New Button
-
-Anywhere you were using `Button`, you will automatically get the updated styling (previously
-`beta_Button`). This will be a visual breaking change (padding and colors have changed). Note, we
-are still supporting the import for `beta_Button` as well. However, if you are using
-`import {beta_Button as Button}...` you can remove it now too since this too will be removed in a
-future release. The new buttons include: blue primary button, and accompanying secondary, delete,
-outline, and dropdown buttons. The import and usage is documented below.
+>
+> ### New Button
+>
+> Anywhere you were using `Button`, you will automatically get the updated styling (previously
+> `beta_Button`). This will be a visual breaking change (padding and colors have changed). The new
+> buttons include: blue primary button, and accompanying secondary, delete, outline, highlight, and
+> dropdown buttons. The import and usage is documented below.
### Deprecated Buttons
@@ -51,7 +49,22 @@ able to compile your code.
---
-## Button
+## Table of Contents
+
+- [Button](#button)
+- [DeleteButton](#deletebutton)
+- [DropdownButton](#dropdownbutton)
+- [HighlightButton](#highlightbutton)
+- [OutlineButton](#outlinebutton)
+- [TextButton](#textbutton)
+- [Hyperlink](#hyperlink)
+- [IconButton](#iconbutton)
+- [ToolbarIconButton](#toolbariconbutton)
+- [ToolbarDropdownButton](#toolbardropdownbutton)
+
+---
+
+# Button
```tsx
import * as React from 'react';
@@ -62,7 +75,7 @@ import {Button} from '@workday/canvas-kit-react-button';
## Static Properties
-#### `Sizes: ButtonSize`
+#### `Size: 'small' | 'medium' | 'large'`
```tsx
Small Button
@@ -70,7 +83,7 @@ import {Button} from '@workday/canvas-kit-react-button';
---
-#### `Types: ButtonVariant`
+#### `Variant: ButtonVariant`
```tsx
Primary Button
@@ -96,21 +109,20 @@ Default: `ButtonVariant.Secondary`
| ----------- | ------------------------------- |
| `Primary` | Blue background, white text |
| `Secondary` | Gray background, dark gray text |
-| `Delete` | Red background, dark text |
---
-#### `size: ButtonSize`
+#### `size: 'small' | 'medium' | 'large'`
> The size of the button
-Default: `ButtonSize.Large`
+Default: `'medium'`
| Theme | Description |
| -------- | -------------------------------------- |
-| `Small` | 18px tall, small padding, small text |
-| `Medium` | 24px tall, medium padding, medium text |
-| `Large` | 40px tall, large padding, larger text |
+| `small` | 24px tall, small padding, small text |
+| `medium` | 32px tall, medium padding, medium text |
+| `large` | 48px tall, large padding, larger text |
---
@@ -126,7 +138,511 @@ Default: `false`
> Returns the ref to the rendered HTMLButtonElement.
-# Icon Button
+---
+
+#### `dataLabel: String`
+
+> The data label of the button (generally used for media timestamps).
+>
+> Note: not displayed at `small` size.
+
+---
+
+### `icon: CanvasSystemIcon`
+
+> The icon of the button.
+>
+> Note: not displayed at `small` size.
+
+---
+
+### `as: 'a'`
+
+> The alternative container type for the button. If `as="a"` is provided, We use Emotion's special
+> `as` prop to render an `a` tag instead of a `button`.
+
+> When defined, all props available via `React.AnchorHTMLAttributes` (e.g.
+> `href`, `target`, etc.) become available.
+
+Default: `undefined`
+
+---
+
+# DeleteButton
+
+```tsx
+import * as React from 'react';
+import {DeleteButton} from '@workday/canvas-kit-react-button';
+
+Button Label ;
+```
+
+## Static Properties
+
+#### `Size: 'small' | 'medium' | 'large'`
+
+```tsx
+Small Button
+```
+
+## Component Props
+
+### Required
+
+#### `children: ReactNode`
+
+> Buttons cannot be empty
+
+### Optional
+
+#### `size: 'small' | 'medium' | 'large'`
+
+> The size of the button
+
+Default: `'medium'`
+
+| Theme | Description |
+| -------- | -------------------------------------- |
+| `small` | 24px tall, small padding, small text |
+| `medium` | 32px tall, medium padding, medium text |
+| `large` | 48px tall, large padding, larger text |
+
+---
+
+#### `buttonRef: React.Ref`
+
+> Returns the ref to the rendered HTMLButtonElement.
+
+---
+
+#### `grow: boolean`
+
+> If true, the button will grow to its container's width.
+
+Default: `false`
+
+---
+
+### `as: 'a'`
+
+> The alternative container type for the button. If `as="a"` is provided, We use Emotion's special
+> `as` prop to render an `a` tag instead of a `button`.
+
+> When defined, all props available via `React.AnchorHTMLAttributes` (e.g.
+> `href`, `target`, etc.) become available.
+
+Default: `undefined`
+
+---
+
+# DropdownButton
+
+```tsx
+import * as React from 'react';
+import {DropdownButton} from '@workday/canvas-kit-react-button';
+
+Button Label ;
+```
+
+## Static Properties
+
+#### `Size: 'medium' | 'large'`
+
+```tsx
+Large Button
+```
+
+---
+
+#### `Variant: DropdownButtonVariant`
+
+```tsx
+Primary Button
+```
+
+## Component Props
+
+### Required
+
+#### `children: ReactNode`
+
+> Buttons cannot be empty
+
+### Optional
+
+#### `variant: ButtonVariant`
+
+> The type of the button
+
+Default: `DropdownButtonVariant.Secondary`
+
+| Theme | Description |
+| ----------- | ------------------------------------ |
+| `Primary` | Blue background, white text/icon |
+| `Secondary` | Gray background, dark gray text/icon |
+
+---
+
+#### `size: 'medium' | 'large'`
+
+> The size of the button
+
+Default: `'medium'`
+
+| Theme | Description |
+| -------- | -------------------------------------- |
+| `medium` | 32px tall, medium padding, medium text |
+| `large` | 48px tall, large padding, larger text |
+
+---
+
+#### `grow: boolean`
+
+> If true, the button will grow to its container's width.
+
+Default: `false`
+
+---
+
+#### `buttonRef: React.Ref`
+
+> Returns the ref to the rendered HTMLButtonElement.
+
+---
+
+### `as: 'a'`
+
+> The alternative container type for the button. If `as="a"` is provided, We use Emotion's special
+> `as` prop to render an `a` tag instead of a `button`.
+
+> When defined, all props available via `React.AnchorHTMLAttributes` (e.g.
+> `href`, `target`, etc.) become available.
+
+Default: `undefined`
+
+---
+
+# HighlightButton
+
+```tsx
+import * as React from 'react';
+import {HighlightButton} from '@workday/canvas-kit-react-button';
+
+Button Label ;
+```
+
+## Static Properties
+
+#### `Size: 'medium' | 'large'`
+
+```tsx
+Large Button
+```
+
+## Component Props
+
+### Required
+
+#### `children: ReactNode`
+
+> Buttons cannot be empty
+
+### Optional
+
+#### `size: 'medium' | 'large'`
+
+> The size of the button
+
+Default: `'medium'`
+
+| Theme | Description |
+| -------- | -------------------------------------- |
+| `medium` | 32px tall, medium padding, medium text |
+| `large` | 48px tall, large padding, larger text |
+
+---
+
+#### `grow: boolean`
+
+> If true, the button will grow to its container's width.
+
+Default: `false`
+
+---
+
+#### `buttonRef: React.Ref`
+
+> Returns the ref to the rendered HTMLButtonElement.
+
+---
+
+### `icon: CanvasSystemIcon`
+
+> The icon of the button
+
+---
+
+### `as: 'a'`
+
+> The alternative container type for the button. If `as="a"` is provided, We use Emotion's special
+> `as` prop to render an `a` tag instead of a `button`.
+
+> When defined, all props available via `React.AnchorHTMLAttributes` (e.g.
+> `href`, `target`, etc.) become available.
+
+Default: `undefined`
+
+---
+
+# OutlineButton
+
+```tsx
+import * as React from 'react';
+import {OutlineButton} from '@workday/canvas-kit-react-button';
+
+Button Label ;
+```
+
+## Static Properties
+
+#### `Size: 'small' | 'medium' | 'large'`
+
+```tsx
+Small Button
+```
+
+---
+
+#### `Variant: OutlineButtonVariant`
+
+```tsx
+Primary Button
+```
+
+## Component Props
+
+### Required
+
+#### `children: ReactNode`
+
+> Buttons cannot be empty
+
+### Optional
+
+#### `variant: ButtonVariant`
+
+> The type of the button
+
+Default: `OutlineButtonVariant.Secondary`
+
+| Theme | Description |
+| ----------- | --------------------------------------------- |
+| `Primary` | Transparent background, blue border and text |
+| `Secondary` | Transparent background, gray border and text |
+| `Inverse` | Transparent background, white border and text |
+
+---
+
+#### `size: 'small' | 'medium' | 'large'`
+
+> The size of the button
+
+Default: `'medium'`
+
+| Theme | Description |
+| -------- | -------------------------------------- |
+| `small` | 24px tall, small padding, small text |
+| `medium` | 32px tall, medium padding, medium text |
+| `large` | 48px tall, large padding, larger text |
+
+---
+
+#### `grow: boolean`
+
+> If true, the button will grow to its container's width.
+
+Default: `false`
+
+---
+
+#### `buttonRef: React.Ref`
+
+> Returns the ref to the rendered HTMLButtonElement.
+
+---
+
+#### `dataLabel: String`
+
+> The data label of the button (generally used for media timestamps)
+>
+> Note: not displayed at `small` size.
+
+---
+
+### `icon: CanvasSystemIcon`
+
+> The icon of the button
+>
+> Note: not displayed at `small` size.
+
+---
+
+### `as: 'a'`
+
+> The alternative container type for the button. If `as="a"` is provided, We use Emotion's special
+> `as` prop to render an `a` tag instead of a `button`.
+
+> When defined, all props available via `React.AnchorHTMLAttributes` (e.g.
+> `href`, `target`, etc.) become available.
+
+Default: `undefined`
+
+---
+
+# TextButton
+
+```tsx
+import * as React from 'react';
+import {TextButton} from '@workday/canvas-kit-react-button';
+
+Button Label ;
+```
+
+## Static Properties
+
+#### `Size: 'small' | 'medium'`
+
+```tsx
+Small Button
+```
+
+---
+
+#### `Variant: ButtonVariant`
+
+```tsx
+Inverse Button
+```
+
+## Component Props
+
+### Required
+
+#### `children: ReactNode`
+
+> Buttons cannot be empty
+
+### Optional
+
+#### `variant: TextButtonVariant`
+
+> The type of the button
+
+Default: `TextButtonVariant.Default`
+
+| Theme | Description |
+| --------- | ----------- |
+| `Default` | Blue text |
+| `Inverse` | White text |
+
+---
+
+#### `size: 'small' | 'medium' | 'large'`
+
+> The size of the button
+
+Default: `'medium'`
+
+| Theme | Description |
+| -------- | -------------------------------------- |
+| `small` | 24px tall, small padding, small text |
+| `medium` | 32px tall, medium padding, medium text |
+| `large` | 48px tall, large padding, larger text |
+
+---
+
+#### `iconPosition: ButtonIconPosition`
+
+> The position of the TextButton icon.
+
+Default: `ButtonIconPosition.Left`
+
+---
+
+#### `buttonRef: React.Ref`
+
+> Returns the ref to the rendered HTMLButtonElement.
+
+---
+
+### `icon: CanvasSystemIcon`
+
+> The icon of the button.
+
+---
+
+### `allCaps: boolean`
+
+> The capitialization of the text in the button.
+
+---
+
+### `as: 'a'`
+
+> The alternative container type for the button. If `as="a"` is provided, We use Emotion's special
+> `as` prop to render an `a` tag instead of a `button`.
+
+> When defined, all props available via `React.AnchorHTMLAttributes` (e.g.
+> `href`, `target`, etc.) become available.
+
+Default: `undefined`
+
+---
+
+# Hyperlink
+
+```tsx
+import * as React from 'react';
+import {Hyperlink} from '@workday/canvas-kit-react-button';
+
+Link ;
+```
+
+Hyperlink will apply our link styling, but follow the font styles of it's container (size, weight,
+line-height, etc.).
+
+## Static Properties
+
+#### `Variant: ButtonVariant`
+
+```tsx
+Link
+```
+
+> The style of the link for different backgrounds
+
+Default: `TextButtonVariant.Default`
+
+| Theme | Description |
+| --------- | ----------- |
+| `Default` | Blue text |
+| `Inverse` | White text |
+
+## Component Props
+
+### Required
+
+> None
+
+### Optional
+
+#### `href: string`
+
+> The href url of the anchor tag
+
+---
+
+# IconButton
> Button containing an icon. Icon may be a component from
> [`canvas-kit-react-icon`](../../icon/react) or an svg element.
@@ -139,16 +655,12 @@ import {IconButton} from '@workday/canvas-kit-react-button';
import {SystemIcon} from '@workday/canvas-kit-react-icon';
import {activityStreamIcon} from '@workday/canvas-system-icons-web';
-
-
- ;
-
- ;
+ ;
```
## Static Properties
-#### `Sizes: ButtonSize`
+#### `Size: 'small' | 'medium'`
```tsx
@@ -156,7 +668,7 @@ import {activityStreamIcon} from '@workday/canvas-system-icons-web';
---
-#### `Types: IconButtonVariant`
+#### `Variant: IconButtonVariant`
```tsx
@@ -164,10 +676,6 @@ import {activityStreamIcon} from '@workday/canvas-system-icons-web';
## Component Props
-> Same as [`Button`](#canvas-kit-button) Undocumented props are spread to the `button` element.
-
----
-
### Required
#### `aria-label: string`
@@ -196,18 +704,16 @@ Default: `IconButtonVariant.Circle`
---
-#### `size: ButtonSize.Small | ButtonSize.Medium`
+#### `size: 'small' | 'medium`
> The size of the icon button
-Default: `ButtonSize.Medium`
+Default: `'medium'`
-| Theme | Description | Is Default |
-| --------------------------- | ----------------------------- | ---------- |
-| `Small` | 32px Diameter, 20px Icon Size | False |
-| `Medium` | 40px Diameter, 24px Icon Size | True |
-| `Small` (Square Icon Type) | 32px x 32px, 24px Icon Size | True |
-| `Medium` (Square Icon Type) | 40px x 40px, 24px Icon Size | False |
+| Theme | Description | Is Default |
+| -------- | ----------------------------- | ---------- |
+| `Small` | 32px Diameter, 20px Icon Size | False |
+| `Medium` | 40px Diameter, 24px Icon Size | True |
---
@@ -231,37 +737,45 @@ Default: `undefined`
---
-#### `value: string`
+#### `buttonRef: React.Ref`
+
+> Returns the ref to the rendered HTMLButtonElement.
+
+---
+
+### `icon: CanvasSystemIcon`
+
+> The icon of the button. Optional because IconButton can also wrap a SystemIcon component.
+
+---
+
+### `as: 'a'`
-> Value of the button. Must be unique if used within an IconButtonToggleGroup.
+> The alternative container type for the button. If `as="a"` is provided, We use Emotion's special
+> `as` prop to render an `a` tag instead of a `button`.
-## Accessibility Notes
+> When defined, all props available via `React.AnchorHTMLAttributes` (e.g.
+> `href`, `target`, etc.) become available.
-> The content of an IconButton is not always clear to the user. In order to better convey what the
-> icon represents, the IconButton should be initialized with `title` and `aria-label` attributes.
+Default: `undefined`
+
+---
-# Icon Button Toggle Group
+# ToolbarIconButton
-> Group of buttons containing an icon. This is a
-> [_controlled_](https://reactjs.org/docs/forms.html#controlled-components) component.
+> Button containing an icon. Icon may be a component from
+> [`canvas-kit-react-icon`](../../icon/react) or an svg element. Note: This button is intended to be
+> used within a toolbar.
## Usage
```tsx
import * as React from 'react';
-import {IconButton, IconButtonToggleGroup} from '@workday/canvas-kit-react-button';
-import {listViewIcon, worksheetsIcon} from '@workday/canvas-system-icons-web';
-
-
-
-
- ;
+import {ToolbarIconButton} from '@workday/canvas-kit-react-button';
+import {activityStreamIcon} from '@workday/canvas-system-icons-web';
+ ;
```
-**Note:** while managing state using a unique `value` for each `IconButton` child is encouraged, you
-can also use indexes and omit the `value` field. It is strongly recommended to not mix these two
-methods.
-
## Static Properties
> None
@@ -270,29 +784,87 @@ methods.
### Required
-#### `children: React.ReactElement[]`
+#### `aria-label: string`
-> Icon buttons to toggle between.
+> The accessibility label to indicate the action triggered by clicking the toolbar icon button.
---
### Optional
-#### `value: string | number`
+#### `toggled: boolean | undefined`
+
+> If defined as a boolean, then it manages the button state: on (`true`) or off (`false`). This is a
+> [_controlled_](https://reactjs.org/docs/forms.html#controlled-components) `button` component. If
+> left `undefined` then the button is not considered toggle-able (`aria-pressed` is `undefined`) and
+> will act as a normal button.
+
+Default: `undefined`
+
+---
+
+#### `onToggleChange: (toggled: boolean | undefined) => void`
+
+> The callback that is fired when a button toggle prop changes This is true when the toggle changes
+> from `true` to `false` but also if you disable the toggle-ability of a button (in other words, if
+> `toggle` changes from a `boolean` to `undefined`). This is important because the `aria-pressed`
+> attribute for accessibility is goverened by whether or not the `toggle` prop is defined.
+
+---
+
+#### `buttonRef: React.Ref`
-> Identify which item is selected (toggled=true). If a string is passed, the IconButton with the
-> corresponding value will be selected. If a number is passed, the IconButton with the corresponding
-> index will be selected.
+> Returns the ref to the rendered HTMLButtonElement.
---
-#### `isRTL: boolean`
+### `icon: CanvasSystemIcon`
-> Identify whether to render from right to left
+> The icon of the button. Optional because ToolbarIconButton can also wrap a SystemIcon component.
---
-#### `onChange: (value:string | number)=> void`
+# ToolbarDropdownButton
-> Callback function when a toggle button is selected. The value (if defined) or the index of the
-> button will be returned.
+> Button containing an icon or custom element. Icon may be a component from
+> [`canvas-kit-react-icon`](../../icon/react) or an svg element. By default, the button has a down
+> chevron to the right indicating that it's a dropdown button. Note: This button is intended to be
+> used within a toolbar.
+
+## Usage
+
+```tsx
+import * as React from 'react';
+import {ToolbarDropdownButton} from '@workday/canvas-kit-react-button';
+import {activityStreamIcon} from '@workday/canvas-system-icons-web';
+ ;
+```
+
+## Static Properties
+
+> None
+
+## Component Props
+
+### Required
+
+#### `aria-label: string`
+
+> The accessibility label to indicate the action triggered by clicking the toolbar icon button.
+
+---
+
+### Optional
+
+#### `buttonRef: React.Ref`
+
+> Returns the ref to the rendered HTMLButtonElement.
+
+---
+
+### `icon: CanvasSystemIcon`
+
+> The icon of the button. Optional because ToolbarDropdownButton can also wrap a SystemIcon
+> component.
+
+---
diff --git a/modules/button/react/index.ts b/modules/button/react/index.ts
index 132e7b7f30..faf6df0e77 100644
--- a/modules/button/react/index.ts
+++ b/modules/button/react/index.ts
@@ -1,17 +1,16 @@
-import Button from './lib/Button';
+export {default, default as Button, default as beta_Button, ButtonProps} from './lib/Button';
-export default Button;
-export {
- default as Button,
- default as beta_Button,
- deprecated_Button,
- ButtonProps,
-} from './lib/Button';
+export {default as DeleteButton, DeleteButtonProps} from './lib/DeleteButton';
+export {default as deprecated_Button, DeprecatedButtonProps} from './lib/deprecated_Button';
+export {default as DropdownButton, DropdownButtonProps} from './lib/DropdownButton';
+export {default as HighlightButton, HighlightButtonProps} from './lib/HighlightButton';
+export {default as OutlineButton, OutlineButtonProps} from './lib/OutlineButton';
export {default as IconButton, IconButtonProps} from './lib/IconButton';
-export {default as DropdownButton} from './lib/DropdownButton';
export {default as TextButton, TextButtonProps} from './lib/TextButton';
+export {default as ToolbarIconButton, ToolbarIconButtonProps} from './lib/ToolbarIconButton';
export {
- default as IconButtonToggleGroup,
- IconButtonToggleGroupProps,
-} from './lib/IconButtonToggleGroup';
+ default as ToolbarDropdownButton,
+ ToolbarDropdownButtonProps,
+} from './lib/ToolbarDropdownButton';
+export {default as Hyperlink} from './lib/Hyperlink';
export * from './lib/types';
diff --git a/modules/button/react/lib/Button.tsx b/modules/button/react/lib/Button.tsx
index efa4c52a84..5a293d2836 100644
--- a/modules/button/react/lib/Button.tsx
+++ b/modules/button/react/lib/Button.tsx
@@ -1,101 +1,141 @@
-/** @jsx jsx */
-import {jsx} from '@emotion/core';
import * as React from 'react';
-import {ButtonBaseCon, ButtonBaseLabel, ButtonLabelData, ButtonLabelIcon} from './ButtonBase';
-import {DeprecatedButtonVariant, ButtonSize, ButtonVariant} from './types';
+import {colors} from '@workday/canvas-kit-react-core';
+import {
+ GrowthBehavior,
+ useTheme,
+ Themeable,
+ EmotionCanvasTheme,
+} from '@workday/canvas-kit-react-common';
import {CanvasSystemIcon} from '@workday/design-assets-types';
-import {GrowthBehavior} from '@workday/canvas-kit-react-common';
-import {labelDataBaseStyles} from './ButtonStyles';
+import {
+ ButtonVariant,
+ ButtonColors,
+ DropdownButtonVariant,
+ ButtonSize,
+ ButtonOrAnchorComponent,
+} from './types';
+import {ButtonContainer, ButtonLabel, ButtonLabelData, ButtonLabelIcon} from './parts';
-export interface BaseButtonProps
- extends React.ButtonHTMLAttributes {
+export interface ButtonProps
+ extends React.ButtonHTMLAttributes,
+ Themeable,
+ GrowthBehavior {
/**
* The variant of the Button.
* @default ButtonVariant.Secondary
*/
- variant?: T;
+ variant?: ButtonVariant;
/**
* The size of the Button.
- * @default ButtonSize.Medium
+ * @default 'medium'
*/
- size?: ButtonSize;
+ size?: 'small' | 'medium' | 'large';
/**
* The ref to the button that the styled component renders.
*/
buttonRef?: React.Ref;
/**
* The data label of the Button.
+ * Note: not displayed at `small` size
*/
dataLabel?: String;
/**
* The icon of the Button.
+ * Note: not displayed at `small` size
*/
icon?: CanvasSystemIcon;
-}
-
-export interface ButtonProps
- extends BaseButtonProps,
- GrowthBehavior {
/**
- * The children of the Button (cannot be empty).
+ * The alternative container type for the button. Uses Emotion's special `as` prop.
+ * Will render an `a` tag instead of a `button` when defined.
*/
- children: React.ReactNode;
+ as?: 'a';
}
-export default class Button extends React.Component {
- public static Variant = ButtonVariant;
- public static Size = ButtonSize;
+const Button: ButtonOrAnchorComponent = ({
+ theme = useTheme(),
+ variant = ButtonVariant.Secondary,
+ size = 'medium',
+ buttonRef,
+ dataLabel,
+ icon,
+ children,
+ ...elemProps
+}: ButtonProps) => (
+
+ {icon && size !== 'small' && }
+ {children}
+ {dataLabel && size !== 'small' && {dataLabel} }
+
+);
- static defaultProps = {
- size: ButtonSize.Medium,
- variant: ButtonVariant.Secondary,
- grow: false,
- };
+Button.Variant = ButtonVariant;
+Button.Size = ButtonSize;
- public render() {
- const {variant, size, buttonRef, dataLabel, icon, children, ...elemProps} = this.props;
+export default Button;
- // Restrict Hightlight button to only being sized Large, Medium with an Icon
- if (variant === ButtonVariant.Highlight && (icon === undefined || size === ButtonSize.Small)) {
- return null;
- }
-
- return (
-
- {icon && }
-
- {children}
-
- {dataLabel && (
-
- {dataLabel}
-
- )}
-
- );
+export const getButtonColors = (
+ variant: ButtonVariant | DropdownButtonVariant,
+ {
+ canvas: {
+ palette: {primary: themePrimary},
+ },
+ }: EmotionCanvasTheme
+): ButtonColors => {
+ switch (variant) {
+ case ButtonVariant.Primary:
+ case DropdownButtonVariant.Primary:
+ return {
+ default: {
+ background: themePrimary.main,
+ icon: themePrimary.contrast,
+ label: themePrimary.contrast,
+ },
+ hover: {
+ background: themePrimary.dark,
+ },
+ active: {
+ background: themePrimary.darkest,
+ },
+ focus: {
+ background: themePrimary.main,
+ },
+ disabled: {
+ background: themePrimary.light,
+ },
+ };
+ case ButtonVariant.Secondary:
+ case DropdownButtonVariant.Secondary:
+ default:
+ return {
+ default: {
+ background: colors.soap200,
+ icon: colors.licorice200,
+ label: colors.blackPepper400,
+ labelData: colors.blackPepper400,
+ },
+ hover: {
+ background: colors.soap400,
+ icon: colors.licorice500,
+ },
+ active: {
+ background: colors.soap500,
+ icon: colors.licorice500,
+ },
+ focus: {
+ background: colors.soap200,
+ icon: colors.licorice500,
+ },
+ disabled: {
+ background: colors.soap100,
+ icon: colors.soap600,
+ label: colors.licorice100,
+ labelData: colors.licorice100,
+ },
+ };
}
-}
-/**
- * @deprecated deprecated_Button in @workday/canvas-kit-react-button will be removed soon. Use Button instead.
- */
-// eslint-disable-next-line @typescript-eslint/class-name-casing
-export class deprecated_Button extends React.Component> {
- public static Variant = DeprecatedButtonVariant;
- public static Size = ButtonSize;
-
- static defaultProps = {
- size: ButtonSize.Large,
- variant: DeprecatedButtonVariant.Secondary,
- grow: false,
- };
-
- public componentDidMount() {
- console.warn('This component is now deprecated, consider using the new Button component');
- }
-
- public render() {
- const {variant, size, buttonRef, dataLabel, icon, children, ...elemProps} = this.props;
-
- return ;
- }
-}
+};
diff --git a/modules/button/react/lib/ButtonBase.tsx b/modules/button/react/lib/ButtonBase.tsx
deleted file mode 100644
index ecc1b5fc9a..0000000000
--- a/modules/button/react/lib/ButtonBase.tsx
+++ /dev/null
@@ -1,162 +0,0 @@
-import * as React from 'react';
-import {styled} from '@workday/canvas-kit-labs-react-core';
-import isPropValid from '@emotion/is-prop-valid';
-import {
- ButtonSize,
- DeprecatedButtonVariant,
- IconPosition,
- AllButtonVariants,
- TextButtonVariant,
-} from './types';
-import {ButtonProps, BaseButtonProps} from './Button';
-import {SystemIcon} from '@workday/canvas-kit-react-icon';
-import * as ButtonStyles from './ButtonStyles';
-import {getBaseButton, getButtonSize, getButtonStyle} from './utils';
-
-export const ButtonBaseCon = styled('button', {
- shouldForwardProp: prop => isPropValid(prop) && prop !== 'size',
-})(
- /* istanbul ignore next line for coverage */
- ({variant, size}) => {
- if (variant === undefined) {
- return {};
- }
-
- const baseButton = getBaseButton(variant);
- const buttonStyles = getButtonStyle(baseButton, variant);
- const sizeStyles = size !== undefined ? getButtonSize(baseButton, size) : {};
-
- return {
- ...baseButton.styles,
- ...buttonStyles,
- ...sizeStyles,
- };
- },
- ({grow}) => grow && {width: '100%', maxWidth: '100%'}
-);
-
-export const ButtonBaseLabel = styled('span', {
- shouldForwardProp: prop => isPropValid(prop) && prop !== 'size',
-})>(
- ButtonStyles.labelBaseStyles.styles,
- ({size}) => {
- const {sizes} = ButtonStyles.labelBaseStyles.variants!;
-
- switch (size) {
- case ButtonSize.Large:
- default:
- return sizes.large;
- case ButtonSize.Small:
- return sizes.small;
- case ButtonSize.Medium:
- return sizes.medium;
- }
- },
- ({variant}) => {
- const {types} = ButtonStyles.labelBaseStyles.variants!;
-
- switch (variant) {
- case TextButtonVariant.Default:
- case TextButtonVariant.Inverse:
- return types.text;
- case TextButtonVariant.AllCaps:
- case TextButtonVariant.InverseAllCaps:
- return types.textAllCaps;
- case DeprecatedButtonVariant.Primary:
- return types.deprecatedPrimary;
- case DeprecatedButtonVariant.Secondary:
- return types.deprecatedSecondary;
- case DeprecatedButtonVariant.Delete:
- return types.deprecatedDelete;
- default:
- return {};
- }
- }
-);
-
-export const ButtonLabelData = styled('span', {
- shouldForwardProp: prop => isPropValid(prop) && prop !== 'size',
-})(ButtonStyles.labelDataBaseStyles.styles, ({size}) => {
- const {sizes} = ButtonStyles.labelDataBaseStyles.variants!;
- switch (size) {
- case ButtonSize.Large:
- default:
- return sizes.large;
- case ButtonSize.Medium:
- return sizes.medium;
- }
-});
-
-const ButtonLabelIconStyled = styled('span', {
- shouldForwardProp: prop => isPropValid(prop) && prop !== 'size',
-})(
- ButtonStyles.labelIconBaseStyles.styles,
- ({size, dropdown}) => {
- if (dropdown) {
- switch (size) {
- case ButtonSize.Large:
- default:
- return {padding: '0 8px 0 0'};
- case ButtonSize.Medium:
- return {padding: '0 4px 0 0'};
- }
- }
-
- const {sizes} = ButtonStyles.labelIconBaseStyles.variants!;
-
- switch (size) {
- case ButtonSize.Large:
- default:
- return sizes.large;
- case ButtonSize.Medium:
- return sizes.medium;
- }
- },
- ({iconPosition}) => {
- if (iconPosition === undefined) {
- return {};
- }
-
- const {types} = ButtonStyles.labelIconBaseStyles.variants!;
-
- switch (iconPosition) {
- case IconPosition.Left:
- default:
- return types.iconPositionLeft;
- case IconPosition.Right:
- return types.iconPositionRight;
- }
- }
-);
-
-export interface ButtonLabelIconProps extends BaseButtonProps {
- iconPosition?: IconPosition;
- dropdown?: boolean;
-}
-
-export class ButtonLabelIcon extends React.Component {
- public render() {
- const {icon, size, dropdown, iconPosition, ...elemProps} = this.props;
- /* istanbul ignore next line for coverage */
- if (icon === undefined) {
- return {};
- }
-
- let iconSize = 24;
-
- if (size === ButtonSize.Small) {
- iconSize = 20;
- }
-
- return (
-
-
-
- );
- }
-}
diff --git a/modules/button/react/lib/ButtonColors.ts b/modules/button/react/lib/ButtonColors.ts
deleted file mode 100644
index 28de79eb10..0000000000
--- a/modules/button/react/lib/ButtonColors.ts
+++ /dev/null
@@ -1,342 +0,0 @@
-import canvas from '@workday/canvas-kit-react-core';
-import {
- AllButtonVariants,
- DeprecatedButtonVariant,
- TextButtonVariant,
- ButtonVariant,
- IconButtonVariant,
-} from './types';
-
-export interface CanvasButtonColors
- extends Partial,
- Partial,
- Partial {}
-
-export interface GenericButtonColors extends CanvasButtonColors {
- focusRingInner?: string;
- focusRingOuter?: string;
- labelData?: string;
- labelDataActive?: string;
- labelDataDisabled?: string;
- labelDataFocus?: string;
- labelDataHover?: string;
- labelIcon?: string;
- labelIconActive?: string;
- labelIconDisabled?: string;
- labelIconFocus?: string;
- labelIconHover?: string;
- labelIconFocusHover?: string;
- focusHover?: string;
-}
-
-export type ButtonColorCollection = {
- [key in AllButtonVariants]: GenericButtonColors | null;
-};
-
-export const ButtonColors: ButtonColorCollection = {
- // TODO (beta button): remove in favor of beta buttons, consider moving from design-assets too
- [DeprecatedButtonVariant.Primary]: canvas.buttonColors.primary,
- [DeprecatedButtonVariant.Secondary]: canvas.buttonColors.secondary,
- [DeprecatedButtonVariant.Delete]: {
- ...canvas.buttonColors.delete,
- focusBorder: canvas.colors.cinnamon500,
- activeBorder: canvas.colors.cinnamon500,
- },
- [ButtonVariant.Primary]: {
- background: canvas.colors.blueberry400,
- border: 'transparent',
- text: canvas.colors.frenchVanilla100,
- activeBackground: canvas.colors.blueberry600,
- activeBorder: 'transparent',
- activeText: canvas.colors.frenchVanilla100,
- disabledBackground: canvas.colors.blueberry200,
- disabledBorder: 'transparent',
- disabledText: canvas.colors.frenchVanilla100,
- focusBackground: canvas.colors.blueberry400,
- focusBorder: 'transparent',
- focusText: canvas.colors.frenchVanilla100,
- hoverBackground: canvas.colors.blueberry500,
- hoverBorder: 'transparent',
- hoverText: canvas.colors.frenchVanilla100,
- labelIcon: canvas.colors.frenchVanilla100,
- labelIconHover: canvas.colors.frenchVanilla100,
- labelIconActive: canvas.colors.frenchVanilla100,
- labelIconFocus: canvas.colors.frenchVanilla100,
- labelIconDisabled: canvas.colors.frenchVanilla100,
- },
- [ButtonVariant.Secondary]: {
- background: canvas.colors.soap200,
- border: 'transparent',
- text: canvas.colors.blackPepper400,
- activeBackground: canvas.colors.soap500,
- activeBorder: 'transparent',
- activeText: canvas.colors.blackPepper400,
- disabledBackground: canvas.colors.soap100,
- disabledBorder: 'transparent',
- disabledText: canvas.colors.licorice100,
- focusBackground: canvas.colors.soap200,
- focusBorder: 'transparent',
- focusText: canvas.colors.blackPepper400,
- hoverBackground: canvas.colors.soap400,
- hoverBorder: 'transparent',
- hoverText: canvas.colors.blackPepper400,
- labelIcon: canvas.colors.licorice200,
- labelIconHover: canvas.colors.licorice500,
- labelIconActive: canvas.colors.licorice500,
- labelIconFocus: canvas.colors.licorice200,
- labelIconDisabled: canvas.colors.soap600,
- labelData: canvas.colors.blackPepper400,
- labelDataDisabled: canvas.colors.licorice100,
- },
- [ButtonVariant.Delete]: {
- background: canvas.colors.cinnamon500,
- border: canvas.colors.cinnamon500,
- text: canvas.colors.frenchVanilla100,
- activeBackground: '#80160E',
- activeBorder: 'transparent',
- activeText: canvas.colors.frenchVanilla100,
- disabledBackground: canvas.colors.cinnamon200,
- disabledBorder: 'transparent',
- disabledText: canvas.colors.frenchVanilla100,
- focusBackground: canvas.colors.cinnamon500,
- focusText: canvas.colors.frenchVanilla100,
- hoverBackground: canvas.colors.cinnamon600,
- hoverBorder: canvas.colors.cinnamon600,
- hoverText: canvas.colors.frenchVanilla100,
- },
- [ButtonVariant.Highlight]: {
- background: canvas.colors.soap200,
- border: canvas.colors.soap200,
- text: canvas.colors.blueberry500,
- activeBackground: canvas.colors.soap500,
- activeBorder: 'transparent',
- activeText: canvas.colors.blueberry500,
- disabledBackground: canvas.colors.soap100,
- disabledBorder: 'transparent',
- disabledText: canvas.colors.licorice100,
- focusBackground: canvas.colors.soap200,
- focusBorder: 'transparent',
- focusText: canvas.colors.blueberry500,
- hoverBackground: canvas.colors.soap400,
- hoverBorder: 'transparent',
- hoverText: canvas.colors.blueberry500,
- labelIcon: canvas.colors.blueberry500,
- labelIconHover: canvas.colors.blueberry500,
- labelIconActive: canvas.colors.blueberry500,
- labelIconFocus: canvas.colors.blueberry500,
- labelIconDisabled: canvas.colors.soap600,
- },
- [ButtonVariant.OutlinePrimary]: {
- background: 'transparent',
- border: canvas.colors.blueberry400,
- text: canvas.colors.blueberry400,
- activeBackground: canvas.colors.blueberry500,
- activeBorder: 'transparent',
- activeText: canvas.colors.frenchVanilla100,
- disabledBackground: canvas.colors.frenchVanilla100,
- disabledBorder: canvas.colors.soap500,
- disabledText: canvas.colors.licorice100,
- focusBackground: canvas.colors.blueberry400,
- focusBorder: 'transparent',
- focusText: canvas.colors.frenchVanilla100,
- hoverBackground: canvas.colors.blueberry400,
- hoverBorder: 'transparent',
- hoverText: canvas.colors.frenchVanilla100,
- labelIcon: canvas.colors.blueberry400,
- labelIconHover: canvas.colors.frenchVanilla100,
- labelIconActive: canvas.colors.frenchVanilla100,
- labelIconFocus: canvas.colors.frenchVanilla100,
- labelIconDisabled: canvas.colors.soap600,
- },
- [ButtonVariant.OutlineSecondary]: {
- background: 'transparent',
- border: canvas.colors.soap500,
- text: canvas.colors.blackPepper400,
- activeBackground: canvas.colors.licorice600,
- activeBorder: 'transparent',
- activeText: canvas.colors.frenchVanilla100,
- disabledBackground: canvas.colors.frenchVanilla100,
- disabledBorder: canvas.colors.soap500,
- disabledText: canvas.colors.licorice100,
- focusBackground: canvas.colors.licorice500,
- focusBorder: 'transparent',
- focusText: canvas.colors.frenchVanilla100,
- hoverBackground: canvas.colors.licorice500,
- hoverBorder: 'transparent',
- hoverText: canvas.colors.frenchVanilla100,
- labelIcon: canvas.colors.licorice200,
- labelIconHover: canvas.colors.frenchVanilla100,
- labelIconActive: canvas.colors.frenchVanilla100,
- labelIconFocus: canvas.colors.frenchVanilla100,
- labelIconDisabled: canvas.colors.soap600,
- },
- [ButtonVariant.OutlineInverse]: {
- background: 'transparent',
- border: canvas.colors.frenchVanilla100,
- text: canvas.colors.frenchVanilla100,
- activeBackground: canvas.colors.soap300,
- activeBorder: 'transparent',
- activeText: canvas.colors.blackPepper400,
- disabledBackground: 'transparent',
- disabledBorder: 'rgba(255, 255, 255, 0.75)',
- disabledText: 'rgba(255, 255, 255, 0.75)',
- focusBackground: canvas.colors.frenchVanilla100,
- focusBorder: 'transparent',
- focusRingInner: 'currentColor',
- focusRingOuter: canvas.colors.frenchVanilla100,
- focusText: canvas.colors.blackPepper400,
- hoverBackground: canvas.colors.frenchVanilla100,
- hoverBorder: 'transparent',
- hoverText: canvas.colors.blackPepper400,
- labelIcon: canvas.colors.frenchVanilla100,
- labelIconHover: canvas.colors.licorice500,
- labelIconActive: canvas.colors.licorice500,
- labelIconFocus: canvas.colors.licorice500,
- labelIconDisabled: 'rgba(255, 255, 255, 0.75)',
- labelDataHover: canvas.colors.licorice300,
- labelDataActive: canvas.colors.licorice300,
- labelDataFocus: canvas.colors.licorice300,
- labelDataDisabled: 'rgba(255, 255, 255, 0.75)',
- },
- [TextButtonVariant.Default]: {
- background: 'transparent',
- border: 'transparent',
- text: canvas.colors.blueberry400,
- activeBackground: 'transparent',
- activeBorder: 'transparent',
- activeText: canvas.colors.blueberry500,
- disabledBackground: 'transparent',
- disabledBorder: 'transparent',
- disabledText: 'rgba(8, 117, 225, 0.5)',
- focusBackground: 'transparent',
- focusText: canvas.colors.blueberry400,
- hoverBorder: 'transparent',
- hoverText: canvas.colors.blueberry500,
- labelIcon: canvas.colors.blueberry400,
- labelIconHover: canvas.colors.blueberry500,
- labelIconActive: canvas.colors.blueberry500,
- labelIconFocus: canvas.colors.blueberry400,
- labelIconDisabled: 'rgba(8, 117, 225, 0.5)',
- },
- [TextButtonVariant.Inverse]: {
- background: 'transparent',
- border: 'transparent',
- text: canvas.colors.frenchVanilla100,
- activeBackground: 'transparent',
- activeBorder: 'transparent',
- activeText: canvas.colors.frenchVanilla100,
- disabledBackground: 'transparent',
- disabledBorder: 'transparent',
- disabledText: 'rgba(255, 255, 255, 0.5)',
- focusBackground: 'transparent',
- focusText: canvas.colors.frenchVanilla100,
- focusRingInner: 'transparent',
- focusRingOuter: canvas.colors.frenchVanilla100,
- hoverBorder: 'transparent',
- labelIcon: canvas.colors.frenchVanilla100,
- labelIconHover: 'currentColor',
- labelIconActive: canvas.colors.frenchVanilla100,
- labelIconFocus: canvas.colors.frenchVanilla100,
- labelIconDisabled: 'rgba(255, 255, 255, 0.5)',
- },
- [TextButtonVariant.AllCaps]: null,
- [TextButtonVariant.InverseAllCaps]: null,
- [IconButtonVariant.Square]: {
- background: 'transparent',
- activeBackground: canvas.colors.soap500,
- disabledBackground: 'transparent',
- focusBackground: canvas.colors.soap300,
- hoverBackground: canvas.colors.soap300,
- labelIcon: canvas.colors.licorice200,
- labelIconHover: canvas.colors.licorice500,
- labelIconActive: canvas.colors.licorice500,
- labelIconFocus: canvas.colors.licorice500,
- labelIconFocusHover: canvas.colors.licorice500,
- labelIconDisabled: canvas.colors.soap600,
- },
- [IconButtonVariant.SquareFilled]: {
- background: canvas.colors.soap200,
- border: canvas.colors.soap500,
- activeBackground: canvas.colors.soap500,
- disabledBackground: canvas.colors.soap100,
- focusBackground: canvas.colors.soap400,
- hoverBackground: canvas.colors.soap400,
- labelIcon: canvas.colors.licorice200,
- labelIconHover: canvas.colors.licorice500,
- labelIconActive: canvas.colors.licorice500,
- labelIconFocus: canvas.colors.licorice500,
- labelIconDisabled: canvas.colors.soap600,
- },
- [IconButtonVariant.Plain]: {
- background: 'transparent',
- activeBackground: 'transparent',
- activeBorder: 'transparent',
- disabledBackground: 'transparent',
- focusBackground: 'transparent',
- hoverBackground: 'transparent',
- labelIcon: canvas.colors.licorice200,
- labelIconHover: canvas.colors.licorice500,
- labelIconActive: canvas.colors.licorice500,
- labelIconFocus: canvas.colors.licorice500,
- labelIconDisabled: canvas.colors.soap600,
- },
- [IconButtonVariant.Circle]: {
- background: 'transparent',
- activeBackground: canvas.colors.soap500,
- disabledBackground: 'transparent',
- focusBackground: canvas.colors.soap300,
- hoverBackground: canvas.colors.soap300,
- labelIcon: canvas.colors.licorice200,
- labelIconHover: canvas.colors.licorice500,
- labelIconActive: canvas.colors.licorice500,
- labelIconFocusHover: canvas.colors.licorice500,
- labelIconFocus: canvas.colors.licorice500,
- labelIconDisabled: canvas.colors.soap600,
- },
- [IconButtonVariant.CircleFilled]: {
- background: canvas.colors.soap200,
- activeBackground: canvas.colors.soap500,
- disabledBackground: canvas.colors.soap100,
- hoverBackground: canvas.colors.soap400,
- focusBackground: canvas.colors.soap400,
- labelIcon: canvas.colors.licorice200,
- labelIconHover: canvas.colors.licorice500,
- labelIconFocus: canvas.colors.licorice500,
- labelIconActive: canvas.colors.licorice500,
- labelIconDisabled: canvas.colors.soap600,
- },
- [IconButtonVariant.Inverse]: {
- background: 'transparent',
- activeBackground: 'rgba(0, 0, 0, 0.3)',
- focusBackground: 'rgba(0, 0, 0, 0.2)',
- disabledBackground: 'transparent',
- hoverBackground: 'rgba(0, 0, 0, 0.2)',
- labelIcon: canvas.colors.frenchVanilla100,
- labelIconHover: canvas.colors.frenchVanilla100,
- labelIconActive: canvas.colors.frenchVanilla100,
- labelIconFocus: canvas.colors.frenchVanilla100,
- labelIconDisabled: 'rgba(255, 255, 255, 0.75)',
- focusRingInner: 'currentColor',
- focusBorder: 'transparent',
- focusRingOuter: canvas.colors.frenchVanilla100,
- focusHover: 'rgba(0, 0, 0, 0.3)',
- },
- [IconButtonVariant.InverseFilled]: {
- background: 'rgba(0, 0, 0, 0.2)',
- activeBackground: 'rgba(0, 0, 0, 0.4)',
- focusBackground: 'rgba(0, 0, 0, 0.2)',
- disabledBackground: 'rgba(0, 0, 0, 0.2)',
- hoverBackground: 'rgba(0, 0, 0, 0.3)',
- labelIcon: canvas.colors.frenchVanilla100,
- labelIconHover: canvas.colors.frenchVanilla100,
- labelIconActive: canvas.colors.frenchVanilla100,
- labelIconFocus: canvas.colors.frenchVanilla100,
- labelIconDisabled: 'rgba(255, 255, 255, 0.75)',
- focusRingInner: 'currentColor',
- focusBorder: 'transparent',
- focusRingOuter: canvas.colors.frenchVanilla100,
- focusHover: 'rgba(0, 0, 0, 0.3)',
- },
-};
-
-export default ButtonColors;
diff --git a/modules/button/react/lib/ButtonStyles.ts b/modules/button/react/lib/ButtonStyles.ts
deleted file mode 100644
index ecde7b591d..0000000000
--- a/modules/button/react/lib/ButtonStyles.ts
+++ /dev/null
@@ -1,362 +0,0 @@
-import {CSSObject} from '@emotion/core';
-import {GenericStyle} from '@workday/canvas-kit-react-common';
-import canvas, {borderRadius} from '@workday/canvas-kit-react-core';
-
-import {
- AllButtonVariants,
- ButtonSize,
- ButtonVariant,
- DeprecatedButtonVariant,
- IconButtonVariant,
- IconPosition,
- TextButtonVariant,
-} from './types';
-import {getButtonStateStyle, getIconButtonStateStyle} from './utils';
-
-export const CANVAS_BUTTON_HEIGHT_LARGE: number = 40;
-export const CANVAS_BUTTON_HEIGHT_MEDIUM: number = 24;
-export const CANVAS_BUTTON_HEIGHT_SMALL: number = 18;
-
-export interface ButtonGenericStyle extends GenericStyle {
- variants?: {
- types: {[key in AllButtonVariants | IconPosition]?: CSSObject};
- sizes: {[key in ButtonSize]?: CSSObject};
- };
-}
-
-export const labelBaseStyles: ButtonGenericStyle = {
- classname: 'button-label',
- styles: {
- position: 'relative', // Fixes an IE issue with text within button moving on click
- ':hover:active': {
- backgroundColor: 'transparent',
- },
- textOverflow: 'ellipsis',
- overflow: 'hidden',
- whiteSpace: 'nowrap',
- fontWeight: 700,
- fontFamily: '"Roboto", "Helvetica Neue", "Helvetica", "Arial", sans-serif',
- WebkitFontSmoothing: 'antialiased',
- MozOsxFontSmoothing: 'grayscale',
- },
- variants: {
- types: {
- [TextButtonVariant.Default]: {
- padding: '0',
- },
- [TextButtonVariant.AllCaps]: {
- ...canvas.type.variant.caps,
- fontSize: '14px',
- letterSpacing: '.5px',
- padding: '0',
- },
- [DeprecatedButtonVariant.Primary]: {
- fontSize: 'inherit',
- fontWeight: 'inherit',
- padding: '0',
- },
- [DeprecatedButtonVariant.Secondary]: {
- fontSize: 'inherit',
- fontWeight: 'inherit',
- padding: '0',
- },
- [DeprecatedButtonVariant.Delete]: {
- fontSize: 'inherit',
- fontWeight: 'inherit',
- padding: '0',
- },
- },
- sizes: {
- [ButtonSize.Large]: {
- fontSize: '16px',
- padding: '0 12px',
- },
- [ButtonSize.Medium]: {
- fontSize: '14px',
- padding: '0 8px',
- },
- [ButtonSize.Small]: {
- fontSize: '14px',
- padding: '0',
- },
- },
- },
-};
-
-export const labelDataBaseStyles: ButtonGenericStyle = {
- classname: 'button-label-data',
- styles: {
- ...labelBaseStyles.styles,
- fontWeight: 400,
- fontSize: '16px',
- },
- variants: {
- types: {},
- sizes: {
- [ButtonSize.Large]: {
- paddingRight: '12px',
- },
- [ButtonSize.Medium]: {
- paddingRight: '8px',
- fontSize: '14px',
- },
- },
- },
-};
-
-export const labelIconBaseStyles: ButtonGenericStyle = {
- classname: 'button-label-icon',
- styles: {
- display: 'flex',
- },
- variants: {
- types: {
- [IconPosition.Left]: {
- padding: '0 8px 0 0',
- },
- [IconPosition.Right]: {
- padding: '0 0 0 8px',
- },
- },
- sizes: {
- [ButtonSize.Large]: {
- paddingLeft: '8px',
- },
- [ButtonSize.Medium]: {
- paddingLeft: '4px',
- },
- },
- },
-};
-
-export const deprecatedButtonStyles: ButtonGenericStyle = {
- classname: 'canvas-deprecated-button',
- styles: {
- boxSizing: 'border-box',
- display: 'inline-flex',
- alignItems: 'center',
- justifyContent: 'center',
- fontSize: '13px',
- borderRadius: borderRadius.circle,
- border: '1px solid transparent',
- boxShadow: 'none',
- position: 'relative',
- cursor: 'pointer',
- outline: 'none',
- transition:
- 'box-shadow 120ms linear, border 120ms linear, background-color 120ms linear, color 120ms linear',
- '&:hover:active': {transitionDuration: '40ms'}, // Makes the "down" state of the button happens faster than the hover state, so it animates in correctly.
- '&:disabled, &:disabled:active': {cursor: 'default', boxShadow: 'none'},
- },
- variants: {
- types: {
- [DeprecatedButtonVariant.Primary]: {
- ...getButtonStateStyle(DeprecatedButtonVariant.Primary),
- },
- [DeprecatedButtonVariant.Secondary]: {
- ...getButtonStateStyle(DeprecatedButtonVariant.Secondary),
- },
- [DeprecatedButtonVariant.Delete]: {
- ...getButtonStateStyle(DeprecatedButtonVariant.Delete),
- },
- },
- sizes: {
- [ButtonSize.Large]: {
- height: `${CANVAS_BUTTON_HEIGHT_LARGE}px`,
- padding: `0 ${canvas.spacing.l}`,
- minWidth: '112px',
- maxWidth: '288px',
- fontSize: '14px',
- fontWeight: 500,
- },
- [ButtonSize.Medium]: {
- height: `${CANVAS_BUTTON_HEIGHT_MEDIUM}px`,
- padding: `0 ${canvas.spacing.m}`,
- minWidth: '80px',
- maxWidth: '200px',
- fontSize: '13px',
- fontWeight: 500,
- },
- [ButtonSize.Small]: {
- height: `${CANVAS_BUTTON_HEIGHT_SMALL}px`,
- padding: `0 ${canvas.spacing.xxs}`,
- minWidth: '56px',
- maxWidth: '120px',
- fontSize: '10px',
- fontWeight: 500,
- },
- },
- },
-};
-
-export const canvasButtonStyles: ButtonGenericStyle = {
- classname: 'canvas-button',
- styles: {
- ...deprecatedButtonStyles.styles,
- verticalAlign: 'middle',
- border: '2px solid transparent',
- fontSize: '14px',
- },
- variants: {
- types: {
- [ButtonVariant.Primary]: {
- ...getButtonStateStyle(ButtonVariant.Primary),
- },
- [ButtonVariant.Secondary]: {
- ...getButtonStateStyle(ButtonVariant.Secondary),
- },
- [ButtonVariant.Delete]: {
- ...getButtonStateStyle(ButtonVariant.Delete),
- },
- [ButtonVariant.Highlight]: {
- ...getButtonStateStyle(ButtonVariant.Highlight),
- },
- [ButtonVariant.OutlinePrimary]: {
- ...getButtonStateStyle(ButtonVariant.OutlinePrimary),
- },
- [ButtonVariant.OutlineSecondary]: {
- ...getButtonStateStyle(ButtonVariant.OutlineSecondary),
- },
- [ButtonVariant.OutlineInverse]: {
- ...getButtonStateStyle(ButtonVariant.OutlineInverse),
- },
- },
- sizes: {
- [ButtonSize.Large]: {
- minWidth: '112px',
- height: '48px',
- padding: '0 20px',
- },
- [ButtonSize.Medium]: {
- minWidth: '96px',
- height: canvas.spacing.xl,
- padding: '0 16px',
- },
- [ButtonSize.Small]: {
- minWidth: '80px',
- height: canvas.spacing.l,
- padding: '0 16px',
- },
- },
- },
-};
-
-export const dropdownButtonStyles: ButtonGenericStyle = {
- classname: 'dropdown-button',
- styles: {
- ...canvasButtonStyles.styles,
- },
- variants: {
- types: {
- [ButtonVariant.Primary]: canvasButtonStyles.variants!.types[ButtonVariant.Primary],
- [ButtonVariant.Secondary]: canvasButtonStyles.variants!.types[ButtonVariant.Secondary],
- },
- sizes: {
- [ButtonSize.Large]: canvasButtonStyles.variants!.sizes.large,
- [ButtonSize.Medium]: canvasButtonStyles.variants!.sizes.medium,
- },
- },
-};
-
-export const textButtonStyles: ButtonGenericStyle = {
- classname: 'text-button',
- styles: {
- ...deprecatedButtonStyles.styles,
- borderRadius: borderRadius.m,
- border: '0',
- margin: '0 8px',
- minWidth: 'auto',
- '&:hover:not([disabled])': {textDecoration: 'underline'},
- },
- variants: {
- types: {
- [TextButtonVariant.Default]: {
- ...getButtonStateStyle(TextButtonVariant.Default),
- },
- [TextButtonVariant.Inverse]: {
- ...getButtonStateStyle(TextButtonVariant.Inverse),
- },
- [TextButtonVariant.AllCaps]: {
- ...getButtonStateStyle(TextButtonVariant.Default),
- height: canvas.spacing.l,
- },
- [TextButtonVariant.InverseAllCaps]: {
- ...getButtonStateStyle(TextButtonVariant.Inverse),
- height: canvas.spacing.l,
- },
- },
- sizes: {
- [ButtonSize.Large]: {
- height: canvas.spacing.xl,
- padding: '0 8px',
- },
- [ButtonSize.Small]: {
- height: canvas.spacing.l,
- padding: '0 8px',
- },
- },
- },
-};
-
-export const iconButtonStyles: ButtonGenericStyle = {
- classname: 'icon-button',
- styles: {
- // TODO: Support data-whatinput='input' css
- ...deprecatedButtonStyles.styles,
- borderWidth: '0',
- borderRadius: borderRadius.circle,
- ['& .wd-icon']: {
- display: 'inline-block',
- verticalAlign: 'middle',
- },
- },
- variants: {
- sizes: {
- [ButtonSize.Small]: {
- minWidth: canvas.spacing.l, // min-width is set so buttons don't collapse in IE11
- width: 'auto',
- height: canvas.spacing.l,
- 'span svg': {
- width: '20px',
- height: '20px',
- },
- },
- [ButtonSize.Medium]: {
- minWidth: canvas.spacing.xl,
- width: canvas.spacing.xl,
- height: canvas.spacing.xl,
- },
- },
- types: {
- [IconButtonVariant.Square]: {
- borderRadius: borderRadius.m,
- minWidth: canvas.spacing.l,
- width: canvas.spacing.l,
- height: canvas.spacing.l,
- ...getIconButtonStateStyle(IconButtonVariant.Square),
- },
- [IconButtonVariant.SquareFilled]: {
- borderRadius: borderRadius.m,
- minWidth: canvas.spacing.l,
- width: canvas.spacing.l,
- height: canvas.spacing.l,
- ...getIconButtonStateStyle(IconButtonVariant.SquareFilled),
- },
- [IconButtonVariant.Plain]: {
- ...getIconButtonStateStyle(IconButtonVariant.Plain),
- },
- [IconButtonVariant.Circle]: {
- ...getIconButtonStateStyle(IconButtonVariant.Circle),
- },
- [IconButtonVariant.CircleFilled]: {
- ...getIconButtonStateStyle(IconButtonVariant.CircleFilled),
- },
- [IconButtonVariant.Inverse]: {
- ...getIconButtonStateStyle(IconButtonVariant.Inverse),
- },
- [IconButtonVariant.InverseFilled]: {
- ...getIconButtonStateStyle(IconButtonVariant.InverseFilled),
- },
- },
- },
-};
diff --git a/modules/button/react/lib/DeleteButton.tsx b/modules/button/react/lib/DeleteButton.tsx
new file mode 100644
index 0000000000..0c34dedb8b
--- /dev/null
+++ b/modules/button/react/lib/DeleteButton.tsx
@@ -0,0 +1,70 @@
+import * as React from 'react';
+import {ButtonColors, ButtonSize, ButtonOrAnchorComponent} from './types';
+import {ButtonContainer, ButtonLabel} from './parts';
+import {
+ GrowthBehavior,
+ useTheme,
+ Themeable,
+ EmotionCanvasTheme,
+} from '@workday/canvas-kit-react-common';
+
+export interface DeleteButtonProps
+ extends React.ButtonHTMLAttributes,
+ Themeable,
+ GrowthBehavior {
+ /**
+ * The size of the Button.
+ * @default 'medium'
+ */
+ size?: 'small' | 'medium' | 'large';
+ /**
+ * The ref to the button that the styled component renders.
+ */
+ buttonRef?: React.Ref;
+ /**
+ * The alternative container type for the button. Uses Emotion's special `as` prop.
+ * Will render an `a` tag instead of a `button` when defined.
+ */
+ as?: 'a';
+}
+
+const getDeleteButtonColors = ({
+ canvas: {
+ palette: {error: themeError},
+ },
+}: EmotionCanvasTheme): ButtonColors => ({
+ default: {
+ background: themeError.main,
+ label: themeError.contrast,
+ },
+ hover: {
+ background: themeError.dark,
+ },
+ active: {
+ background: themeError.darkest,
+ },
+ focus: {
+ background: themeError.main,
+ },
+ disabled: {
+ background: themeError.light,
+ },
+});
+
+const DeleteButton: ButtonOrAnchorComponent = ({
+ theme = useTheme(),
+ size = 'medium',
+ buttonRef,
+ children,
+ ...elemProps
+}: DeleteButtonProps) => (
+
+ {children}
+
+);
+
+DeleteButton.Size = ButtonSize;
+// @ts-ignore ButtonOrAnchorComponent requires a Variant, but will complain Variable 'Variant' implicitly has an 'any' type. ts(7005)
+DeleteButton.Variant = undefined;
+
+export default DeleteButton;
diff --git a/modules/button/react/lib/DropdownButton.tsx b/modules/button/react/lib/DropdownButton.tsx
index 4c634db73e..2128b27854 100644
--- a/modules/button/react/lib/DropdownButton.tsx
+++ b/modules/button/react/lib/DropdownButton.tsx
@@ -1,40 +1,66 @@
import * as React from 'react';
-import {ButtonBaseLabel, ButtonLabelIcon} from './ButtonBase';
-import {getButtonStyle, getButtonSize} from './utils';
-import {styled} from '@workday/canvas-kit-labs-react-core';
-import isPropValid from '@emotion/is-prop-valid';
-import {BaseButtonProps} from './Button';
-import {dropdownButtonStyles} from './ButtonStyles';
import {caretDownIcon} from '@workday/canvas-system-icons-web';
-import {ButtonSize, ButtonVariant} from './types';
+import {GrowthBehavior, useTheme, Themeable} from '@workday/canvas-kit-react-common';
+import {DropdownButtonVariant, ButtonIconPosition, ButtonOrAnchorComponent} from './types';
+import {ButtonContainer, ButtonLabel, ButtonLabelIcon} from './parts';
+import {getButtonColors} from './Button';
-const DropdownButtonCon = styled('button', {
- shouldForwardProp: prop => isPropValid(prop) && prop !== 'size',
-})(
- dropdownButtonStyles.styles,
- ({variant}) => getButtonStyle(dropdownButtonStyles, variant),
- ({size}) => getButtonSize(dropdownButtonStyles, size)
-);
-
-export default class DropdownButton extends React.Component {
- public static Variant = ButtonVariant;
- public static Size = ButtonSize;
+export interface DropdownButtonProps
+ extends React.ButtonHTMLAttributes,
+ Themeable,
+ GrowthBehavior {
+ /**
+ * The variant of the Button.
+ * @default DropdownButtonVariant.Secondary
+ */
+ variant?: DropdownButtonVariant;
+ /**
+ * The size of the Button.
+ * @default 'medium'
+ */
+ size?: 'medium' | 'large';
+ /**
+ * The ref to the button that the styled component renders.
+ */
+ buttonRef?: React.Ref;
+ /**
+ * The alternative container type for the button. Uses Emotion's special `as` prop.
+ * Will render an `a` tag instead of a `button` when defined.
+ */
+ as?: 'a';
+}
- static defaultProps = {
- variant: ButtonVariant.Primary,
- size: ButtonSize.Medium,
- };
+const DropdownButton: ButtonOrAnchorComponent<
+ DropdownButtonProps,
+ typeof DropdownButtonVariant
+> = ({
+ theme = useTheme(),
+ variant = DropdownButtonVariant.Secondary,
+ size = 'medium',
+ buttonRef,
+ children,
+ ...elemProps
+}: DropdownButtonProps) => (
+
+ {children}
+
+
+);
- public render() {
- const {variant, size, buttonRef, dataLabel, icon, children, ...elemProps} = this.props;
+DropdownButton.Variant = DropdownButtonVariant;
+DropdownButton.Size = {
+ Medium: 'medium',
+ Large: 'large',
+} as const;
- return (
-
-
- {children}
-
-
-
- );
- }
-}
+export default DropdownButton;
diff --git a/modules/button/react/lib/HighlightButton.tsx b/modules/button/react/lib/HighlightButton.tsx
new file mode 100644
index 0000000000..b2d8ce1dac
--- /dev/null
+++ b/modules/button/react/lib/HighlightButton.tsx
@@ -0,0 +1,101 @@
+import * as React from 'react';
+import {colors} from '@workday/canvas-kit-react-core';
+import {
+ GrowthBehavior,
+ useTheme,
+ Themeable,
+ EmotionCanvasTheme,
+} from '@workday/canvas-kit-react-common';
+import {CanvasSystemIcon} from '@workday/design-assets-types';
+import {ButtonColors, ButtonOrAnchorComponent} from './types';
+import {ButtonContainer, ButtonLabel, ButtonLabelIcon} from './parts';
+
+export interface HighlightButtonProps
+ extends React.ButtonHTMLAttributes,
+ Themeable,
+ GrowthBehavior {
+ /**
+ * The size of the HighlightButton.
+ * @default 'medium'
+ */
+ size?: 'medium' | 'large';
+ /**
+ * The ref to the button that the styled component renders.
+ */
+ buttonRef?: React.Ref;
+ /**
+ * The icon of the HighlightButton.
+ */
+ icon?: CanvasSystemIcon;
+ /**
+ * The alternative container type for the button. Uses Emotion's special `as` prop.
+ * Will render an `a` tag instead of a `button` when defined.
+ */
+ as?: 'a';
+}
+
+const getHighlightButtonColors = ({
+ canvas: {
+ palette: {primary: themePrimary},
+ },
+}: EmotionCanvasTheme): ButtonColors => ({
+ default: {
+ background: colors.soap200,
+ border: colors.soap200,
+ icon: themePrimary.dark,
+ label: themePrimary.dark,
+ },
+ hover: {
+ background: colors.soap400,
+ border: 'transparent',
+ icon: themePrimary.dark,
+ label: themePrimary.dark,
+ },
+ active: {
+ background: colors.soap500,
+ border: 'transparent',
+ icon: themePrimary.dark,
+ label: themePrimary.dark,
+ },
+ focus: {
+ background: colors.soap200,
+ border: 'transparent',
+ icon: themePrimary.dark,
+ label: themePrimary.dark,
+ },
+ disabled: {
+ background: colors.soap100,
+ border: 'transparent',
+ icon: colors.soap600,
+ label: colors.licorice100,
+ },
+});
+
+const HighlightButton: ButtonOrAnchorComponent = ({
+ theme = useTheme(),
+ size = 'medium',
+ buttonRef,
+ icon,
+ children,
+ ...elemProps
+}: HighlightButtonProps) => (
+
+ {icon && }
+ {children}
+
+);
+
+HighlightButton.Size = {
+ Medium: 'medium',
+ Large: 'large',
+} as const;
+
+// @ts-ignore ButtonOrAnchorComponent requires a Variant, but will complain Variable 'Variant' implicitly has an 'any' type. ts(7005)
+HighlightButton.Variant = undefined;
+
+export default HighlightButton;
diff --git a/modules/button/react/lib/Hyperlink.tsx b/modules/button/react/lib/Hyperlink.tsx
new file mode 100644
index 0000000000..eacad88e4b
--- /dev/null
+++ b/modules/button/react/lib/Hyperlink.tsx
@@ -0,0 +1,51 @@
+import * as React from 'react';
+import {colors} from '@workday/canvas-kit-react-core';
+import {type} from '@workday/canvas-kit-labs-react-core';
+import {TextButtonVariant} from './types';
+import {styled} from '@workday/canvas-kit-react-common';
+
+export interface HyperlinkProps extends React.AnchorHTMLAttributes {
+ /**
+ * The variant of the Hyperlink.
+ * @default TextButtonVariant.Default
+ */
+ variant?: TextButtonVariant;
+ /**
+ * The href of the anchor tag.
+ */
+ href?: string;
+}
+
+const Anchor = styled('a')(
+ {
+ fontFamily: type.body.fontFamily,
+ ...type.variant.link,
+ },
+ ({variant}) => {
+ if (variant === TextButtonVariant.Inverse) {
+ return {
+ color: colors.frenchVanilla100,
+ '&:hover': {
+ color: colors.frenchVanilla100,
+ background: 'rgba(255, 255, 255, 0.1)',
+ },
+ '&:focus': {
+ boxShadow: `0 0 0 2px ${colors.frenchVanilla100}`,
+ },
+ '&:active': {
+ color: colors.blueberry600,
+ background: colors.soap200,
+ },
+ };
+ }
+ return {};
+ }
+);
+
+const Hyperlink = ({variant, href, ...elemProps}: HyperlinkProps) => (
+
+);
+
+Hyperlink.Variant = TextButtonVariant;
+
+export default Hyperlink;
diff --git a/modules/button/react/lib/IconButton.tsx b/modules/button/react/lib/IconButton.tsx
index aa300ffd88..773ecb7305 100644
--- a/modules/button/react/lib/IconButton.tsx
+++ b/modules/button/react/lib/IconButton.tsx
@@ -1,33 +1,29 @@
import * as React from 'react';
-import {styled} from '@workday/canvas-kit-labs-react-core';
-import isPropValid from '@emotion/is-prop-valid';
-import {IconButtonVariant, IconButtonSize} from './types';
-import {iconButtonStyles} from './ButtonStyles';
-import {getButtonStyle} from './utils';
-import {colors} from '@workday/canvas-kit-react-core';
+import {colors, spacing, borderRadius} from '@workday/canvas-kit-react-core';
+import {focusRing, useTheme, Themeable, EmotionCanvasTheme} from '@workday/canvas-kit-react-common';
import {SystemIcon} from '@workday/canvas-kit-react-icon';
-import {focusRing, mouseFocusBehavior} from '@workday/canvas-kit-react-common';
import {CanvasSystemIcon} from '@workday/design-assets-types';
-import {ClassNames, CSSObject} from '@emotion/core';
+import {IconButtonVariant, ButtonColors, ButtonOrAnchorComponent} from './types';
+import {ButtonContainer} from './parts';
-export interface IconButtonProps extends React.ButtonHTMLAttributes {
- /**
- * The type of the IconButton.
- * @default IconButtonVariant.Circle
- */
- variant: IconButtonVariant;
+export interface IconButtonProps extends React.ButtonHTMLAttributes, Themeable {
/**
* The accessibility label to indicate the action triggered by clicking the IconButton.
*/
'aria-label': string;
+ /**
+ * The type of the IconButton.
+ * @default IconButtonVariant.Circle
+ */
+ variant?: IconButtonVariant;
/**
* The size of the IconButton.
- * @default IconButtonSize.Medium
+ * @default 'medium'
*/
- size?: IconButtonSize;
+ size?: 'small' | 'medium';
/**
- * If true, toggle the IconButton on.
- * @default false
+ * The toggled state of the button. If defined as a boolean, then it manages the toggled state: on (`true`) or off (`false`).
+ * @default undefined
*/
toggled?: boolean;
/**
@@ -42,229 +38,232 @@ export interface IconButtonProps extends React.ButtonHTMLAttributes void;
+ /**
+ * The alternative container type for the button. Uses Emotion's special `as` prop.
+ * Will render an `a` tag instead of a `button` when defined.
+ */
+ as?: 'a';
}
-function getFillSelector(fillColor: string): CSSObject {
- return {
- '&:focus span .wd-icon-fill, &:hover span .wd-icon-fill, span .wd-icon-fill': {
- fill: fillColor,
- },
- };
-}
+const IconButton: ButtonOrAnchorComponent<
+ IconButtonProps,
+ typeof IconButtonVariant,
+ 'aria-label'
+> = ({
+ theme = useTheme(),
+ variant = IconButtonVariant.Circle,
+ size = 'medium',
+ buttonRef,
+ onToggleChange,
+ 'aria-label': iconArialabel,
+ icon,
+ toggled,
+ children,
+ ...elemProps
+}: IconButtonProps) => {
+ const isInitialMount = React.useRef(true);
-function getBackgroundSelector(fillColor: string): CSSObject {
- return {
- '&:hover span .wd-icon-background, span .wd-icon-background': {
- fill: fillColor,
- },
- };
-}
+ // Only call onToggleChange on update - not on first mount
+ React.useEffect(() => {
+ if (isInitialMount.current) {
+ isInitialMount.current = false;
+ } else {
+ if (toggled && typeof onToggleChange === 'function') {
+ onToggleChange(toggled);
+ }
+ }
+ }, [toggled, onToggleChange]);
-function getAccentSelector(fillColor: string): CSSObject {
- return {
- '&:focus span .wd-icon-accent, &:hover span .wd-icon-accent, span .wd-icon-accent': {
- fill: fillColor,
+ const containerStyles = {
+ padding: 0,
+ margin: variant === IconButtonVariant.Plain ? '-8px' : undefined,
+ minWidth: size === 'small' ? spacing.l : spacing.xl, // min-width is set so buttons don't collapse in IE11
+ width: size === 'small' ? spacing.l : spacing.xl,
+ height: size === 'small' ? spacing.l : spacing.xl,
+ ...getIconButtonBorderRadius(variant),
+ ['& .wd-icon']: {
+ display: 'inline-block',
+ verticalAlign: 'middle',
+ width: size === 'small' ? '20px' : undefined,
+ height: size === 'small' ? '20px' : undefined,
},
};
-}
-export const iconButtonIdentifier = 'wdc-ckr-icon-button';
+ return (
+
+ {icon ? : children}
+
+ );
+};
-export const IconButtonCon = styled('button', {
- shouldForwardProp: prop => isPropValid(prop) && prop !== 'size',
-})(
- iconButtonStyles.styles,
- ({variant}) => getButtonStyle(iconButtonStyles, variant),
- ({size, variant}) => {
- switch (size) {
- default:
- case IconButtonSize.Medium:
- return {
- margin: variant === IconButtonVariant.Plain ? '-8px' : undefined,
- ...iconButtonStyles.variants!.sizes.medium,
- };
- case IconButtonSize.Small:
- return {
- margin: variant === IconButtonVariant.Plain ? '-6px' : undefined,
- ...iconButtonStyles.variants!.sizes.small,
- };
- }
- },
- ({variant, toggled}) => {
- if (!toggled) {
- return {};
- }
- switch (variant) {
- case IconButtonVariant.CircleFilled:
- case IconButtonVariant.SquareFilled:
- case IconButtonVariant.Circle:
- case IconButtonVariant.Square:
- default: {
- return {
- backgroundColor: colors.blueberry400,
- ...getFillSelector(colors.frenchVanilla100),
- ...getAccentSelector(colors.blueberry400),
- ...getBackgroundSelector(colors.frenchVanilla100),
- '&:focus&:hover, &:focus, &:active, &:active:hover': {
- ...getFillSelector(colors.frenchVanilla100),
- ...getAccentSelector(colors.blueberry400),
- ...getBackgroundSelector(colors.frenchVanilla100),
- backgroundColor: colors.blueberry500,
- },
- '&:not([disabled])': {
- '&:focus, &:focus:active': {
- backgroundColor: colors.blueberry500,
- ...(toggled ? focusRing(2, 2) : {}),
- },
- },
- '&:hover, &:active': {
- ...getFillSelector(colors.frenchVanilla100),
- ...getAccentSelector(colors.blueberry400),
- ...getBackgroundSelector(colors.frenchVanilla100),
- backgroundColor: colors.blueberry500,
- },
- '&:disabled, &:active:disabled, &:focus:disabled, &:hover:disabled': {
- backgroundColor: colors.blueberry100,
- ...getFillSelector(colors.blueberry300),
- ...getAccentSelector(colors.frenchVanilla100),
- ...getBackgroundSelector(colors.blueberry300),
- },
- ...mouseFocusBehavior({
- '&:focus:active': {
- ...getFillSelector(colors.frenchVanilla100),
- ...getAccentSelector(colors.blueberry400),
- ...getBackgroundSelector(colors.frenchVanilla100),
- backgroundColor: `${colors.blueberry500} !important`,
- },
- }),
- };
- }
+IconButton.Variant = IconButtonVariant;
+IconButton.Size = {
+ Small: 'small',
+ Medium: 'medium',
+} as const;
- case IconButtonVariant.Plain:
- return {
- backgroundColor: 'transparent',
- ...getFillSelector(colors.blueberry400),
- ...getAccentSelector(colors.frenchVanilla100),
- ...getBackgroundSelector(colors.blueberry400),
- '&:focus:hover, &:focus, &:active, &:active:hover': {
- ...getFillSelector(colors.blueberry400),
- ...getAccentSelector(colors.frenchVanilla100),
- ...getBackgroundSelector(colors.blueberry400),
- },
- '&:not([disabled]):focus': {
- ...getFillSelector(colors.blueberry400),
- ...getAccentSelector(colors.frenchVanilla100),
- ...getBackgroundSelector(colors.blueberry400),
- ...(toggled ? focusRing(2, 0) : {}),
- },
- '&:disabled, &:active:disabled, &:focus:disabled, &:hover:disabled': {
- ...getFillSelector(colors.blueberry200),
- ...getAccentSelector(colors.frenchVanilla100),
- ...getBackgroundSelector(colors.blueberry200),
- },
- };
+export default IconButton;
- case IconButtonVariant.Inverse:
- case IconButtonVariant.InverseFilled:
- return {
- ...getFillSelector(colors.blueberry400),
- ...getAccentSelector(colors.frenchVanilla100),
- ...getBackgroundSelector(colors.blueberry400),
- backgroundColor: colors.frenchVanilla100,
- '&:focus': {
- backgroundColor: colors.frenchVanilla100,
- ...getFillSelector(colors.blueberry400),
- ...getAccentSelector(colors.frenchVanilla100),
- ...getBackgroundSelector(colors.blueberry400),
- },
- '&:focus&:hover, &:active, &:active:hover': {
- backgroundColor: colors.frenchVanilla100,
- ...getFillSelector(colors.blueberry400),
- ...getAccentSelector(colors.frenchVanilla100),
- ...getBackgroundSelector(colors.blueberry400),
- },
- '&:focus:active': {
- backgroundColor: colors.frenchVanilla100,
- },
-
- '&:hover': {
- backgroundColor: colors.frenchVanilla100,
- },
- '&:not([disabled])': {
- '&:focus': {
- backgroundColor: colors.frenchVanilla100,
- ...(toggled
- ? focusRing(2, 2, true, false, 'currentColor', colors.frenchVanilla100)
- : {}),
- },
- '&:focus:active': {
- backgroundColor: colors.frenchVanilla100,
- },
- },
- '&:disabled, &:active:disabled, &:focus:disabled, &:hover:disabled': {
- backgroundColor: 'rgba(255,255,255,0.75)',
- ...getFillSelector(colors.blueberry400),
- ...getAccentSelector(colors.frenchVanilla100),
- ...getBackgroundSelector(colors.blueberry400),
- },
- ...mouseFocusBehavior({
- '&:focus:active': {
- backgroundColor: `${colors.frenchVanilla100} !important`,
- },
- }),
- };
- }
+const getIconButtonBorderRadius = (variant: IconButtonVariant) => {
+ switch (variant) {
+ case IconButtonVariant.Square:
+ case IconButtonVariant.SquareFilled:
+ return {borderRadius: borderRadius.m};
+ default:
+ return {borderRadius: borderRadius.circle};
}
-);
+};
-export default class IconButton extends React.Component {
- public static Variant = IconButtonVariant;
- public static Size = IconButtonSize;
-
- static defaultProps = {
- variant: IconButtonVariant.Circle,
- size: IconButtonSize.Medium,
- } as const;
-
- componentDidUpdate(prevProps: IconButtonProps) {
- if (
- prevProps.toggled !== this.props.toggled &&
- typeof this.props.onToggleChange === 'function'
- ) {
- this.props.onToggleChange(this.props.toggled);
- }
- }
-
- public render() {
- // onToggleChange will generate a warning if spread over a
- const {
- buttonRef,
- size,
- variant,
- onToggleChange,
- icon,
- toggled,
- children,
- className,
- ...elemProps
- } = this.props;
+const getIconButtonColors = (
+ variant: IconButtonVariant,
+ theme: EmotionCanvasTheme,
+ toggled?: boolean
+): ButtonColors => {
+ const {
+ canvas: {
+ palette: {primary: themePrimary},
+ },
+ } = theme;
- return (
-
- {({cx}) => (
-
- {icon ? : children}
-
- )}
-
- );
+ switch (variant) {
+ case IconButton.Variant.Square:
+ case IconButtonVariant.Circle:
+ default:
+ return {
+ default: {
+ background: toggled ? themePrimary.main : undefined,
+ icon: toggled ? themePrimary.contrast : colors.licorice200,
+ },
+ hover: {
+ background: toggled ? themePrimary.dark : colors.soap300,
+ icon: toggled ? themePrimary.contrast : colors.licorice500,
+ },
+ active: {
+ background: toggled ? themePrimary.dark : colors.soap500,
+ icon: toggled ? themePrimary.contrast : colors.licorice500,
+ },
+ focus: {
+ background: toggled ? themePrimary.main : undefined,
+ icon: toggled ? themePrimary.contrast : colors.licorice500,
+ },
+ disabled: {
+ background: toggled ? themePrimary.lightest : 'transparent',
+ icon: toggled ? themePrimary.light : colors.soap600,
+ },
+ };
+ case IconButtonVariant.SquareFilled:
+ case IconButtonVariant.CircleFilled:
+ return {
+ default: {
+ background: toggled ? themePrimary.main : colors.soap200,
+ icon: toggled ? themePrimary.contrast : colors.licorice200,
+ },
+ hover: {
+ background: toggled ? themePrimary.dark : colors.soap400,
+ icon: toggled ? themePrimary.contrast : colors.licorice500,
+ },
+ active: {
+ background: toggled ? themePrimary.dark : colors.soap500,
+ icon: toggled ? themePrimary.contrast : colors.licorice500,
+ },
+ focus: {
+ background: toggled ? themePrimary.main : colors.soap200,
+ icon: toggled ? themePrimary.contrast : colors.licorice500,
+ },
+ disabled: {
+ background: toggled ? themePrimary.lightest : colors.soap100,
+ icon: toggled ? themePrimary.light : colors.soap600,
+ },
+ };
+ case IconButtonVariant.Plain:
+ return {
+ default: {
+ icon: toggled ? themePrimary.main : colors.licorice200,
+ },
+ hover: {
+ icon: toggled ? themePrimary.main : colors.licorice500,
+ },
+ active: {
+ icon: toggled ? themePrimary.main : colors.licorice500,
+ },
+ focus: {
+ icon: toggled ? themePrimary.main : colors.licorice500,
+ focusRing: focusRing({}, theme),
+ },
+ disabled: {
+ icon: toggled ? themePrimary.light : colors.soap600,
+ },
+ };
+ case IconButtonVariant.Inverse:
+ return {
+ default: {
+ background: toggled ? themePrimary.contrast : undefined,
+ icon: toggled ? themePrimary.main : themePrimary.contrast,
+ },
+ hover: {
+ background: toggled ? themePrimary.contrast : 'rgba(0, 0, 0, 0.2)',
+ icon: toggled ? themePrimary.main : themePrimary.contrast,
+ },
+ active: {
+ background: toggled ? themePrimary.contrast : 'rgba(0, 0, 0, 0.3)',
+ icon: toggled ? themePrimary.main : themePrimary.contrast,
+ },
+ focus: {
+ background: toggled ? themePrimary.contrast : undefined,
+ icon: toggled ? themePrimary.main : themePrimary.contrast,
+ focusRing: focusRing(
+ {
+ separation: 2,
+ innerColor: 'currentColor',
+ outerColor: themePrimary.contrast,
+ },
+ theme
+ ),
+ },
+ disabled: {
+ background: toggled ? 'rgba(255,255,255,0.75)' : 'transparent',
+ icon: toggled ? themePrimary.main : 'rgba(255, 255, 255, 0.75)',
+ },
+ };
+ case IconButtonVariant.InverseFilled:
+ return {
+ default: {
+ background: toggled ? themePrimary.contrast : 'rgba(0, 0, 0, 0.2)',
+ icon: toggled ? themePrimary.main : themePrimary.contrast,
+ },
+ hover: {
+ background: toggled ? themePrimary.contrast : 'rgba(0, 0, 0, 0.3)',
+ icon: toggled ? themePrimary.main : themePrimary.contrast,
+ },
+ active: {
+ background: toggled ? themePrimary.contrast : 'rgba(0, 0, 0, 0.4)',
+ icon: toggled ? themePrimary.main : themePrimary.contrast,
+ },
+ focus: {
+ background: toggled ? themePrimary.contrast : 'rgba(0, 0, 0, 0.2)',
+ icon: toggled ? themePrimary.main : themePrimary.contrast,
+ focusRing: focusRing(
+ {
+ separation: 2,
+ innerColor: 'currentColor',
+ outerColor: themePrimary.contrast,
+ },
+ theme
+ ),
+ },
+ disabled: {
+ background: toggled ? 'rgba(255,255,255,0.75)' : 'rgba(0, 0, 0, 0.2)',
+ icon: toggled ? themePrimary.main : 'rgba(255, 255, 255, 0.75)',
+ },
+ };
}
-}
+};
diff --git a/modules/button/react/lib/IconButtonToggleGroup.tsx b/modules/button/react/lib/IconButtonToggleGroup.tsx
deleted file mode 100644
index 47e4450e25..0000000000
--- a/modules/button/react/lib/IconButtonToggleGroup.tsx
+++ /dev/null
@@ -1,102 +0,0 @@
-import * as React from 'react';
-import styled from '@emotion/styled';
-import {spacing, borderRadius} from '@workday/canvas-kit-react-core';
-import IconButton, {IconButtonProps, iconButtonIdentifier} from './IconButton';
-
-export interface IconButtonToggleGroupProps {
- /**
- * The IconButton children of the IconButtonToggleGroup (must be at least two).
- */
- children: React.ReactElement[];
-
- /**
- * The value or index of the IconButton that the IconButtonToggleGroup should be toggled on to. If a string is provided, the IconButton with the corresponding value will be selected. If a number is provided, the IconButton with the corresponding index will be selected.
- * @default 0
- */
- value?: string | number;
-
- /**
- * If true, render the IconButtonToggleGroup from right to left.
- * @default false
- */
- isRTL?: boolean;
-
- /**
- * The function called when a button in the IconButtonToggleGroup is selected. If the selected button has a value, that value will be passed to the callback function; otherwise, the index of the button will be passed.
- */
- onChange?: (value: string | number) => void;
-}
-
-const Container = styled('div')({
- [`.${iconButtonIdentifier}`]: {
- borderRadius: borderRadius.zero,
- borderWidth: '1px',
- marginLeft: '-1px',
- '&:first-child': {
- borderTopLeftRadius: spacing.xxxs,
- borderBottomLeftRadius: spacing.xxxs,
- marginLeft: 0,
- },
- '&:last-child': {
- borderTopRightRadius: spacing.xxxs,
- borderBottomRightRadius: spacing.xxxs,
- },
- '&:focus': {
- borderRadius: spacing.xxxs,
- zIndex: 1,
- animation: 'none', // reset focusRing animation
- transition: 'all 120ms, border-radius 1ms',
- },
- },
-});
-
-export default class IconButtonToggleGroup extends React.Component {
- static defaultProps = {
- value: 0,
- };
-
- render(): React.ReactNode {
- const children = this.props.isRTL ? [...this.props.children].reverse() : this.props.children;
- return {React.Children.map(children, this.renderChild)} ;
- }
-
- private renderChild = (
- child: React.ReactElement,
- index: number
- ): React.ReactNode => {
- if (typeof child.type === typeof IconButton) {
- const childProps = child.props;
- const toggled =
- typeof this.props.value === 'number'
- ? index === this.props.value
- : childProps.value === this.props.value;
-
- return React.cloneElement(child, {
- toggled,
- variant: IconButton.Variant.SquareFilled,
- onClick: this.onButtonClick.bind(this, childProps.onClick, index),
- });
- }
-
- return child;
- };
-
- private onButtonClick = (
- existingOnClick: (e: React.SyntheticEvent) => void | undefined,
- index: number,
- event: React.MouseEvent
- ): void => {
- if (existingOnClick) {
- existingOnClick(event);
- }
-
- const target = event.currentTarget;
- if (target && this.props.onChange) {
- if (target.value) {
- this.props.onChange(target.value);
- } else {
- this.props.onChange(index);
- }
- }
- };
-}
diff --git a/modules/button/react/lib/OutlineButton.tsx b/modules/button/react/lib/OutlineButton.tsx
new file mode 100644
index 0000000000..f7cde657df
--- /dev/null
+++ b/modules/button/react/lib/OutlineButton.tsx
@@ -0,0 +1,196 @@
+import * as React from 'react';
+import {colors} from '@workday/canvas-kit-react-core';
+import {
+ focusRing,
+ GrowthBehavior,
+ useTheme,
+ Themeable,
+ EmotionCanvasTheme,
+} from '@workday/canvas-kit-react-common';
+import {CanvasSystemIcon} from '@workday/design-assets-types';
+import {OutlineButtonVariant, ButtonColors, ButtonSize, ButtonOrAnchorComponent} from './types';
+import {ButtonContainer, ButtonLabel, ButtonLabelData, ButtonLabelIcon} from './parts';
+
+export interface OutlineButtonProps
+ extends React.ButtonHTMLAttributes,
+ Themeable,
+ GrowthBehavior {
+ /**
+ * The variant of the Button.
+ * @default OutlineButtonVariant.Secondary
+ */
+ variant?: OutlineButtonVariant;
+ /**
+ * The size of the Button.
+ * @default 'medium'
+ */
+ size?: 'small' | 'medium' | 'large';
+ /**
+ * The ref to the button that the styled component renders.
+ */
+ buttonRef?: React.Ref;
+ /**
+ * The data label of the Button.
+ * Note: not displayed at `small` size
+ */
+ dataLabel?: String;
+ /**
+ * The icon of the Button.
+ * Note: not displayed at `small` size
+ */
+ icon?: CanvasSystemIcon;
+ /**
+ * The alternative container type for the button. Uses Emotion's special `as` prop.
+ * Will render an `a` tag instead of a `button` when defined.
+ */
+ as?: 'a';
+}
+
+const OutlineButton: ButtonOrAnchorComponent = ({
+ theme = useTheme(),
+ variant = OutlineButtonVariant.Secondary,
+ size = 'medium',
+ buttonRef,
+ dataLabel,
+ icon,
+ children,
+ ...elemProps
+}: OutlineButtonProps) => (
+
+ {icon && size !== 'small' && }
+ {children}
+ {dataLabel && size !== 'small' && {dataLabel} }
+
+);
+
+OutlineButton.Variant = OutlineButtonVariant;
+OutlineButton.Size = ButtonSize;
+
+export default OutlineButton;
+
+export const getOutlineButtonColors = (
+ variant: OutlineButtonVariant,
+ theme: EmotionCanvasTheme
+): ButtonColors => {
+ const {
+ canvas: {
+ palette: {primary: themePrimary},
+ },
+ } = theme;
+
+ switch (variant) {
+ case OutlineButtonVariant.Primary:
+ return {
+ default: {
+ background: 'transparent',
+ border: themePrimary.main,
+ icon: themePrimary.main,
+ label: themePrimary.main,
+ },
+ hover: {
+ background: themePrimary.main,
+ icon: themePrimary.contrast,
+ label: themePrimary.contrast,
+ },
+ active: {
+ background: themePrimary.dark,
+ border: themePrimary.dark,
+ icon: themePrimary.contrast,
+ label: themePrimary.contrast,
+ },
+ focus: {
+ background: themePrimary.main,
+ icon: themePrimary.contrast,
+ label: themePrimary.contrast,
+ },
+ disabled: {
+ background: 'transparent',
+ border: colors.soap500,
+ icon: colors.soap600,
+ label: colors.licorice100,
+ },
+ };
+ case OutlineButtonVariant.Secondary:
+ default:
+ return {
+ default: {
+ background: 'transparent',
+ border: colors.soap500,
+ icon: colors.licorice200,
+ label: colors.blackPepper400,
+ },
+ hover: {
+ background: colors.licorice500,
+ border: colors.licorice500,
+ icon: themePrimary.contrast,
+ label: themePrimary.contrast,
+ },
+ active: {
+ background: colors.licorice600,
+ border: colors.licorice600,
+ icon: themePrimary.contrast,
+ label: themePrimary.contrast,
+ },
+ focus: {
+ background: colors.licorice500,
+ border: colors.licorice500,
+ icon: themePrimary.contrast,
+ label: themePrimary.contrast,
+ },
+ disabled: {
+ background: 'transparent',
+ border: colors.soap500,
+ icon: colors.soap600,
+ label: colors.licorice100,
+ },
+ };
+ case OutlineButtonVariant.Inverse:
+ return {
+ default: {
+ background: 'transparent',
+ border: colors.frenchVanilla100,
+ icon: colors.frenchVanilla100,
+ label: colors.frenchVanilla100,
+ },
+ hover: {
+ background: colors.frenchVanilla100,
+ icon: colors.licorice500,
+ label: colors.blackPepper400,
+ labelData: colors.licorice300,
+ },
+ active: {
+ background: colors.soap300,
+ border: colors.soap300,
+ icon: colors.licorice500,
+ label: colors.blackPepper400,
+ labelData: colors.licorice300,
+ },
+ focus: {
+ background: colors.frenchVanilla100,
+ icon: colors.licorice500,
+ label: colors.blackPepper400,
+ labelData: colors.licorice300,
+ focusRing: focusRing(
+ {
+ separation: 2,
+ innerColor: 'currentColor',
+ outerColor: colors.frenchVanilla100,
+ },
+ theme
+ ),
+ },
+ disabled: {
+ background: 'transparent',
+ border: 'rgba(255, 255, 255, 0.75)',
+ icon: 'rgba(255, 255, 255, 0.75)',
+ label: 'rgba(255, 255, 255, 0.75)',
+ labelData: 'rgba(255, 255, 255, 0.75)',
+ },
+ };
+ }
+};
diff --git a/modules/button/react/lib/TextButton.tsx b/modules/button/react/lib/TextButton.tsx
index f5c5376926..ce7e7ba0d2 100644
--- a/modules/button/react/lib/TextButton.tsx
+++ b/modules/button/react/lib/TextButton.tsx
@@ -1,74 +1,196 @@
import * as React from 'react';
-import {ButtonBaseLabel, ButtonLabelIcon} from './ButtonBase';
-import {getButtonStyle} from './utils';
-import {styled} from '@workday/canvas-kit-labs-react-core';
-import isPropValid from '@emotion/is-prop-valid';
-import {ButtonSize, IconPosition, TextButtonVariant} from './types';
-import {BaseButtonProps} from './Button';
-import {textButtonStyles} from './ButtonStyles';
+import {type} from '@workday/canvas-kit-labs-react-core';
+import {focusRing, useTheme, Themeable, EmotionCanvasTheme} from '@workday/canvas-kit-react-common';
+import {colors, spacing, borderRadius} from '@workday/canvas-kit-react-core';
+import {CanvasSystemIcon} from '@workday/design-assets-types';
+import {
+ TextButtonVariant,
+ ButtonIconPosition,
+ ButtonColors,
+ ButtonOrAnchorComponent,
+} from './types';
+import {ButtonContainer, ButtonLabelIcon, ButtonLabel} from './parts';
-export interface TextButtonProps extends BaseButtonProps {
+export interface TextButtonProps extends React.ButtonHTMLAttributes, Themeable {
+ /**
+ * The variant of the TextButton.
+ * @default TextButtonVariant.Default
+ */
+ variant?: TextButtonVariant;
+ /**
+ * The size of the TextButton.
+ * @default 'medium'
+ */
+ size?: 'small' | 'medium';
/**
* The position of the TextButton icon. Accepts `Left` or `Right`.
- * @default IconPosition.Left
+ * @default ButtonIconPosition.Left
+ */
+ iconPosition?: ButtonIconPosition;
+ /**
+ * The ref to the button that the styled component renders.
+ */
+ buttonRef?: React.Ref;
+ /**
+ * The icon of the TextButton.
*/
- iconPosition?: IconPosition;
+ icon?: CanvasSystemIcon;
+ /**
+ * The capitialization of the text in the button.
+ */
+ allCaps?: boolean;
+ /**
+ * The alternative container type for the button. Uses Emotion's special `as` prop.
+ * Will render an `a` tag instead of a `button` when defined.
+ */
+ as?: 'a';
}
-const TextButtonCon = styled('button', {
- shouldForwardProp: prop => isPropValid(prop) && prop !== 'size',
-})(
- textButtonStyles.styles,
- ({variant}) => getButtonStyle(textButtonStyles, variant),
- ({size}) => {
- const {sizes} = textButtonStyles.variants!;
+const getTextButtonColors = (
+ variant: TextButtonVariant,
+ theme: EmotionCanvasTheme
+): ButtonColors => {
+ const {
+ canvas: {
+ palette: {primary: themePrimary},
+ },
+ } = theme;
- switch (size) {
- case ButtonSize.Large:
- default:
- return sizes.large;
- case ButtonSize.Medium:
- case ButtonSize.Small:
- return sizes.small;
- }
+ switch (variant) {
+ case TextButtonVariant.Default:
+ default:
+ return {
+ default: {
+ icon: themePrimary.main,
+ label: themePrimary.main,
+ },
+ hover: {
+ background: colors.soap200,
+ icon: themePrimary.dark,
+ label: themePrimary.dark,
+ },
+ active: {
+ background: colors.soap300,
+ icon: themePrimary.dark,
+ label: themePrimary.dark,
+ },
+ focus: {
+ icon: themePrimary.dark,
+ label: themePrimary.dark,
+ focusRing: focusRing({}, theme),
+ },
+ disabled: {
+ background: 'transparent',
+ icon: themePrimary.light,
+ label: themePrimary.light,
+ },
+ };
+ case TextButtonVariant.Inverse:
+ return {
+ default: {
+ background: 'transparent',
+ icon: colors.frenchVanilla100,
+ label: colors.frenchVanilla100,
+ },
+ hover: {
+ background: colors.frenchVanilla100,
+ icon: colors.blackPepper400,
+ label: colors.blackPepper400,
+ },
+ active: {
+ background: colors.soap200,
+ icon: colors.blackPepper400,
+ label: colors.blackPepper400,
+ },
+ focus: {
+ background: colors.frenchVanilla100,
+ icon: colors.blackPepper400,
+ label: colors.blackPepper400,
+ focusRing: focusRing(
+ {
+ separation: 2,
+ inset: 'inner',
+ innerColor: 'currentColor',
+ outerColor: colors.frenchVanilla100,
+ },
+ theme
+ ),
+ },
+ disabled: {
+ background: 'transparent',
+ icon: 'rgba(255, 255, 255, 0.5)',
+ label: 'rgba(255, 255, 255, 0.5)',
+ },
+ };
}
-);
+};
-export default class TextButton extends React.Component {
- public static IconPosition = IconPosition;
- public static Variant = TextButtonVariant;
- public static Size = ButtonSize;
+const containerStyles = {
+ borderRadius: borderRadius.m,
+ border: '0',
+ padding: `0 ${spacing.xxs}`,
+ minWidth: 'auto',
+ '.wdc-text-button-label': {
+ borderBottom: '2px solid transparent',
+ paddingTop: '2px',
+ transition: 'border-color 0.3s',
+ },
+ '&:hover:not([disabled]) .wdc-text-button-label': {
+ borderBottomColor: 'currentColor',
+ },
+};
- static defaultProps = {
- iconPosition: IconPosition.Left,
- variant: TextButtonVariant.Default,
- size: ButtonSize.Large,
- };
+const TextButton: ButtonOrAnchorComponent & {
+ IconPosition: typeof ButtonIconPosition;
+} = ({
+ theme = useTheme(),
+ variant = TextButtonVariant.Default,
+ size = 'medium',
+ iconPosition = ButtonIconPosition.Left,
+ buttonRef,
+ children,
+ icon,
+ allCaps,
+ ...elemProps
+}: TextButtonProps) => {
+ // Note: We don't use ButtonLabel because the label styles differ from other button types
+ const allContainerStyles = allCaps
+ ? {
+ ...containerStyles,
+ ...type.variant.caps,
+ ...type.variant.button,
+ fontSize: size === 'medium' ? type.body.fontSize : undefined,
+ letterSpacing: '.5px',
+ }
+ : {
+ ...containerStyles,
+ fontSize: size === 'medium' ? type.body.fontSize : undefined,
+ };
- public render() {
- const {
- buttonRef,
- onClick,
- children,
- iconPosition,
- size,
- variant,
- icon,
- ...elemProps
- } = this.props;
+ return (
+
+ {icon && iconPosition === ButtonIconPosition.Left && (
+
+ )}
+ {children}
+ {icon && iconPosition === ButtonIconPosition.Right && (
+
+ )}
+
+ );
+};
- return (
-
- {icon && iconPosition === IconPosition.Left && (
-
- )}
-
- {children}
-
- {icon && iconPosition === IconPosition.Right && (
-
- )}
-
- );
- }
-}
+TextButton.IconPosition = ButtonIconPosition;
+TextButton.Variant = TextButtonVariant;
+TextButton.Size = {
+ Small: 'small',
+ Medium: 'medium',
+} as const;
+
+export default TextButton;
diff --git a/modules/button/react/lib/ToolbarDropdownButton.tsx b/modules/button/react/lib/ToolbarDropdownButton.tsx
new file mode 100644
index 0000000000..2643a7455b
--- /dev/null
+++ b/modules/button/react/lib/ToolbarDropdownButton.tsx
@@ -0,0 +1,88 @@
+/** @jsx jsx */
+import {jsx} from '@emotion/core';
+import {colors, spacing, borderRadius} from '@workday/canvas-kit-react-core';
+import {focusRing, useTheme, Themeable, EmotionCanvasTheme} from '@workday/canvas-kit-react-common';
+import {SystemIcon} from '@workday/canvas-kit-react-icon';
+import {ButtonColors} from './types';
+import {ButtonContainer} from './parts';
+import {chevronDownSmallIcon} from '@workday/canvas-system-icons-web';
+import {ToolbarIconButtonProps} from './ToolbarIconButton';
+
+export interface ToolbarDropdownButtonProps
+ extends Omit,
+ Themeable {}
+
+const containerStyles = {
+ padding: spacing.zero,
+ minWidth: spacing.l,
+ width: 'auto',
+ height: spacing.l,
+ borderRadius: borderRadius.m,
+ '& .wd-icon': {
+ display: 'inline-block',
+ verticalAlign: 'middle',
+ width: 20,
+ height: 20,
+ },
+ '& .wdc-toolbar-dropdown-btn-arrow': {
+ margin: '0 2px 0 0',
+ },
+ '& .wdc-toolbar-dropdown-btn-custom-icon': {
+ marginLeft: `${spacing.xxxs}`,
+ marginRight: 0,
+ width: 18, // decrease the space between a custom icon and the chevron per design
+ },
+};
+
+const ToolbarDropdownButton = ({
+ theme = useTheme(),
+ buttonRef,
+ 'aria-label': iconArialabel,
+ icon,
+ children,
+ ...elemProps
+}: ToolbarDropdownButtonProps) => {
+ return (
+
+ {icon ? (
+
+ ) : (
+ children
+ )}
+
+
+ );
+};
+
+export default ToolbarDropdownButton;
+
+const getToolbarDropdownButtonColors = (theme: EmotionCanvasTheme): ButtonColors => {
+ return {
+ default: {
+ icon: colors.licorice200,
+ },
+ hover: {
+ icon: colors.licorice500,
+ background: colors.soap300,
+ },
+ active: {
+ icon: colors.licorice500,
+ background: colors.soap500,
+ },
+ focus: {
+ icon: colors.licorice200,
+ focusRing: focusRing({width: 2, separation: 0}, theme),
+ background: 'transparent',
+ },
+ disabled: {
+ icon: colors.soap600,
+ background: 'transparent',
+ },
+ };
+};
diff --git a/modules/button/react/lib/ToolbarIconButton.tsx b/modules/button/react/lib/ToolbarIconButton.tsx
new file mode 100644
index 0000000000..4242e686ab
--- /dev/null
+++ b/modules/button/react/lib/ToolbarIconButton.tsx
@@ -0,0 +1,97 @@
+import * as React from 'react';
+import {colors, spacing, borderRadius} from '@workday/canvas-kit-react-core';
+import {focusRing, useTheme, Themeable, EmotionCanvasTheme} from '@workday/canvas-kit-react-common';
+import {SystemIcon} from '@workday/canvas-kit-react-icon';
+import {ButtonColors} from './types';
+import {ButtonContainer} from './parts';
+import {IconButtonProps} from './IconButton';
+
+export interface ToolbarIconButtonProps
+ extends Omit,
+ Themeable {}
+
+const containerStyles = {
+ padding: 0,
+ minWidth: spacing.l,
+ width: spacing.l,
+ height: spacing.l,
+ borderRadius: borderRadius.m,
+ ['& .wd-icon']: {
+ display: 'inline-block',
+ verticalAlign: 'middle',
+ width: 20,
+ height: 20,
+ },
+};
+
+const ToolbarIconButton = ({
+ theme = useTheme(),
+ buttonRef,
+ onToggleChange,
+ 'aria-label': iconArialabel,
+ icon,
+ toggled,
+ children,
+ ...elemProps
+}: ToolbarIconButtonProps) => {
+ const isInitialMount = React.useRef(true);
+
+ // Only call onToggleChange on update - not on first mount
+ React.useEffect(() => {
+ if (isInitialMount.current) {
+ isInitialMount.current = false;
+ } else {
+ if (toggled && typeof onToggleChange === 'function') {
+ onToggleChange(toggled);
+ }
+ }
+ }, [toggled, onToggleChange]);
+
+ return (
+
+ {icon ? : children}
+
+ );
+};
+
+export default ToolbarIconButton;
+
+const getToolbarIconButtonColors = (theme: EmotionCanvasTheme, toggled?: boolean): ButtonColors => {
+ const {
+ canvas: {
+ palette: {primary: themePrimary},
+ },
+ } = theme;
+ return {
+ default: {
+ icon: toggled ? themePrimary.main : colors.licorice200,
+ background: toggled ? themePrimary.lightest : 'transparent',
+ },
+ hover: {
+ icon: toggled ? themePrimary.dark : colors.licorice500,
+ background: colors.soap300,
+ },
+ active: {
+ icon: toggled ? themePrimary.dark : colors.licorice500,
+ background: colors.soap500,
+ },
+ focus: {
+ icon: toggled ? themePrimary.main : colors.licorice200,
+ focusRing: focusRing({width: 2, separation: 0}, theme),
+ background: toggled ? themePrimary.lightest : 'transparent',
+ },
+ disabled: {
+ icon: toggled ? themePrimary.light : colors.soap600,
+ background: toggled ? themePrimary.lightest : 'transparent',
+ },
+ };
+};
diff --git a/modules/button/react/lib/deprecated_Button.tsx b/modules/button/react/lib/deprecated_Button.tsx
new file mode 100644
index 0000000000..b294105916
--- /dev/null
+++ b/modules/button/react/lib/deprecated_Button.tsx
@@ -0,0 +1,189 @@
+import * as React from 'react';
+import canvas, {borderRadius, type} from '@workday/canvas-kit-react-core';
+import {focusRing, mouseFocusBehavior, GrowthBehavior} from '@workday/canvas-kit-react-common';
+import {DeprecatedButtonVariant, ButtonSize, ButtonOrAnchorComponent} from './types';
+import styled from '@emotion/styled';
+
+export interface DeprecatedButtonProps
+ extends React.ButtonHTMLAttributes,
+ GrowthBehavior {
+ /**
+ * The variant of the Button.
+ * @default DeprecatedButtonVariant.Secondary
+ */
+ variant?: DeprecatedButtonVariant;
+ /**
+ * The size of the Button.
+ * @default 'medium'
+ */
+ size?: 'small' | 'medium' | 'large';
+ /**
+ * The ref to the button that the styled component renders.
+ */
+ buttonRef?: React.Ref;
+ /**
+ * The alternative container type for the button. Uses Emotion's special `as` prop.
+ * Will render an `a` tag instead of a `button` when defined.
+ */
+ as?: 'a';
+}
+
+const Container = styled('button')(
+ {
+ fontFamily: type.body.fontFamily,
+ fontSize: type.body.fontSize,
+ ...type.variant.button,
+ boxSizing: 'border-box',
+ display: 'inline-flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ borderRadius: borderRadius.circle,
+ border: '1px solid transparent',
+ boxShadow: 'none',
+ position: 'relative',
+ cursor: 'pointer',
+ outline: 'none',
+ transition:
+ 'box-shadow 120ms linear, border 120ms linear, background-color 120ms linear, color 120ms linear',
+ '&:hover:active': {transitionDuration: '40ms'}, // Makes the "down" state of the button happens faster than the hover state, so it animates in correctly.
+ '&:disabled, &:disabled:active': {cursor: 'default', boxShadow: 'none'},
+ },
+ ({size}) => {
+ switch (size) {
+ case 'large':
+ return {
+ height: '40px',
+ padding: `0 ${canvas.spacing.l}`,
+ minWidth: '112px',
+ maxWidth: '288px',
+ };
+ case 'medium':
+ default:
+ return {
+ height: '24px',
+ padding: `0 ${canvas.spacing.m}`,
+ minWidth: '80px',
+ maxWidth: '200px',
+ fontSize: type.body2.fontSize,
+ };
+ case 'small':
+ return {
+ height: '18px',
+ padding: `0 ${canvas.spacing.xxs}`,
+ minWidth: '56px',
+ maxWidth: '120px',
+ fontSize: '10px',
+ lineHeight: 'normal',
+ };
+ }
+ },
+ ({grow}) => grow && {width: '100%', maxWidth: '100%'},
+ ({variant}) => {
+ let buttonColors;
+ switch (variant) {
+ case DeprecatedButtonVariant.Primary:
+ buttonColors = canvas.buttonColors.primary;
+ break;
+ case DeprecatedButtonVariant.Secondary:
+ default:
+ buttonColors = canvas.buttonColors.secondary;
+ break;
+ case DeprecatedButtonVariant.Delete:
+ buttonColors = {
+ ...canvas.buttonColors.delete,
+ focusBorder: canvas.colors.cinnamon500,
+ activeBorder: canvas.colors.cinnamon500,
+ };
+ break;
+ }
+
+ if (!buttonColors) {
+ return {};
+ }
+
+ const baseStyles = {
+ backgroundColor: buttonColors.background,
+ borderColor: buttonColors.border,
+ color: buttonColors.text,
+ };
+
+ const hoverStyles = {
+ ':hover': {
+ backgroundColor: buttonColors.hoverBackground,
+ borderColor: buttonColors.hoverBorder,
+ color: buttonColors.hoverText,
+ },
+ };
+
+ const activeStyles = {
+ ':active, :focus:active, :hover:active': {
+ backgroundColor: buttonColors.activeBackground,
+ borderColor: buttonColors.activeBorder,
+ color: buttonColors.activeText,
+ },
+ };
+
+ return {
+ ...baseStyles,
+ ':focus': {
+ backgroundColor: buttonColors.focusBackground,
+ borderColor: buttonColors.focusBorder,
+ color: buttonColors.focusText,
+ },
+
+ ...activeStyles,
+ ...hoverStyles,
+ ':disabled, :active:disabled, :focus:disabled, :hover:disabled': {
+ backgroundColor: buttonColors.disabledBackground,
+ borderColor: buttonColors.disabledBorder,
+ color: buttonColors.disabledText,
+ },
+ '&:not([disabled])': {
+ '&:focus': {
+ borderColor: buttonColors.focusBorder,
+ ...focusRing(),
+ },
+ '&:active': {
+ borderColor: buttonColors.activeBorder,
+ ...focusRing(),
+ },
+ },
+ ...mouseFocusBehavior({
+ '&:focus': {
+ ...baseStyles,
+ outline: 'none',
+ boxShadow: 'none',
+ animation: 'none',
+ ...hoverStyles,
+ ...activeStyles,
+ },
+ }),
+ };
+ }
+);
+
+const DeprecatedButton: ButtonOrAnchorComponent<
+ DeprecatedButtonProps,
+ typeof DeprecatedButtonVariant
+> = ({
+ variant = DeprecatedButtonVariant.Secondary,
+ size = 'large',
+ buttonRef,
+ children,
+ ...elemProps
+}: DeprecatedButtonProps) => {
+ React.useEffect(() => {
+ console.warn('This component is now deprecated, consider using the new Button component');
+ }, []);
+
+ return (
+
+ {children}
+
+ );
+};
+
+DeprecatedButton.Variant = DeprecatedButtonVariant;
+DeprecatedButton.Size = ButtonSize;
+
+export default DeprecatedButton;
diff --git a/modules/button/react/lib/parts/ButtonContainer.tsx b/modules/button/react/lib/parts/ButtonContainer.tsx
new file mode 100644
index 0000000000..57975e1944
--- /dev/null
+++ b/modules/button/react/lib/parts/ButtonContainer.tsx
@@ -0,0 +1,228 @@
+import * as React from 'react';
+import isPropValid from '@emotion/is-prop-valid';
+import {CSSObject} from '@emotion/core';
+import {type} from '@workday/canvas-kit-labs-react-core';
+import {borderRadius, spacing, spacingNumbers} from '@workday/canvas-kit-react-core';
+import {
+ GrowthBehavior,
+ mouseFocusBehavior,
+ focusRing,
+ styled,
+ EmotionCanvasTheme,
+} from '@workday/canvas-kit-react-common';
+import {ButtonColors} from '../types';
+import {buttonLabelDataClassName} from './ButtonLabelData';
+
+export interface ButtonContainerProps
+ extends React.ButtonHTMLAttributes,
+ GrowthBehavior {
+ colors?: ButtonColors;
+ /**
+ * The size of the Button.
+ * @default 'medium'
+ */
+ size?: 'small' | 'medium' | 'large';
+ /**
+ * The ref to the button that the styled component renders.
+ */
+ ref?: React.Ref;
+ /**
+ * Whether the icon should received filled (colored background layer) or regular styles.
+ * Corresponds to `toggled` in IconButton
+ */
+ fillIcon?: boolean;
+ /**
+ * Any extra styles necessary for a given parent component.
+ * This avoids using the inline `style` attribute when the shape needs to be customized (e.g. for IconButton)
+ */
+ extraStyles?: CSSObject;
+}
+
+function getIconColorSelectors(
+ {
+ canvas: {
+ palette: {primary: themePrimary},
+ },
+ }: EmotionCanvasTheme,
+ color: string,
+ fill?: boolean
+): CSSObject {
+ return {
+ '&:focus span, &:hover span, & span': {
+ '.wd-icon-fill': {
+ fill: color,
+ },
+ '.wd-icon-background': {
+ fill: fill ? color : undefined,
+ },
+ '.wd-icon-accent, .wd-icon-accent2': {
+ fill: fill
+ ? color === themePrimary.contrast
+ ? themePrimary.main
+ : themePrimary.contrast
+ : color,
+ },
+ },
+ };
+}
+
+export const ButtonContainer = styled('button', {
+ shouldForwardProp: prop => isPropValid(prop) && prop !== 'size',
+})(
+ {
+ ...type.body2,
+ ...type.variant.button,
+ lineHeight: 'normal',
+ boxSizing: 'border-box',
+ display: 'inline-flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ background: 'none',
+ borderRadius: borderRadius.circle,
+ boxShadow: 'none',
+ position: 'relative',
+ cursor: 'pointer',
+ outline: 'none',
+ verticalAlign: 'middle',
+ border: '2px solid transparent',
+ overflow: 'hidden',
+ whiteSpace: 'nowrap',
+ WebkitFontSmoothing: 'antialiased',
+ MozOsxFontSmoothing: 'grayscale',
+ transition:
+ 'box-shadow 120ms linear, border 120ms linear, background-color 120ms linear, color 120ms linear',
+ '&:hover:active': {transitionDuration: '40ms'}, // Makes the "down" state of the button happens faster than the hover state, so it animates in correctly.
+ '&:disabled, &:disabled:active': {cursor: 'default', boxShadow: 'none'},
+ '& > *:first-of-type': {
+ marginLeft: 0,
+ },
+ '& > *:last-of-type': {
+ marginRight: 0,
+ },
+ },
+ ({size}) => {
+ switch (size) {
+ case 'large':
+ return {
+ fontSize: type.body.fontSize,
+ minWidth: '112px',
+ height: '48px',
+ padding: `0 ${spacing.l}`,
+ '& > * ': {
+ margin: `0 ${spacingNumbers.xs / 2}px`,
+ },
+ };
+ case 'medium':
+ default:
+ return {
+ minWidth: '96px',
+ height: spacing.xl,
+ padding: `0 ${spacing.m}`,
+ '& > * ': {
+ margin: `0 ${spacingNumbers.xxs / 2}px`,
+ },
+ };
+ case 'small':
+ return {
+ minWidth: '80px',
+ height: spacing.l,
+ padding: `0 ${spacing.s}`,
+ '& > * ': {
+ margin: `0 ${spacingNumbers.xxxs / 2}px`,
+ },
+ };
+ }
+ },
+ ({grow}) => grow && {width: '100%', maxWidth: '100%'},
+ ({colors, fillIcon, theme}) => {
+ if (!colors) {
+ return;
+ }
+ const baseStyles = {
+ backgroundColor: colors.default.background,
+ borderColor: colors.default.border,
+ color: colors.default.label,
+ ...(colors.default.icon && {
+ '.wd-icon-fill, .wd-icon-accent, .wd-icon-accent2, .wd-icon-background': {
+ transition: 'fill 120ms ease-in',
+ },
+ ...getIconColorSelectors(theme, colors.default.icon, fillIcon),
+ }),
+ ...(colors.default.labelData && {
+ ['.' + buttonLabelDataClassName]: {
+ color: colors.default.labelData,
+ },
+ }),
+ };
+
+ const hoverStyles = {
+ '&:hover': {
+ backgroundColor: colors.hover.background,
+ borderColor: colors.hover.border,
+ color: colors.hover.label,
+ ...(colors.hover.labelData && {
+ ['.' + buttonLabelDataClassName]: {
+ transition: 'color 120ms ease-in',
+ color: colors.hover.labelData,
+ },
+ }),
+ ...(colors.hover.icon && getIconColorSelectors(theme, colors.hover.icon, fillIcon)),
+ },
+ };
+
+ const activeStyles = {
+ '&:active, &:focus:active, &:hover:active': {
+ backgroundColor: colors.active.background,
+ borderColor: colors.active.border,
+ color: colors.active.label,
+ ...(colors.active.labelData && {
+ ['.' + buttonLabelDataClassName]: {
+ color: colors.active.labelData,
+ },
+ }),
+ ...(colors.active.icon && getIconColorSelectors(theme, colors.active.icon, fillIcon)),
+ },
+ };
+
+ return {
+ ...baseStyles,
+ '&:focus': {
+ backgroundColor: colors.focus.background,
+ borderColor: colors.focus.border,
+ color: colors.focus.label,
+ ...(colors.focus.focusRing || focusRing({separation: 2}, theme)),
+ ...(colors.focus.labelData && {
+ ['.' + buttonLabelDataClassName]: {
+ color: colors.focus.labelData,
+ },
+ }),
+ ...(colors.focus.icon && getIconColorSelectors(theme, colors.focus.icon, fillIcon)),
+ },
+
+ ...activeStyles,
+ ...hoverStyles,
+ '&:disabled, &:active:disabled, &:focus:disabled, &:hover:disabled': {
+ backgroundColor: colors.disabled.background,
+ borderColor: colors.disabled.border,
+ color: colors.disabled.label,
+ ...(colors.disabled.icon && getIconColorSelectors(theme, colors.disabled.icon, fillIcon)),
+ ...(colors.disabled.labelData && {
+ ['.' + buttonLabelDataClassName]: {
+ color: colors.disabled.labelData,
+ },
+ }),
+ },
+ ...mouseFocusBehavior({
+ '&:focus': {
+ ...baseStyles,
+ outline: 'none',
+ boxShadow: 'none',
+ animation: 'none',
+ ...hoverStyles,
+ ...activeStyles,
+ },
+ }),
+ };
+ },
+ ({extraStyles}) => extraStyles
+);
diff --git a/modules/button/react/lib/parts/ButtonLabel.tsx b/modules/button/react/lib/parts/ButtonLabel.tsx
new file mode 100644
index 0000000000..add529e88d
--- /dev/null
+++ b/modules/button/react/lib/parts/ButtonLabel.tsx
@@ -0,0 +1,8 @@
+import {styled} from '@workday/canvas-kit-react-common';
+
+export const ButtonLabel = styled('span')({
+ position: 'relative', // Fixes an IE issue with text within button moving on click
+ textOverflow: 'ellipsis',
+ overflow: 'hidden',
+ whiteSpace: 'nowrap',
+});
diff --git a/modules/button/react/lib/parts/ButtonLabelData.tsx b/modules/button/react/lib/parts/ButtonLabelData.tsx
new file mode 100644
index 0000000000..5c5a826c15
--- /dev/null
+++ b/modules/button/react/lib/parts/ButtonLabelData.tsx
@@ -0,0 +1,16 @@
+import * as React from 'react';
+import {styled} from '@workday/canvas-kit-react-common';
+
+export const buttonLabelDataClassName = 'button-label-data';
+
+const Container = styled('span')({
+ position: 'relative', // Fixes an IE issue with text within button moving on click
+ textOverflow: 'ellipsis',
+ overflow: 'hidden',
+ whiteSpace: 'nowrap',
+ fontWeight: 400,
+});
+
+export const ButtonLabelData = (props: any) => (
+
+);
diff --git a/modules/button/react/lib/parts/ButtonLabelIcon.tsx b/modules/button/react/lib/parts/ButtonLabelIcon.tsx
new file mode 100644
index 0000000000..11fe470f60
--- /dev/null
+++ b/modules/button/react/lib/parts/ButtonLabelIcon.tsx
@@ -0,0 +1,63 @@
+import * as React from 'react';
+import {spacing} from '@workday/canvas-kit-react-core';
+import isPropValid from '@emotion/is-prop-valid';
+import {ButtonIconPosition} from '../types';
+import {ButtonProps} from '../Button';
+import {SystemIcon} from '@workday/canvas-kit-react-icon';
+import {styled} from '@workday/canvas-kit-react-common';
+
+export interface ButtonLabelIconProps extends Pick {
+ dropdown?: boolean;
+ iconPosition?: ButtonIconPosition;
+}
+
+const ICON_SIZE = 24;
+const SMALL_ICON_SIZE = 20;
+
+const ButtonLabelIconStyled = styled('span', {
+ shouldForwardProp: prop => isPropValid(prop) && prop !== 'size',
+})(
+ {
+ display: 'inline-block',
+ },
+ ({size}) => ({
+ width: size === 'small' ? SMALL_ICON_SIZE : ICON_SIZE,
+ height: size === 'small' ? SMALL_ICON_SIZE : ICON_SIZE,
+ }),
+ ({iconPosition, dropdown}) => ({
+ marginLeft:
+ iconPosition === ButtonIconPosition.Right
+ ? undefined
+ : `-${dropdown ? spacing.xxs : spacing.xxxs} !important`,
+ marginRight:
+ iconPosition === ButtonIconPosition.Right
+ ? `-${dropdown ? spacing.xxs : spacing.xxxs} !important`
+ : undefined,
+ })
+);
+
+export const ButtonLabelIcon = ({
+ icon,
+ size,
+ dropdown,
+ iconPosition,
+ ...elemProps
+}: ButtonLabelIconProps) => {
+ /* istanbul ignore next line for coverage */
+ if (icon === undefined) {
+ return null;
+ }
+
+ const iconSize = size === 'small' ? SMALL_ICON_SIZE : undefined;
+
+ return (
+
+