diff --git a/cypress/integration/Button.spec.ts b/cypress/integration/Button.spec.ts index 2dae24ec00..d33179542d 100644 --- a/cypress/integration/Button.spec.ts +++ b/cypress/integration/Button.spec.ts @@ -7,7 +7,7 @@ describe('Button', () => { context('given primary buttons are rendered', () => { beforeEach(() => { - h.stories.load('Components|Buttons/Button/React', 'Primary'); + h.stories.load('Components|Buttons/Button/React/Standard', 'Primary'); }); it('should not have any axe errors', () => { diff --git a/eslintrc.js b/eslintrc.js index ac148570bc..4d5079b04f 100644 --- a/eslintrc.js +++ b/eslintrc.js @@ -75,7 +75,7 @@ module.exports = { 'no-param-reassign': 'error', 'no-undef-init': 'error', 'no-unused-labels': 'error', - 'no-use-before-define': 'warn', // Decide on this + 'no-use-before-define': ['warn', {functions: false, classes: true}], 'no-var': 'error', 'prefer-const': 'error', 'quote-props': 'off', diff --git a/modules/_labs/pagination/react/lib/Pages.tsx b/modules/_labs/pagination/react/lib/Pages.tsx index 377c3e110d..6dae2fdd56 100644 --- a/modules/_labs/pagination/react/lib/Pages.tsx +++ b/modules/_labs/pagination/react/lib/Pages.tsx @@ -19,17 +19,21 @@ const noPointerEvents = css({ pointerEvents: 'none', }); +const autoWidth = css({ + width: 'auto', +}); + const ellipsisStyle = css(noPointerEvents, { width: canvas.spacing.l, textAlign: 'center', display: 'inline-block', }); -const noTransitions = css({ +const noTransitions = css(autoWidth, { '&:not(:hover)': {transition: 'none !important'}, }); -const activeStyling = css(noPointerEvents, noTransitions, { +const activeStyling = css(noPointerEvents, noTransitions, autoWidth, { color: canvas.colors.frenchVanilla100, }); diff --git a/modules/button/react/README.md b/modules/button/react/README.md index e43fe5fcd4..3d6b36efaf 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,19 @@ able to compile your code. --- -## Button +## Table of Contents + +- [Button](#button) +- [DeleteButton](#deletebutton) +- [DropdownButton](#dropdownbutton) +- [HighlightButton](#highlightbutton) +- [OutlineButton](#outlinebutton) +- [TextButton](#textbutton) +- [Icon Button](#icon-button) + +--- + +# Button ```tsx import * as React from 'react'; @@ -62,7 +72,7 @@ import {Button} from '@workday/canvas-kit-react-button'; ## Static Properties -#### `Sizes: ButtonSize` +#### `Size: 'small' | 'medium' | 'large'` ```tsx Small Button @@ -70,7 +80,7 @@ import {Button} from '@workday/canvas-kit-react-button'; --- -#### `Types: ButtonVariant` +#### `Variant: ButtonVariant` ```tsx Primary Button @@ -96,21 +106,220 @@ 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: `'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. + +--- + +# 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` + +--- + +# 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. + +--- + +# 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: `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 | +| `medium` | 32px tall, medium padding, medium text | +| `large` | 48px tall, large padding, larger text | --- @@ -126,6 +335,195 @@ Default: `false` > Returns the ref to the rendered HTMLButtonElement. +--- + +### `icon: CanvasSystemIcon` + +> The icon of the button + +--- + +# 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. + +--- + +# 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. + +--- + # Icon Button > Button containing an icon. Icon may be a component from @@ -139,16 +537,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 +550,7 @@ import {activityStreamIcon} from '@workday/canvas-system-icons-web'; --- -#### `Types: IconButtonVariant` +#### `Variant: IconButtonVariant` ```tsx @@ -196,18 +590,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,68 +623,19 @@ Default: `undefined` --- -#### `value: string` - -> Value of the button. Must be unique if used within an IconButtonToggleGroup. - -## Accessibility Notes - -> 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. - -# Icon Button Toggle Group - -> Group of buttons containing an icon. This is a -> [_controlled_](https://reactjs.org/docs/forms.html#controlled-components) component. - -## 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'; - - - - -; -``` - -**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 - -## Component Props - -### Required - -#### `children: React.ReactElement[]` - -> Icon buttons to toggle between. - ---- - -### Optional - -#### `value: string | number` +#### `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 IconButton can also wrap a SystemIcon component. --- -#### `onChange: (value:string | number)=> void` +## Accessibility Notes -> Callback function when a toggle button is selected. The value (if defined) or the index of the -> button will be returned. +> 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. diff --git a/modules/button/react/index.ts b/modules/button/react/index.ts index 132e7b7f30..d5d3252c25 100644 --- a/modules/button/react/index.ts +++ b/modules/button/react/index.ts @@ -1,17 +1,10 @@ -import Button from './lib/Button'; +export {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 IconButtonToggleGroup, - IconButtonToggleGroupProps, -} from './lib/IconButtonToggleGroup'; export * from './lib/types'; diff --git a/modules/button/react/lib/Button.tsx b/modules/button/react/lib/Button.tsx index efa4c52a84..8d372630e4 100644 --- a/modules/button/react/lib/Button.tsx +++ b/modules/button/react/lib/Button.tsx @@ -1,101 +1,109 @@ -/** @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 {CanvasSystemIcon} from '@workday/design-assets-types'; +import {colors} from '@workday/canvas-kit-react-core'; import {GrowthBehavior} from '@workday/canvas-kit-react-common'; -import {labelDataBaseStyles} from './ButtonStyles'; +import {CanvasSystemIcon} from '@workday/design-assets-types'; +import {ButtonVariant, ButtonColors, DropdownButtonVariant, ButtonSize} from './types'; +import {ButtonContainer, ButtonLabel, ButtonLabelData, ButtonLabelIcon} from './parts'; -export interface BaseButtonProps - extends React.ButtonHTMLAttributes { +export interface ButtonProps extends React.ButtonHTMLAttributes, 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). - */ - children: React.ReactNode; -} - -export default class Button extends React.Component { - public static Variant = ButtonVariant; - public static Size = ButtonSize; +const Button = ({ + 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): ButtonColors => { + switch (variant) { + case ButtonVariant.Primary: + case DropdownButtonVariant.Primary: + return { + default: { + background: colors.blueberry400, + icon: colors.frenchVanilla100, + label: colors.frenchVanilla100, + }, + hover: { + background: colors.blueberry500, + }, + active: { + background: colors.blueberry600, + }, + focus: { + background: colors.blueberry400, + }, + disabled: { + background: colors.blueberry200, + }, + }; + 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..96d1dbcc85 --- /dev/null +++ b/modules/button/react/lib/DeleteButton.tsx @@ -0,0 +1,48 @@ +import * as React from 'react'; +import {colors} from '@workday/canvas-kit-react-core'; +import {ButtonColors, ButtonSize} from './types'; +import {ButtonContainer, ButtonLabel} from './parts'; +import {GrowthBehavior} from '@workday/canvas-kit-react-common'; + +export interface DeleteButtonProps + extends React.ButtonHTMLAttributes, + 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; +} + +const getDeleteButtonColors = (): ButtonColors => ({ + default: { + background: colors.cinnamon500, + label: colors.frenchVanilla100, + }, + hover: { + background: colors.cinnamon600, + }, + active: { + background: '#80160E', + }, + focus: { + background: colors.cinnamon500, + }, + disabled: { + background: colors.cinnamon200, + }, +}); + +const DeleteButton = ({size = 'medium', buttonRef, children, ...elemProps}: DeleteButtonProps) => ( + + {children} + +); + +DeleteButton.Size = ButtonSize; + +export default DeleteButton; diff --git a/modules/button/react/lib/DropdownButton.tsx b/modules/button/react/lib/DropdownButton.tsx index 4c634db73e..224e215499 100644 --- a/modules/button/react/lib/DropdownButton.tsx +++ b/modules/button/react/lib/DropdownButton.tsx @@ -1,40 +1,51 @@ 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} from '@workday/canvas-kit-react-common'; +import {DropdownButtonVariant, ButtonIconPosition} 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, + 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; +} - static defaultProps = { - variant: ButtonVariant.Primary, - size: ButtonSize.Medium, - }; +const DropdownButton = ({ + 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..b58122eff0 --- /dev/null +++ b/modules/button/react/lib/HighlightButton.tsx @@ -0,0 +1,77 @@ +import * as React from 'react'; +import {colors} from '@workday/canvas-kit-react-core'; +import {GrowthBehavior} from '@workday/canvas-kit-react-common'; +import {CanvasSystemIcon} from '@workday/design-assets-types'; +import {ButtonColors} from './types'; +import {ButtonContainer, ButtonLabel, ButtonLabelIcon} from './parts'; + +export interface HighlightButtonProps + extends React.ButtonHTMLAttributes, + 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; +} + +const getHighlightButtonColors = (): ButtonColors => ({ + default: { + background: colors.soap200, + border: colors.soap200, + icon: colors.blueberry500, + label: colors.blueberry500, + }, + hover: { + background: colors.soap400, + border: 'transparent', + icon: colors.blueberry500, + label: colors.blueberry500, + }, + active: { + background: colors.soap500, + border: 'transparent', + icon: colors.blueberry500, + label: colors.blueberry500, + }, + focus: { + background: colors.soap200, + border: 'transparent', + icon: colors.blueberry500, + label: colors.blueberry500, + }, + disabled: { + background: colors.soap100, + border: 'transparent', + icon: colors.soap600, + label: colors.licorice100, + }, +}); + +const HighlightButton = ({ + size = 'medium', + buttonRef, + icon, + children, + ...elemProps +}: HighlightButtonProps) => ( + + {icon && } + {children} + +); + +HighlightButton.Size = { + Medium: 'medium', + Large: 'large', +} as const; + +export default HighlightButton; diff --git a/modules/button/react/lib/IconButton.tsx b/modules/button/react/lib/IconButton.tsx index aa300ffd88..7f0e5e4287 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} 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} from './types'; +import {ButtonContainer} from './parts'; export interface IconButtonProps extends React.ButtonHTMLAttributes { - /** - * The type of the IconButton. - * @default IconButtonVariant.Circle - */ - variant: IconButtonVariant; /** * 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; /** @@ -44,227 +40,187 @@ export interface IconButtonProps extends React.ButtonHTMLAttributes void; } -function getFillSelector(fillColor: string): CSSObject { - return { - '&:focus span .wd-icon-fill, &:hover span .wd-icon-fill, span .wd-icon-fill': { - fill: fillColor, - }, - }; -} - -function getBackgroundSelector(fillColor: string): CSSObject { - return { - '&:hover span .wd-icon-background, span .wd-icon-background': { - fill: fillColor, - }, - }; -} +const IconButton = ({ + variant = IconButtonVariant.Circle, + size = 'medium', + buttonRef, + onToggleChange, + icon, + toggled, + children, + ...elemProps +}: IconButtonProps) => { + const isInitialMount = React.useRef(true); -function getAccentSelector(fillColor: string): CSSObject { - return { - '&:focus span .wd-icon-accent, &:hover span .wd-icon-accent, span .wd-icon-accent': { - fill: fillColor, - }, - }; -} - -export const iconButtonIdentifier = 'wdc-ckr-icon-button'; - -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`, - }, - }), - }; + // 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); } - - 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), - }, - }; - - 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`, - }, - }), - }; } - } -); + }, [toggled, onToggleChange]); -export default class IconButton extends React.Component { - public static Variant = IconButtonVariant; - public static Size = IconButtonSize; + 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, + borderRadius: + variant === IconButtonVariant.Square || variant === IconButtonVariant.SquareFilled + ? borderRadius.m + : borderRadius.circle, + ['& .wd-icon']: { + display: 'inline-block', + verticalAlign: 'middle', + width: size === 'small' ? '20px' : undefined, + height: size === 'small' ? '20px' : undefined, + }, + }; - static defaultProps = { - variant: IconButtonVariant.Circle, - size: IconButtonSize.Medium, - } as const; + return ( + + {icon ? : children} + + ); +}; - componentDidUpdate(prevProps: IconButtonProps) { - if ( - prevProps.toggled !== this.props.toggled && - typeof this.props.onToggleChange === 'function' - ) { - this.props.onToggleChange(this.props.toggled); - } - } +IconButton.Variant = IconButtonVariant; +IconButton.Size = { + Small: 'small', + Medium: 'medium', +} as const; - public render() { - // onToggleChange will generate a warning if spread over a - const { - buttonRef, - size, - variant, - onToggleChange, - icon, - toggled, - children, - className, - ...elemProps - } = this.props; +export default IconButton; - return ( - - {({cx}) => ( - - {icon ? : children} - - )} - - ); +const getIconButtonColors = (variant: IconButtonVariant, toggled?: boolean): ButtonColors => { + switch (variant) { + case IconButton.Variant.Square: + case IconButtonVariant.Circle: + default: + return { + default: { + background: toggled ? colors.blueberry400 : undefined, + icon: toggled ? colors.frenchVanilla100 : colors.licorice200, + }, + hover: { + background: toggled ? colors.blueberry500 : colors.soap300, + icon: toggled ? colors.frenchVanilla100 : colors.licorice500, + }, + active: { + background: toggled ? colors.blueberry500 : colors.soap500, + icon: toggled ? colors.frenchVanilla100 : colors.licorice500, + }, + focus: { + background: toggled ? colors.blueberry500 : colors.soap300, + icon: toggled ? colors.frenchVanilla100 : colors.licorice500, + }, + disabled: { + background: toggled ? colors.blueberry100 : 'transparent', + icon: toggled ? colors.blueberry300 : colors.soap600, + }, + }; + case IconButtonVariant.SquareFilled: + case IconButtonVariant.CircleFilled: + return { + default: { + background: toggled ? colors.blueberry400 : colors.soap200, + icon: toggled ? colors.frenchVanilla100 : colors.licorice200, + }, + hover: { + background: toggled ? colors.blueberry500 : colors.soap400, + icon: toggled ? colors.frenchVanilla100 : colors.licorice500, + }, + active: { + background: toggled ? colors.blueberry500 : colors.soap500, + icon: toggled ? colors.frenchVanilla100 : colors.licorice500, + }, + focus: { + background: toggled ? colors.blueberry500 : colors.soap400, + icon: toggled ? colors.frenchVanilla100 : colors.licorice500, + }, + disabled: { + background: toggled ? colors.blueberry100 : colors.soap100, + icon: toggled ? colors.blueberry300 : colors.soap600, + }, + }; + case IconButtonVariant.Plain: + return { + default: { + icon: toggled ? colors.blueberry400 : colors.licorice200, + }, + hover: { + icon: toggled ? colors.blueberry400 : colors.licorice500, + }, + active: { + icon: toggled ? colors.blueberry400 : colors.licorice500, + }, + focus: { + icon: toggled ? colors.blueberry400 : colors.licorice500, + focusRing: focusRing(2, 0), + }, + disabled: { + icon: toggled ? colors.blueberry200 : colors.soap600, + }, + }; + case IconButtonVariant.Inverse: + return { + default: { + background: toggled ? colors.frenchVanilla100 : undefined, + icon: toggled ? colors.blueberry400 : colors.frenchVanilla100, + }, + hover: { + background: toggled ? colors.frenchVanilla100 : 'rgba(0, 0, 0, 0.2)', + icon: toggled ? colors.blueberry400 : colors.frenchVanilla100, + }, + active: { + background: toggled ? colors.frenchVanilla100 : 'rgba(0, 0, 0, 0.3)', + icon: toggled ? colors.blueberry400 : colors.frenchVanilla100, + }, + focus: { + background: toggled ? colors.frenchVanilla100 : 'rgba(0, 0, 0, 0.2)', + icon: toggled ? colors.blueberry400 : colors.frenchVanilla100, + focusRing: focusRing(2, 2, true, false, 'currentColor', colors.frenchVanilla100), + }, + disabled: { + background: toggled ? 'rgba(255,255,255,0.75)' : 'transparent', + icon: toggled ? colors.blueberry400 : 'rgba(255, 255, 255, 0.75)', + }, + }; + case IconButtonVariant.InverseFilled: + return { + default: { + background: toggled ? colors.frenchVanilla100 : 'rgba(0, 0, 0, 0.2)', + icon: toggled ? colors.blueberry400 : colors.frenchVanilla100, + }, + hover: { + background: toggled ? colors.frenchVanilla100 : 'rgba(0, 0, 0, 0.3)', + icon: toggled ? colors.blueberry400 : colors.frenchVanilla100, + }, + active: { + background: toggled ? colors.frenchVanilla100 : 'rgba(0, 0, 0, 0.4)', + icon: toggled ? colors.blueberry400 : colors.frenchVanilla100, + }, + focus: { + background: toggled ? colors.frenchVanilla100 : 'rgba(0, 0, 0, 0.2)', + icon: toggled ? colors.blueberry400 : colors.frenchVanilla100, + focusRing: focusRing(2, 2, true, false, 'currentColor', colors.frenchVanilla100), + }, + disabled: { + background: toggled ? 'rgba(255,255,255,0.75)' : 'rgba(0, 0, 0, 0.2)', + icon: toggled ? colors.blueberry400 : '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..f14f27b3f9 --- /dev/null +++ b/modules/button/react/lib/OutlineButton.tsx @@ -0,0 +1,164 @@ +import * as React from 'react'; +import {colors} from '@workday/canvas-kit-react-core'; +import {focusRing, GrowthBehavior} from '@workday/canvas-kit-react-common'; +import {CanvasSystemIcon} from '@workday/design-assets-types'; +import {OutlineButtonVariant, ButtonColors, ButtonSize} from './types'; +import {ButtonContainer, ButtonLabel, ButtonLabelData, ButtonLabelIcon} from './parts'; + +export interface OutlineButtonProps + extends React.ButtonHTMLAttributes, + 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; +} + +const OutlineButton = ({ + 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): ButtonColors => { + switch (variant) { + case OutlineButtonVariant.Primary: + return { + default: { + border: colors.blueberry400, + icon: colors.blueberry400, + label: colors.blueberry400, + }, + hover: { + background: colors.blueberry400, + icon: colors.frenchVanilla100, + label: colors.frenchVanilla100, + }, + active: { + background: colors.blueberry500, + border: colors.blueberry500, + icon: colors.frenchVanilla100, + label: colors.frenchVanilla100, + }, + focus: { + background: colors.blueberry400, + icon: colors.frenchVanilla100, + label: colors.frenchVanilla100, + }, + disabled: { + background: colors.frenchVanilla100, + border: colors.soap500, + icon: colors.soap600, + label: colors.licorice100, + }, + }; + case OutlineButtonVariant.Secondary: + default: + return { + default: { + border: colors.soap500, + icon: colors.licorice200, + label: colors.blackPepper400, + }, + hover: { + background: colors.licorice500, + border: colors.licorice500, + icon: colors.frenchVanilla100, + label: colors.frenchVanilla100, + }, + active: { + background: colors.licorice600, + border: colors.licorice600, + icon: colors.frenchVanilla100, + label: colors.frenchVanilla100, + }, + focus: { + background: colors.licorice500, + border: colors.licorice500, + icon: colors.frenchVanilla100, + label: colors.frenchVanilla100, + }, + disabled: { + background: colors.frenchVanilla100, + border: colors.soap500, + icon: colors.soap600, + label: colors.licorice100, + }, + }; + case OutlineButtonVariant.Inverse: + return { + default: { + 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(2, 2, true, false, 'currentColor', colors.frenchVanilla100), + }, + 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..4e601d791d 100644 --- a/modules/button/react/lib/TextButton.tsx +++ b/modules/button/react/lib/TextButton.tsx @@ -1,74 +1,143 @@ 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} 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} from './types'; +import {ButtonContainer, ButtonLabelIcon, ButtonLabel} from './parts'; -export interface TextButtonProps extends BaseButtonProps { +export interface TextButtonProps extends React.ButtonHTMLAttributes { + /** + * 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. + */ + icon?: CanvasSystemIcon; + /** + * The capitialization of the text in the button. */ - iconPosition?: IconPosition; + allCaps?: boolean; } -const TextButtonCon = styled('button', { - shouldForwardProp: prop => isPropValid(prop) && prop !== 'size', -})( - textButtonStyles.styles, - ({variant}) => getButtonStyle(textButtonStyles, variant), - ({size}) => { - const {sizes} = textButtonStyles.variants!; - - switch (size) { - case ButtonSize.Large: - default: - return sizes.large; - case ButtonSize.Medium: - case ButtonSize.Small: - return sizes.small; - } +const getTextButtonColors = (variant: TextButtonVariant): ButtonColors => { + switch (variant) { + case TextButtonVariant.Default: + default: + return { + default: { + icon: colors.blueberry400, + label: colors.blueberry400, + }, + hover: { + icon: colors.blueberry500, + label: colors.blueberry500, + }, + active: { + icon: colors.blueberry500, + label: colors.blueberry500, + }, + focus: { + icon: colors.blueberry500, + label: colors.blueberry500, + focusRing: focusRing(2, 0), + }, + disabled: { + icon: 'rgba(8, 117, 225, 0.5)', + label: 'rgba(8, 117, 225, 0.5)', + }, + }; + case TextButtonVariant.Inverse: + return { + default: { + icon: colors.frenchVanilla100, + label: colors.frenchVanilla100, + }, + hover: {}, + active: {}, + focus: { + focusRing: focusRing(2, 0, true, false, undefined, 'currentColor'), + }, + disabled: { + 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', + '&:hover:not([disabled])': {textDecoration: 'underline'}, +}; - static defaultProps = { - iconPosition: IconPosition.Left, - variant: TextButtonVariant.Default, - size: ButtonSize.Large, - }; +const TextButton = ({ + 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/deprecated_Button.tsx b/modules/button/react/lib/deprecated_Button.tsx new file mode 100644 index 0000000000..a0303b560d --- /dev/null +++ b/modules/button/react/lib/deprecated_Button.tsx @@ -0,0 +1,181 @@ +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} 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; +} + +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(2, 0), + }, + '&:active': { + borderColor: buttonColors.activeBorder, + ...focusRing(2, 0), + }, + }, + ...mouseFocusBehavior({ + '&:focus': { + ...baseStyles, + outline: 'none', + boxShadow: 'none', + animation: 'none', + ...hoverStyles, + ...activeStyles, + }, + }), + }; + } +); + +const DeprecatedButton = ({ + 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..a7d5cc616d --- /dev/null +++ b/modules/button/react/lib/parts/ButtonContainer.tsx @@ -0,0 +1,214 @@ +import * as React from 'react'; +import isPropValid from '@emotion/is-prop-valid'; +import {CSSObject} from '@emotion/core'; +import {styled, type} from '@workday/canvas-kit-labs-react-core'; +import canvas, {borderRadius, spacing, spacingNumbers} from '@workday/canvas-kit-react-core'; +import {GrowthBehavior, focusRing, mouseFocusBehavior} 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(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 === canvas.colors.frenchVanilla100 + ? canvas.colors.blueberry400 + : canvas.colors.frenchVanilla100 + : 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': { + paddingLeft: 0, + }, + '& > *:last-of-type': { + paddingRight: 0, + }, + }, + ({size}) => { + switch (size) { + case 'large': + return { + fontSize: type.body.fontSize, + minWidth: '112px', + height: '48px', + padding: `0 ${spacing.l}`, + '& > * ': { + padding: `0 ${spacingNumbers.xs / 2}px`, + }, + }; + case 'medium': + default: + return { + minWidth: '96px', + height: spacing.xl, + padding: `0 ${spacing.m}`, + '& > * ': { + padding: `0 ${spacingNumbers.xxs / 2}px`, + }, + }; + case 'small': + return { + minWidth: '80px', + height: spacing.l, + padding: `0 ${spacing.s}`, + '& > * ': { + padding: `0 ${spacingNumbers.xxxs / 2}px`, + }, + }; + } + }, + ({grow}) => grow && {width: '100%', maxWidth: '100%'}, + ({colors, fillIcon}) => { + 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(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(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(colors.active.icon, fillIcon)), + }, + }; + + return { + ...baseStyles, + '&:focus': { + backgroundColor: colors.focus.background, + borderColor: colors.focus.border, + color: colors.focus.label, + ...(colors.focus.focusRing || focusRing(2, 2)), + ...(colors.focus.labelData && { + ['.' + buttonLabelDataClassName]: { + color: colors.focus.labelData, + }, + }), + ...(colors.focus.icon && getIconColorSelectors(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(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..b81158fa0c --- /dev/null +++ b/modules/button/react/lib/parts/ButtonLabel.tsx @@ -0,0 +1,8 @@ +import {styled} from '@workday/canvas-kit-labs-react-core'; + +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..6166741e31 --- /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-labs-react-core'; + +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..45dd690c20 --- /dev/null +++ b/modules/button/react/lib/parts/ButtonLabelIcon.tsx @@ -0,0 +1,63 @@ +import * as React from 'react'; +import {styled} from '@workday/canvas-kit-labs-react-core'; +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'; + +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}`, + marginRight: + iconPosition === ButtonIconPosition.Right + ? `-${dropdown ? spacing.xxs : spacing.xxxs} ` + : 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 ( + + + + ); +}; diff --git a/modules/button/react/lib/parts/index.tsx b/modules/button/react/lib/parts/index.tsx new file mode 100644 index 0000000000..3f897e02a0 --- /dev/null +++ b/modules/button/react/lib/parts/index.tsx @@ -0,0 +1,4 @@ +export {ButtonContainer} from './ButtonContainer'; +export {ButtonLabel} from './ButtonLabel'; +export {ButtonLabelData} from './ButtonLabelData'; +export {ButtonLabelIcon} from './ButtonLabelIcon'; diff --git a/modules/button/react/lib/types.ts b/modules/button/react/lib/types.ts index 823783d03b..280d761b80 100644 --- a/modules/button/react/lib/types.ts +++ b/modules/button/react/lib/types.ts @@ -1,41 +1,42 @@ +import {CSSObject} from '@emotion/core'; + /** - * The different button sizes. + * Standard Button */ -export enum ButtonSize { - Small = 'small', - Medium = 'medium', - Large = 'large', +export enum ButtonVariant { + Primary = 'primary', + Secondary = 'secondary', } - -export enum IconButtonSize { - Small = 'small', - Medium = 'medium', +export enum ButtonIconPosition { + Left = 'iconPositionLeft', + Right = 'iconPositionRight', } +export const ButtonSize = { + Small: 'small', + Medium: 'medium', + Large: 'large', +} as const; -// TODO (beta button): consolidate all these button types /** - * The different button types. + * Outline Button */ +export enum OutlineButtonVariant { + Primary = 'outlinePrimary', + Secondary = 'outlineSecondary', + Inverse = 'outlineInverse', +} /** - * @deprecated These type are deprecated along with deprecated_Button component + * Dropdown Button */ -export enum DeprecatedButtonVariant { - Primary = 'deprecatedPrimary', - Secondary = 'deprecatedSecondary', - Delete = 'deprecatedDelete', -} - -export enum ButtonVariant { - Primary = 'primary', - Secondary = 'betaSecondary', - Delete = 'delete', - Highlight = 'highlight', - OutlinePrimary = 'outlinePrimary', - OutlineSecondary = 'outlineSecondary', - OutlineInverse = 'outlineInverse', +export enum DropdownButtonVariant { + Primary = 'dropdownPrimary', + Secondary = 'dropdownSecondary', } +/** + * Icon Button + */ export enum IconButtonVariant { Square = 'square', SquareFilled = 'squareFilled', @@ -47,22 +48,47 @@ export enum IconButtonVariant { } /** - * The different icon positions. + * Text Button */ -export enum IconPosition { - Left = 'iconPositionLeft', - Right = 'iconPositionRight', -} - export enum TextButtonVariant { Default = 'text', Inverse = 'textInverse', - AllCaps = 'textAllCaps', - InverseAllCaps = 'textInverseAllCaps', +} + +/** + * Old orange buttons + * @deprecated These type are deprecated along with deprecated_Button component + */ +export enum DeprecatedButtonVariant { + Primary = 'deprecatedPrimary', + Secondary = 'deprecatedSecondary', + Delete = 'deprecatedDelete', } export type AllButtonVariants = - | DeprecatedButtonVariant | ButtonVariant + | DropdownButtonVariant | TextButtonVariant - | IconButtonVariant; + | IconButtonVariant + | DeprecatedButtonVariant; + +/** + * The object used for passing in colors to the ButtonContainer + */ +export interface ButtonStateColors { + background?: string; + border?: string; + icon?: string; + iconFill?: boolean; + label?: string; + labelData?: string; +} +export interface ButtonColors { + default: ButtonStateColors; + hover: ButtonStateColors; + active: ButtonStateColors; + focus: ButtonStateColors & { + focusRing?: CSSObject; + }; + disabled: ButtonStateColors; +} diff --git a/modules/button/react/lib/utils.ts b/modules/button/react/lib/utils.ts deleted file mode 100644 index f24a3ffd91..0000000000 --- a/modules/button/react/lib/utils.ts +++ /dev/null @@ -1,356 +0,0 @@ -import {CSSObject} from '@emotion/core'; -import {focusRing, mouseFocusBehavior} from '@workday/canvas-kit-react-common'; -import { - ButtonSize, - DeprecatedButtonVariant, - AllButtonVariants, - TextButtonVariant, - ButtonVariant, - IconButtonVariant, -} from './types'; -import * as ButtonStyles from './ButtonStyles'; -import {ButtonColors} from './ButtonColors'; - -export function getButtonSize(baseButton: ButtonStyles.ButtonGenericStyle, size?: ButtonSize) { - const {sizes} = baseButton.variants!; - - switch (size) { - case ButtonSize.Large: - return sizes.large; - case ButtonSize.Medium: - default: - return sizes.medium; - case ButtonSize.Small: - return sizes.small; - } -} - -export function getButtonStyle( - baseButton: ButtonStyles.ButtonGenericStyle, - variant?: AllButtonVariants -) { - const {types} = baseButton.variants!; - - switch (variant) { - case DeprecatedButtonVariant.Primary: - return types[DeprecatedButtonVariant.Primary]; - case DeprecatedButtonVariant.Secondary: - return types[DeprecatedButtonVariant.Secondary]; - case DeprecatedButtonVariant.Delete: - return types[DeprecatedButtonVariant.Delete]; - case ButtonVariant.Highlight: - return types[ButtonVariant.Highlight]; - case ButtonVariant.OutlinePrimary: - return types[ButtonVariant.OutlinePrimary]; - case ButtonVariant.OutlineSecondary: - return types[ButtonVariant.OutlineSecondary]; - case ButtonVariant.OutlineInverse: - return types[ButtonVariant.OutlineInverse]; - case ButtonVariant.Primary: - return types[ButtonVariant.Primary]; - case ButtonVariant.Secondary: - default: - return types[ButtonVariant.Secondary]; - case ButtonVariant.Delete: - return types[ButtonVariant.Delete]; - case TextButtonVariant.Default: - return types[TextButtonVariant.Default]; - case TextButtonVariant.Inverse: - return types[TextButtonVariant.Inverse]; - case TextButtonVariant.AllCaps: - return types[TextButtonVariant.AllCaps]; - case TextButtonVariant.InverseAllCaps: - return types[TextButtonVariant.InverseAllCaps]; - case IconButtonVariant.Square: - return types[IconButtonVariant.Square]; - case IconButtonVariant.SquareFilled: - return types[IconButtonVariant.SquareFilled]; - case IconButtonVariant.Plain: - return types[IconButtonVariant.Plain]; - case IconButtonVariant.Circle: - return types[IconButtonVariant.Circle]; - case IconButtonVariant.CircleFilled: - return types[IconButtonVariant.CircleFilled]; - case IconButtonVariant.Inverse: - return types[IconButtonVariant.Inverse]; - case IconButtonVariant.InverseFilled: - return types[IconButtonVariant.InverseFilled]; - } -} - -export function getBaseButton(buttonType: DeprecatedButtonVariant | ButtonVariant) { - switch (buttonType) { - case DeprecatedButtonVariant.Primary: - case DeprecatedButtonVariant.Secondary: - case DeprecatedButtonVariant.Delete: - return ButtonStyles.deprecatedButtonStyles; - case ButtonVariant.Primary: - case ButtonVariant.Secondary: - case ButtonVariant.Delete: - case ButtonVariant.Highlight: - case ButtonVariant.OutlinePrimary: - case ButtonVariant.OutlineSecondary: - case ButtonVariant.OutlineInverse: - default: - return ButtonStyles.canvasButtonStyles; - } -} - -export function getButtonFocusRing(variant: AllButtonVariants): CSSObject { - const buttonColors = ButtonColors[variant]; - - if (buttonColors == null) { - return {}; - } - - switch (variant) { - case DeprecatedButtonVariant.Primary: - case DeprecatedButtonVariant.Secondary: - case TextButtonVariant.Default: - case TextButtonVariant.AllCaps: - return focusRing(2, 0); - case ButtonVariant.OutlineInverse: - return focusRing(2, 2, true, false, buttonColors.focusRingInner, buttonColors.focusRingOuter); - case IconButtonVariant.Plain: - return focusRing(2); - case IconButtonVariant.Inverse: - case IconButtonVariant.InverseFilled: - return focusRing(2, 2, true, false, buttonColors.focusRingInner, buttonColors.focusRingOuter); - case TextButtonVariant.Inverse: - case TextButtonVariant.InverseAllCaps: - return focusRing(2, 0, true, false, buttonColors.focusRingInner, buttonColors.focusRingOuter); - default: - return focusRing(2, 2); - } -} - -export function getButtonStateStyle(variant: AllButtonVariants): CSSObject { - const buttonColors = ButtonColors[variant]; - - if (buttonColors == null) { - return {}; - } - - const baseStyles = { - backgroundColor: buttonColors.background, - borderColor: buttonColors.border, - color: buttonColors.text, - ...(buttonColors.labelIcon && { - 'span .wd-icon-fill, span .wd-icon-accent': { - transition: 'fill 120ms ease-in', - fill: buttonColors.labelIcon, - }, - }), - ...(buttonColors.labelData && { - ['.' + ButtonStyles.labelDataBaseStyles.classname]: { - color: buttonColors.labelData, - }, - }), - }; - - const hoverStyles = { - ':hover': { - backgroundColor: buttonColors.hoverBackground, - borderColor: buttonColors.hoverBorder, - color: buttonColors.hoverText, - ...(buttonColors.labelDataHover && { - ['.' + ButtonStyles.labelDataBaseStyles.classname]: { - transition: 'color 120ms ease-in', - color: buttonColors.labelDataHover, - }, - }), - ...(buttonColors.labelIconHover && { - 'span .wd-icon-fill, span .wd-icon-accent': { - fill: buttonColors.labelIconHover, - }, - }), - }, - }; - - const activeStyles = { - ':active, :focus:active, :hover:active': { - backgroundColor: buttonColors.activeBackground, - borderColor: buttonColors.activeBorder, - color: buttonColors.activeText, - ...(buttonColors.labelDataActive && { - ['.' + ButtonStyles.labelDataBaseStyles.classname]: { - color: buttonColors.labelDataActive, - }, - }), - ...(buttonColors.labelIconActive && { - 'span .wd-icon-fill, span .wd-icon-accent': { - fill: buttonColors.labelIconActive, - }, - }), - }, - }; - - return { - ...baseStyles, - ':focus': { - backgroundColor: buttonColors.focusBackground, - borderColor: buttonColors.focusBorder, - color: buttonColors.focusText, - ...(buttonColors.labelDataFocus && { - ['.' + ButtonStyles.labelDataBaseStyles.classname]: { - color: buttonColors.labelDataFocus, - }, - }), - ...(buttonColors.labelIconFocus && { - 'span .wd-icon-fill, span .wd-icon-accent': { - fill: buttonColors.labelIconFocus, - }, - }), - }, - - ...activeStyles, - ...hoverStyles, - ':disabled, :active:disabled, :focus:disabled, :hover:disabled': { - backgroundColor: buttonColors.disabledBackground, - borderColor: buttonColors.disabledBorder, - color: buttonColors.disabledText, - ...(buttonColors.labelIconDisabled && { - 'span .wd-icon-fill, span .wd-icon-accent': { - fill: buttonColors.labelIconDisabled, - }, - }), - ...(buttonColors.labelDataDisabled && { - ['.' + ButtonStyles.labelDataBaseStyles.classname]: { - color: buttonColors.labelDataDisabled, - }, - }), - }, - '&:not([disabled])': { - '&:focus': { - borderColor: buttonColors.focusBorder, - ...getButtonFocusRing(variant), - }, - '&:active': { - borderColor: buttonColors.activeBorder, - ...getButtonFocusRing(variant), - }, - }, - ...mouseFocusBehavior({ - '&:focus': { - ...baseStyles, - outline: 'none', - boxShadow: 'none', - animation: 'none', - ...hoverStyles, - ...activeStyles, - }, - }), - }; -} - -export function getIconButtonStateStyle(variant: AllButtonVariants): CSSObject { - const buttonColors = ButtonColors[variant]; - - if (buttonColors == null) { - return {}; - } - - const baseStyles = { - borderColor: buttonColors.border, - backgroundColor: buttonColors.background, - ...(buttonColors.labelIcon && { - 'span .wd-icon-fill, span .wd-icon-accent': { - transition: 'fill 120ms ease-in', - fill: buttonColors.labelIcon, - }, - }), - ...(buttonColors.labelData && { - ['.' + ButtonStyles.labelDataBaseStyles.classname]: { - color: buttonColors.labelData, - }, - }), - }; - - const hoverStyles = { - ':hover': { - backgroundColor: buttonColors.hoverBackground, - ...(buttonColors.labelIconHover && { - 'span .wd-icon-fill, span .wd-icon-accent': { - fill: buttonColors.labelIconHover, - }, - }), - }, - }; - - const activeStyles = { - ':active, :focus:active, :hover:active': { - backgroundColor: buttonColors.activeBackground, - ...(buttonColors.labelIconHover && { - 'span .wd-icon-fill, span .wd-icon-accent': { - fill: buttonColors.labelIconActive, - }, - }), - }, - }; - - return { - ...baseStyles, - ':focus': { - backgroundColor: buttonColors.focusBackground, - ...(buttonColors.labelDataFocus && { - ['.' + ButtonStyles.labelDataBaseStyles.classname]: { - color: buttonColors.labelDataFocus, - }, - }), - ...(buttonColors.labelIconFocus && { - 'span .wd-icon-fill, span .wd-icon-accent': { - fill: buttonColors.labelIconFocus, - }, - }), - }, - ...hoverStyles, - ...activeStyles, - ':disabled, :active:disabled, :focus:disabled, :hover:disabled': { - pointerEvents: 'none', - backgroundColor: buttonColors.disabledBackground, - borderColor: buttonColors.disabledBorder, - color: buttonColors.disabledText, - ...(buttonColors.labelIconDisabled && { - 'span .wd-icon-fill, span .wd-icon-accent': { - fill: buttonColors.labelIconDisabled, - }, - }), - ...(buttonColors.labelDataDisabled && { - ['.' + ButtonStyles.labelDataBaseStyles.classname]: { - color: buttonColors.labelDataDisabled, - }, - }), - }, - '&:not([disabled])': { - '&:focus': { - background: buttonColors.focusBackground, - borderColor: buttonColors.focusBorder, - ...getButtonFocusRing(variant), - }, - '&:active': { - borderColor: buttonColors.activeBorder, - ...getButtonFocusRing(variant), - }, - - '&:hover:focus': { - backgroundColor: buttonColors.focusHover, - 'span .wd-icon-fill, span .wd-icon-accent': { - fill: buttonColors.labelIconFocusHover, - }, - }, - '&:focus:active': { - backgroundColor: buttonColors.activeBackground, - }, - }, - ...mouseFocusBehavior({ - '&:focus': { - ...baseStyles, - outline: 'none', - boxShadow: 'none', - animation: 'none', - ...hoverStyles, - ...activeStyles, - }, - }), - }; -} diff --git a/modules/button/react/package.json b/modules/button/react/package.json index 60942d7ef5..9f40376b68 100644 --- a/modules/button/react/package.json +++ b/modules/button/react/package.json @@ -1,7 +1,8 @@ + { - "name": "@workday/canvas-kit-react-button", - "version": "3.4.0", - "description": "A canvas-styled react button", + "name": "@workday/canvas-kit-labs-react-button", + "version": "0.0.0", + "description": "A collection of various Canvas Buttons", "author": "Workday, Inc. (https://www.workday.com)", "license": "Apache-2.0", "main": "dist/commonjs/index.js", @@ -10,7 +11,7 @@ "types": "dist/es6/index.d.ts", "repository": { "type": "git", - "url": "http://github.com/Workday/canvas-kit/tree/master/modules/button/react" + "url": "https://github.com/Workday/canvas-kit/tree/master/modules/_labs/button/react" }, "files": [ "dist/", @@ -19,13 +20,11 @@ ], "scripts": { "watch": "yarn build:es6 -w", - "test": "echo \"Error: no test specified\" && exit 1", "clean": "rimraf dist && rimraf .build-info && mkdirp dist", - "build:cjs": "babel --no-babelrc --plugins babel-plugin-transform-es2015-modules-commonjs dist/es6 --out-dir dist/commonjs --copy-files", + "build:cjs": "tsc -p tsconfig.cjs.json", "build:es6": "tsc -p tsconfig.es6.json", - "build:babel": "babel --no-babelrc --plugins babel-plugin-emotion dist --out-dir dist", "build:rebuild": "npm-run-all clean build", - "build": "npm-run-all --sequential build:es6 build:babel build:cjs", + "build": "npm-run-all --parallel build:cjs build:es6", "depcheck": "node ../../../utils/check-dependencies-exist.js" }, "keywords": [ diff --git a/modules/button/react/spec/Button.spec.tsx b/modules/button/react/spec/Button.spec.tsx new file mode 100644 index 0000000000..5bb29454d7 --- /dev/null +++ b/modules/button/react/spec/Button.spec.tsx @@ -0,0 +1,88 @@ +import * as React from 'react'; +import { + Button, + DeleteButton, + DropdownButton, + HighlightButton, + OutlineButton, + TextButton, + deprecated_Button as DeprecatedButton, +} from '../index'; +import {render, fireEvent} from '@testing-library/react'; + +const map: any = { + Button: Button, + 'Delete Button': DeleteButton, + 'Dropdown Button': DropdownButton, + 'Highlight Button': HighlightButton, + 'Outline Button': OutlineButton, + 'Text Button': TextButton, + 'Deprecated Button': DeprecatedButton, +}; +Object.keys(map).forEach(buttonName => { + const ButtonComponent = map[buttonName]; + + describe(buttonName, () => { + const cb = jest.fn(); + afterEach(() => { + cb.mockReset(); + }); + + describe('when rendered', () => { + it('should render a button', () => { + const {getByRole} = render(); + expect(getByRole('button')).toBeDefined(); + }); + }); + + describe('when rendered with an id', () => { + it('should render a button with id', () => { + const id = 'myButton'; + const {getByRole} = render(); + expect(getByRole('button')).toHaveAttribute('id', id); + }); + }); + + describe('when rendered with contents', () => { + it('should render a button with contents', () => { + const label = 'Button Label'; + const {getByRole} = render({label}); + expect(getByRole('button')).toContainHTML(label); + }); + }); + + describe('when rendered with disabled attribute', () => { + it('should render a disabled button', () => { + const {getByRole} = render(); + expect(getByRole('button')).toHaveProperty('disabled', true); + }); + }); + + describe('when rendered with extra, arbitrary props', () => { + it('should spread extra props onto the button', () => { + const attr = 'test'; + const {getByRole} = render(); + expect(getByRole('button')).toHaveAttribute('data-propspread', attr); + }); + }); + + describe('when rendered with a button ref', () => { + it('should set the ref to the checkbox input element', () => { + const ref = React.createRef(); + + render(); + + expect(ref.current).not.toBeNull(); + expect(ref.current!.tagName.toLowerCase()).toEqual('button'); + }); + }); + + describe('when clicked', () => { + it('should call a callback function', () => { + const {getByRole} = render(); + fireEvent.click(getByRole('button')); + expect(cb).toHaveBeenCalledTimes(1); + }); + }); + }); +}); diff --git a/modules/button/react/spec/Button_Deprecated.spec.tsx b/modules/button/react/spec/Button_Deprecated.spec.tsx deleted file mode 100644 index 0e6221a3df..0000000000 --- a/modules/button/react/spec/Button_Deprecated.spec.tsx +++ /dev/null @@ -1,200 +0,0 @@ -import * as React from 'react'; -import {deprecated_Button as Button, ButtonProps} from '../lib/Button'; -import {DeprecatedButtonVariant} from '../lib/types'; -import TextButton from '../lib/TextButton'; -import {mount} from 'enzyme'; -import ReactDOMServer from 'react-dom/server'; -import {axe} from 'jest-axe'; - -describe('Deprecated Button', () => { - const cb = jest.fn(); - afterEach(() => { - cb.mockReset(); - }); - - test('render a button with id', () => { - const component = mount(Button Label); - expect(component.find('button').props().id).toBe('myBtn'); - component.unmount(); - }); - - test('should call a callback function', () => { - const component = mount(Button Label); - const button = component.find('button'); - button.simulate('click'); - expect(cb.mock.calls.length).toBe(1); - component.unmount(); - }); - - test('should not call a callback function when disabled', () => { - const component = mount( - - Button Label - - ); - const button = component.find('button'); - button.simulate('click'); - expect(cb.mock.calls.length).toBe(0); - component.unmount(); - }); - - test('Button should spread extra props', () => { - const component = mount(Button Label); - const container = component.at(0).getDOMNode(); - expect(container.getAttribute('data-propspread')).toBe('test'); - component.unmount(); - }); -}); - -describe('TextButton', () => { - const cb = jest.fn(); - afterEach(() => { - cb.mockReset(); - }); - - test('should call a callback function', () => { - const component = mount(Button Label); - const button = component.find('button span'); - button.simulate('click'); - expect(cb.mock.calls.length).toBe(1); - component.unmount(); - }); -}); - -describe('Button Accessibility', () => { - const cb = jest.fn(); - afterEach(() => { - cb.mockReset(); - }); - - test('button should be using HTML5 tag', () => { - const component = mount(Button); - expect(component.getDOMNode().tagName.toLowerCase()).toEqual('button'); - component.unmount(); - }); - - test("button's label should be in a span", () => { - const labelText: string = 'Button'; - const component = mount({labelText}); - expect( - component - .find('button') - .find('span') - .getDOMNode().innerHTML - ).toEqual(labelText); - component.unmount(); - }); - - test('enabled button should NOT have disabled attribute set', () => { - const component = mount(Button); - expect( - component - .find('button') - .getDOMNode() - .hasAttribute('disabled') - ).toEqual(false); - component.unmount(); - }); - - test('disabled button should have disabled attribute set', () => { - const component = mount(Button); - expect( - component - .find('button') - .getDOMNode() - .hasAttribute('disabled') - ).toEqual(true); - component.unmount(); - }); - - test('button should pass axe DOM accessibility guidelines', async () => { - const html = ReactDOMServer.renderToString(Button); - expect(await axe(html)).toHaveNoViolations(); - }); -}); - -describe('Button Focus', () => { - const cb = jest.fn(); - afterEach(() => { - cb.mockReset(); - }); - - // expected usage to manage focus via buttonRef - class FocusableButton extends React.Component> { - readonly buttonRef: React.RefObject; - - constructor(props: ButtonProps) { - super(props); - this.buttonRef = React.createRef(); - } - - // focus on button in componentDidMount for purposes of tests - componentDidMount() { - if (!this.props.disabled && this.buttonRef && this.buttonRef.current) { - this.buttonRef.current.focus(); - } - } - - render() { - return ( - - {this.props.children} - - ); - } - } - - // expected usage to manage focus via buttonRef cb - class FocusableButtonCB extends React.Component> { - private buttonElement: HTMLButtonElement; - - // focus on button in componentDidMount for purposes of tests - componentDidMount() { - if (!this.props.disabled && this.buttonElement) { - this.buttonElement.focus(); - } - } - - render() { - return ( - { - this.buttonElement = buttonElement; - }} - {...this.props} - > - {this.props.children} - - ); - } - } - - test('button should not allow focus when disabled via buttonRef cb', () => { - const component = mount(Button); - const activeElement = document.activeElement; - const buttonWrapper = component.find('button'); - expect(buttonWrapper.getDOMNode()).not.toEqual(activeElement); - }); - - test('button should allow focus via buttonRef cb', () => { - const component = mount(Button); - const activeElement = document.activeElement; - const buttonWrapper = component.find('button'); - expect(buttonWrapper.getDOMNode()).toEqual(activeElement); - }); - - test('button should not allow focus when disabled via buttonRef', () => { - const component = mount(Button); - const activeElement = document.activeElement; - const buttonWrapper = component.find('button'); - expect(buttonWrapper.getDOMNode()).not.toEqual(activeElement); - }); - - test('button should allow focus via buttonRef', () => { - const component = mount(Button); - const activeElement = document.activeElement; - const buttonWrapper = component.find('button'); - expect(buttonWrapper.getDOMNode()).toEqual(activeElement); - }); -}); diff --git a/modules/button/react/spec/IconButton.spec.tsx b/modules/button/react/spec/IconButton.spec.tsx index 534b988559..c0379741d8 100644 --- a/modules/button/react/spec/IconButton.spec.tsx +++ b/modules/button/react/spec/IconButton.spec.tsx @@ -1,11 +1,7 @@ import * as React from 'react'; -import {mount} from 'enzyme'; -import {render} from '@testing-library/react'; -import IconButton, {iconButtonIdentifier} from '../lib/IconButton'; -import {SystemIcon} from '@workday/canvas-kit-react-icon'; +import {IconButton} from '../index'; import {activityStreamIcon} from '@workday/canvas-system-icons-web'; -import ReactDOMServer from 'react-dom/server'; -import {axe} from 'jest-axe'; +import {render, fireEvent} from '@testing-library/react'; describe('Icon Button', () => { const cb = jest.fn(); @@ -13,130 +9,89 @@ describe('Icon Button', () => { cb.mockReset(); }); - test('render an icon button with id', () => { - const component = mount( - - - - ); - expect(component.find('button').props().id).toBe('myBtn'); - component.unmount(); + describe('when rendered', () => { + it('should render a button', () => { + const {getByRole} = render( + + ); + expect(getByRole('button')).toBeDefined(); + }); }); - it('should have an identifier class', () => { - const {getByRole} = render( - - - - ); - - expect(getByRole('button').className).toContain(iconButtonIdentifier); - }); - it('should compose custom classNames with the identifier class', () => { - const testClassName = 'test classname'; - const {getByRole} = render( - - - - ); - - expect(getByRole('button').className).toContain(`${iconButtonIdentifier} ${testClassName}`); + describe('when rendered with an id', () => { + it('should render a button with id', () => { + const id = 'myButton'; + const {getByRole} = render( + + ); + expect(getByRole('button')).toHaveAttribute('id', id); + }); }); - test('should call a callback function', () => { - const component = mount( - - - - ); - const button = component.find('button'); - button.simulate('click'); - expect(cb.mock.calls.length).toBe(1); - component.unmount(); + describe('when rendered with disabled attribute', () => { + it('should render a disabled button', () => { + const {getByRole} = render( + + ); + expect(getByRole('button')).toHaveProperty('disabled', true); + }); }); - test('should not call a callback function when disabled', () => { - const component = mount( - - - - ); - const button = component.find('button'); - button.simulate('click'); - expect(cb.mock.calls.length).toBe(0); - component.unmount(); + describe('when rendered with extra, arbitrary props', () => { + it('should spread extra props onto the button', () => { + const attr = 'test'; + const {getByRole} = render( + + ); + expect(getByRole('button')).toHaveAttribute('data-propspread', attr); + }); }); - test('should call onToggleChange when toggle prop changes', () => { - const wrapper = mount( - - - - ); + describe('when rendered with a button ref', () => { + it('should set the ref to the checkbox input element', () => { + const ref = React.createRef(); - wrapper.setProps({toggled: true}); - wrapper.update(); - wrapper.setProps({toggled: true}); - wrapper.update(); - wrapper.setProps({toggled: undefined}); - wrapper.update(); + render(); - expect(cb.mock.calls.length).toBe(2); + expect(ref.current).not.toBeNull(); + expect(ref.current!.tagName.toLowerCase()).toEqual('button'); + }); }); -}); -describe('Icon Button Accessibility', () => { - const cb = jest.fn(); - afterEach(() => { - cb.mockReset(); + describe('when clicked', () => { + it('should call a callback function', () => { + const {getByRole} = render( + + ); + fireEvent.click(getByRole('button')); + expect(cb).toHaveBeenCalledTimes(1); + }); }); - test('icon button should be using HTML5 tag', () => { - const component = mount( - - {' '} - - ); - expect(component.getDOMNode().tagName.toLowerCase()).toEqual('button'); - component.unmount(); - }); + describe('when toggled', () => { + test('should call onToggleChange when toggle prop changes', () => { + const {getByRole, rerender} = render( + + ); - test('enabled icon button should NOT have disabled attribute set', () => { - const component = mount( - - {' '} - - ); - expect( - component - .find('button') - .getDOMNode() - .hasAttribute('disabled') - ).toEqual(false); - component.unmount(); - }); - - test('disabled icon button should have disabled attribute set', () => { - const component = mount( - - {' '} - - ); - expect( - component - .find('button') - .getDOMNode() - .hasAttribute('disabled') - ).toEqual(true); - component.unmount(); - }); + expect(getByRole('button')).toHaveAttribute('aria-pressed', 'false'); + rerender( + + ); - test('IconButton should pass axe DOM accessibility guidelines', async () => { - const html = ReactDOMServer.renderToString( - - {' '} - - ); - expect(await axe(html)).toHaveNoViolations(); + expect(getByRole('button')).toHaveAttribute('aria-pressed', 'true'); + expect(cb).toHaveBeenCalledTimes(1); + expect(cb).toHaveBeenCalledWith(true); + }); }); }); diff --git a/modules/button/react/spec/IconButtonToggleGroup.spec.tsx b/modules/button/react/spec/IconButtonToggleGroup.spec.tsx deleted file mode 100644 index bd0f097426..0000000000 --- a/modules/button/react/spec/IconButtonToggleGroup.spec.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import * as React from 'react'; -import {mount} from 'enzyme'; -import IconButton from '../lib/IconButton'; -import IconButtonToggleGroup from '../lib/IconButtonToggleGroup'; -import { - listViewIcon, - worksheetsIcon, - deviceTabletIcon, - percentageIcon, -} from '@workday/canvas-system-icons-web'; - -describe('Icon Button Toggle', () => { - const cb = jest.fn(); - afterEach(() => { - cb.mockReset(); - }); - - test('renders two buttons as expected', () => { - const component = mount( - - - - - ); - expect(component.find('button').length).toEqual(2); - - component - .find('button') - .at(0) - .simulate('click'); - - expect(cb.mock.calls.length).toBe(1); - }); - - test('renders two buttons with disabled button', () => { - const component = mount( - - - - {/* ensure random elements don't break anything */} - - ); - - // clicking on a disabled button shouldn't trigger callback - component - .find('button') - .at(0) - .simulate('click'); - - expect(cb.mock.calls.length).toBe(0); - - // clicking on an active button should trigger callback - component - .find('button') - .at(1) - .simulate('click'); - - expect(cb.mock.calls.length).toBe(1); - expect(cb.mock.calls[0][0]).toBe('table-view'); - }); - - test('renders correctly when RTL is true', () => { - const component = mount( - - - - - - - ); - - expect(component.find('button').length).toEqual(4); - - expect( - component - .find('button') - .at(0) - .prop('value') - ).toEqual('percent-view'); - }); - - test('behavior when no values are used', () => { - const component = mount( - - - - - ); - - component - .find('button') - .at(1) - .simulate('click'); - - expect(cb.mock.calls.length).toBe(1); - expect(cb.mock.calls[0][0]).toBe(1); - }); -}); diff --git a/modules/button/react/stories/stories.tsx b/modules/button/react/stories/stories.tsx deleted file mode 100644 index 488108ce17..0000000000 --- a/modules/button/react/stories/stories.tsx +++ /dev/null @@ -1,610 +0,0 @@ -/// -/** @jsx jsx */ -import {jsx, CSSObject} from '@emotion/core'; -import {storiesOf} from '@storybook/react'; -import withReadme from 'storybook-readme/with-readme'; - -import { - editIcon, - playCircleIcon, - arrowRightIcon, - activityStreamIcon, -} from '@workday/canvas-system-icons-web'; - -import {Button, DropdownButton, TextButton} from '../index'; -import README from '../README.md'; - -const blueBackground: CSSObject = { - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - flexWrap: 'wrap', - backgroundColor: '#0875e1', - margin: '0 10px', - padding: '12px', - maxWidth: 'max-content', - borderRadius: '3px', - button: { - margin: '12px', - }, -}; - -const buttonContainer = { - display: 'flex', - alignItems: 'center', - '& button + button': { - marginLeft: 10, - }, -}; - -storiesOf('Components|Buttons/Button/React', module) - .addParameters({component: Button}) - .addDecorator(withReadme(README)) - .add('Primary', () => ( - - Large Primary - - Primary - - - Primary - - - Primary - - - Primary - - Medium Primary - - Primary - - - Primary - - - Primary - - - Primary - - Small Primary - - Primary - - - Primary - - Growing Primary - - - Primary - - - - )) - .add('Secondary', () => ( - - Large Secondary - - Secondary - - - Secondary - - - Secondary - - - Secondary - - Medium Secondary - - Secondary - - - Secondary - - - Secondary - - - Secondary - - Small Secondary - - Secondary - - - Secondary - - Growing Secondary - - Growing Secondary - - - )) - .add('Delete', () => ( - - Large Delete - - Delete - - - Delete - - Medium Delete - - Delete - - - Delete - - Small Delete - - Delete - - - Delete - - - )) - .add('Highlight', () => ( - - Large Highlight - - Highlight - - - Highlight - - - Highlight - - Medium Highlight - - Highlight - - - Highlight - - - Highlight - - Growing - - - Highlight - - - Highlight - - - - )); - -storiesOf('Components|Buttons/Button/React/Text', module) - .addParameters({component: TextButton}) - .addDecorator(withReadme(README)) - .add('Default', () => ( - - Large - - Text - - - Text - - Small - - Text - - - Text - - All Caps - All Caps - Icons - - - Left Icon Large - - - Right Icon Large - - - - )) - .add('Inverse', () => ( - - Large Inverse - - - Text - - - Text - - - Small Inverse - - - Text - - - Text - - - All Caps Inverse - - All Caps - - Icons Inverse - - - Left Icon Large - - - Right Icon Large - - - - )); - -storiesOf('Components|Buttons/Button/React/Outline', module) - .addParameters({component: Button}) - .addDecorator(withReadme(README)) - .add('Primary', () => ( - - Large Primary - - Outline Primary - - - Outline Primary - - - Outline Primary - - - Outline Primary - - Medium Primary - - Outline Primary - - - Outline Primary - - - Outline Primary - - - Outline Primary - - Small Primary - - Outline Primary - - - Outline Primary - - Growing Primary - - Growing Primary Outline - - - )) - .add('Secondary', () => ( - - Large Secondary - - Outline Secondary - - - Outline Secondary - - - Outline Secondary - - - Outline Secondary - - Medium Secondary - - Outline Secondary - - - Outline Secondary - - - Outline Secondary - - - Outline Secondary - - Small Secondary - - Outline Secondary - - - Outline Secondary - - Growing Secondary - - Growing Secondary Outline - - - )) - .add('Inverse', () => ( - - Large Inverse - - - Outline Inverse - - - Outline Inverse - - - Outline Inverse - - - Outline Inverse - - - Medium Inverse - - - Outline Inverse - - - Outline Inverse - - - Outline Inverse - - - Outline Inverse - - - Small Inverse - - - Outline Inverse - - - Outline Inverse - - - Growing Inverse - - - Growing Inverse Outline - - - - )); - -storiesOf('Components|Buttons/Button/React/Dropdown', module) - .addParameters({component: DropdownButton}) - .addDecorator(withReadme(README)) - .add('Primary', () => ( - - Large Primary - - Dropdown Button - - - Dropdown Button - - Medium Primary - - Dropdown Button - - - Dropdown Button - - - )) - .add('Secondary', () => ( - - Large Secondary - - Dropdown Button - - - Dropdown Button - - Medium Secondary - - Dropdown Button - - - Dropdown Button - - - )); diff --git a/modules/button/react/stories/stories_Button.tsx b/modules/button/react/stories/stories_Button.tsx new file mode 100644 index 0000000000..5b42b7c605 --- /dev/null +++ b/modules/button/react/stories/stories_Button.tsx @@ -0,0 +1,156 @@ +/// +/** @jsx jsx */ +import {jsx} from '@emotion/core'; +import {storiesOf} from '@storybook/react'; +import withReadme from 'storybook-readme/with-readme'; + +import {editIcon, playCircleIcon, activityStreamIcon} from '@workday/canvas-system-icons-web'; + +import {Button} from '../index'; +import README from '../README.md'; + +const buttonContainer = { + display: 'flex', + alignItems: 'center', + '& button + button': { + marginLeft: 10, + }, +}; + +storiesOf('Components|Buttons/Button/React/Standard', module) + .addParameters({component: Button}) + .addDecorator(withReadme(README)) + .add('Primary', () => ( + + Large Primary + + Primary + + + Primary + + + Primary + + + Primary + + + Medium Primary + + Primary + + + Primary + + + Primary + + + Primary + + + Small Primary + + Primary + + + Primary + + + Growing Primary + + + Primary + + + + )) + .add('Secondary', () => ( + + Large Secondary + + Secondary + + + Secondary + + + Secondary + + + Secondary + + + Medium Secondary + + Secondary + + + Secondary + + + Secondary + + + Secondary + + + Small Secondary + + Secondary + + + Secondary + + + Growing Secondary + + Growing Secondary + + + )); diff --git a/modules/button/react/stories/stories_DeleteButton.tsx b/modules/button/react/stories/stories_DeleteButton.tsx new file mode 100644 index 0000000000..7d5400de54 --- /dev/null +++ b/modules/button/react/stories/stories_DeleteButton.tsx @@ -0,0 +1,33 @@ +/// +/** @jsx jsx */ +import {jsx} from '@emotion/core'; +import {storiesOf} from '@storybook/react'; +import withReadme from 'storybook-readme/with-readme'; + +import {DeleteButton} from '../index'; +import README from '../README.md'; + +storiesOf('Components|Buttons/Button/React', module) + .addParameters({component: DeleteButton}) + .addDecorator(withReadme(README)) + .add('Delete', () => ( + + Large Delete + Delete + + Delete + + + Medium Delete + Delete + + Delete + + + Small Delete + Delete + + Delete + + + )); diff --git a/modules/button/react/stories/stories_Dropdown.tsx b/modules/button/react/stories/stories_Dropdown.tsx new file mode 100644 index 0000000000..58325fc518 --- /dev/null +++ b/modules/button/react/stories/stories_Dropdown.tsx @@ -0,0 +1,50 @@ +/// +/** @jsx jsx */ +import {jsx} from '@emotion/core'; +import {storiesOf} from '@storybook/react'; +import withReadme from 'storybook-readme/with-readme'; + +import {DropdownButton} from '../index'; +import README from '../README.md'; + +storiesOf('Components|Buttons/Button/React/Dropdown', module) + .addParameters({component: DropdownButton}) + .addDecorator(withReadme(README)) + .add('Primary', () => ( + + Large Primary + + Dropdown Button + + + Dropdown Button + + + Medium Primary + + Dropdown Button + + + Dropdown Button + + + )) + .add('Secondary', () => ( + + Large Secondary + + Dropdown Button + + + Dropdown Button + + + Medium Secondary + + Dropdown Button + + + Dropdown Button + + + )); diff --git a/modules/button/react/stories/stories_HighlightButton.tsx b/modules/button/react/stories/stories_HighlightButton.tsx new file mode 100644 index 0000000000..57371b8144 --- /dev/null +++ b/modules/button/react/stories/stories_HighlightButton.tsx @@ -0,0 +1,49 @@ +/// +/** @jsx jsx */ +import {jsx} from '@emotion/core'; +import {storiesOf} from '@storybook/react'; +import withReadme from 'storybook-readme/with-readme'; + +import {playCircleIcon, activityStreamIcon} from '@workday/canvas-system-icons-web'; + +import {HighlightButton} from '../index'; +import README from '../README.md'; + +const buttonContainer = { + display: 'flex', + alignItems: 'center', + '& button + button': { + marginLeft: 10, + }, +}; + +storiesOf('Components|Buttons/Button/React', module) + .addParameters({component: HighlightButton}) + .addDecorator(withReadme(README)) + .add('Highlight', () => ( + + Large Highlight + Highlight + + Highlight + + + Medium Highlight + + Highlight + + + Highlight + + + Growing + + + Highlight + + + Highlight + + + + )); diff --git a/modules/button/react/stories/stories_IconButton.tsx b/modules/button/react/stories/stories_IconButton.tsx new file mode 100644 index 0000000000..09f24566db --- /dev/null +++ b/modules/button/react/stories/stories_IconButton.tsx @@ -0,0 +1,379 @@ +/// +/** @jsx jsx */ +import {jsx, CSSObject} from '@emotion/core'; +import * as React from 'react'; +import {storiesOf} from '@storybook/react'; +import withReadme from 'storybook-readme/with-readme'; + +import {activityStreamIcon} from '@workday/canvas-system-icons-web'; + +import {IconButton} from '../index'; + +import README from '../README.md'; + +const iconButtonLayout: CSSObject = { + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + margin: '0 10px', + padding: '24px', + maxWidth: 'max-content', + borderRadius: '4px', + button: { + margin: '0 12px', + }, +}; + +const blueBackground: CSSObject = { + ...iconButtonLayout, + backgroundColor: '#0875e1', +}; + +storiesOf('Components|Buttons/Button/React/Icon Button', module) + .addParameters({component: IconButton}) + .addDecorator(withReadme(README)) + .add('Circle', () => { + const [toggled, setToggled] = React.useState(); + const handleToggle = () => { + setToggled(!toggled); + }; + + return ( + + Medium Default + + + + Small Default + + + + Toggleable Default + + + ); + }) + .add('Square', () => { + const [toggled, setToggled] = React.useState(); + const handleToggle = () => { + setToggled(!toggled); + }; + + return ( + + Medium Square + + + + Small Square + + + + Toggleable Square + + + ); + }) + .add('Square Filled', () => { + const [toggled, setToggled] = React.useState(); + const handleToggle = () => { + setToggled(!toggled); + }; + + return ( + + Medium Square + + + Small Square + + + Toggleable Square + + + ); + }) + .add('Plain', () => { + const [toggled, setToggled] = React.useState(); + const handleToggle = () => { + setToggled(!toggled); + }; + + return ( + + Medium Plain + + + Small Plain + + + Toggleable Plain + + + ); + }) + .add('Circle Filled', () => { + const [toggled, setToggled] = React.useState(); + const handleToggle = () => { + setToggled(!toggled); + }; + + return ( + + Medium Filled + + + Small Filled + + + Toggleable Filled + + + ); + }) + .add('Inverse', () => { + const [toggled, setToggled] = React.useState(); + const handleToggle = () => { + setToggled(!toggled); + }; + + return ( + + Medium Inverse + + + + + Small Inverse + + + + + Toggleable Inverse + + + + + ); + }) + .add('Inverse Filled', () => { + const [toggled, setToggled] = React.useState(); + const handleToggle = () => { + setToggled(!toggled); + }; + + return ( + + Medium Inverse Filled + + + + + Small Inverse Filled + + + + + Toggleable Inverse Filled + + + + + ); + }); diff --git a/modules/button/react/stories/stories_OutlineButton.tsx b/modules/button/react/stories/stories_OutlineButton.tsx new file mode 100644 index 0000000000..96dfc596b7 --- /dev/null +++ b/modules/button/react/stories/stories_OutlineButton.tsx @@ -0,0 +1,211 @@ +/// +/** @jsx jsx */ +import {jsx, CSSObject} from '@emotion/core'; +import {storiesOf} from '@storybook/react'; +import withReadme from 'storybook-readme/with-readme'; + +import {editIcon, playCircleIcon, activityStreamIcon} from '@workday/canvas-system-icons-web'; + +import {OutlineButton} from '../index'; +import README from '../README.md'; + +const blueBackground: CSSObject = { + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + flexWrap: 'wrap', + backgroundColor: '#0875e1', + margin: '0 10px', + padding: '12px', + maxWidth: 'max-content', + borderRadius: '3px', + button: { + margin: '12px', + }, +}; + +storiesOf('Components|OutlineButtons/OutlineButton/React/Outline', module) + .addParameters({component: OutlineButton}) + .addDecorator(withReadme(README)) + .add('Primary', () => ( + + Large Primary + + Outline Primary + + + Outline Primary + + + Outline Primary + + + Outline Primary + + + Medium Primary + + Outline Primary + + + Outline Primary + + + Outline Primary + + + Outline Primary + + + Small Primary + + Outline Primary + + + Outline Primary + + + Growing Primary + + Growing Primary Outline + + + )) + .add('Secondary', () => ( + + Large Secondary + Outline Secondary + + Outline Secondary + + + Outline Secondary + + + Outline Secondary + + + Medium Secondary + Outline Secondary + + Outline Secondary + + + Outline Secondary + + + Outline Secondary + + + Small Secondary + Outline Secondary + + Outline Secondary + + + Growing Secondary + + Growing Secondary Outline + + + )) + .add('Inverse', () => ( + + Large Inverse + + + Outline Inverse + + + Outline Inverse + + + Outline Inverse + + + Outline Inverse + + + + Medium Inverse + + + Outline Inverse + + + Outline Inverse + + + Outline Inverse + + + Outline Inverse + + + + Small Inverse + + + Outline Inverse + + + Outline Inverse + + + + Growing Inverse + + + Growing Inverse Outline + + + + )); diff --git a/modules/button/react/stories/stories_TextButton.tsx b/modules/button/react/stories/stories_TextButton.tsx new file mode 100644 index 0000000000..aa3f0eba3d --- /dev/null +++ b/modules/button/react/stories/stories_TextButton.tsx @@ -0,0 +1,125 @@ +/// +/** @jsx jsx */ +import {jsx, CSSObject} from '@emotion/core'; +import {storiesOf} from '@storybook/react'; +import withReadme from 'storybook-readme/with-readme'; + +import {editIcon, arrowRightIcon} from '@workday/canvas-system-icons-web'; + +import {TextButton} from '../index'; +import README from '../README.md'; + +const blueBackground: CSSObject = { + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + flexWrap: 'wrap', + backgroundColor: '#0875e1', + margin: '0 10px', + padding: '12px', + maxWidth: 'max-content', + borderRadius: '3px', + button: { + margin: '12px', + }, +}; + +const buttonContainer = { + display: 'flex', + alignItems: 'center', + '& button + button': { + marginLeft: 10, + }, +}; + +storiesOf('Components|Buttons/Button/React/Text', module) + .addParameters({component: TextButton}) + .addDecorator(withReadme(README)) + .add('Default', () => ( + + Medium Large + + Text + + + Text + + + Small + + Text + + + Text + + + All Caps + All Caps + + Icons + + + Left Icon + + + Right Icon + + + + )) + .add('Inverse', () => ( + + Medium Inverse + + + Text + + + Text + + + + Small Inverse + + + Text + + + Text + + + + All Caps Inverse + + + All Caps + + + + Icons Inverse + + + Left Icon + + + Right Icon + + + + )); diff --git a/modules/button/react/stories/stories_deprecated.tsx b/modules/button/react/stories/stories_deprecatedButton.tsx similarity index 99% rename from modules/button/react/stories/stories_deprecated.tsx rename to modules/button/react/stories/stories_deprecatedButton.tsx index 9cb037eafd..6d2b16685a 100644 --- a/modules/button/react/stories/stories_deprecated.tsx +++ b/modules/button/react/stories/stories_deprecatedButton.tsx @@ -16,6 +16,7 @@ storiesOf('Components|Buttons/Button/React/Deprecated', module) Primary Button + Medium Primary Primary Button @@ -23,6 +24,7 @@ storiesOf('Components|Buttons/Button/React/Deprecated', module) Primary Button + Small Primary Primary Button @@ -30,6 +32,7 @@ storiesOf('Components|Buttons/Button/React/Deprecated', module) Primary Button + Growing Primary Growing Primary Button @@ -43,6 +46,7 @@ storiesOf('Components|Buttons/Button/React/Deprecated', module) Secondary Button + Medium Secondary Secondary Button @@ -50,6 +54,7 @@ storiesOf('Components|Buttons/Button/React/Deprecated', module) Secondary Button + Small Secondary Secondary Button @@ -57,6 +62,7 @@ storiesOf('Components|Buttons/Button/React/Deprecated', module) Secondary Button + Growing Secondary Growing Secondary Button @@ -70,6 +76,7 @@ storiesOf('Components|Buttons/Button/React/Deprecated', module) Delete Button + Medium Delete Delete Button @@ -77,6 +84,7 @@ storiesOf('Components|Buttons/Button/React/Deprecated', module) Delete Button + Small Delete Delete Button @@ -84,6 +92,7 @@ storiesOf('Components|Buttons/Button/React/Deprecated', module) Delete Button + Growing Delete Growing Delete Button diff --git a/modules/button/react/stories/stories_iconButton.tsx b/modules/button/react/stories/stories_iconButton.tsx deleted file mode 100644 index 666595e8f1..0000000000 --- a/modules/button/react/stories/stories_iconButton.tsx +++ /dev/null @@ -1,421 +0,0 @@ -/// -/** @jsx jsx */ -import {jsx, CSSObject} from '@emotion/core'; -import * as React from 'react'; -import {storiesOf} from '@storybook/react'; -import withReadme from 'storybook-readme/with-readme'; - -import { - activityStreamIcon, - listViewIcon, - worksheetsIcon, - deviceTabletIcon, - percentageIcon, -} from '@workday/canvas-system-icons-web'; - -import { - IconButton, - IconButtonToggleGroup, - IconButtonToggleGroupProps, - IconButtonVariant, -} from '../index'; - -import README from '../README.md'; -import {IconButtonProps} from '../lib/IconButton'; - -const iconButtonLayout: CSSObject = { - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - margin: '0 10px', - padding: '24px', - maxWidth: 'max-content', - borderRadius: '4px', - button: { - margin: '0 12px', - }, -}; - -const blueBackground: CSSObject = { - ...iconButtonLayout, - backgroundColor: '#0875e1', -}; - -const commonIconButtonProps: Pick = { - 'aria-label': 'Activity Stream', - title: 'Activity Stream', - icon: activityStreamIcon, -}; - -// Wrapper to add state mgmt to IconButtons -interface ToggleIconButtonWrapperState { - isToggled: boolean; -} - -interface ToggleIconButtonWrapperProps { - variant: IconButtonVariant; -} - -export class ToggleIconButtonWrapper extends React.Component< - ToggleIconButtonWrapperProps, - ToggleIconButtonWrapperState -> { - state = { - isToggled: false, - }; - - public render() { - return ( - - ); - } - - public handleToggle = () => { - this.setState({isToggled: !this.state.isToggled}); - }; -} - -// Wrapper to add state mgmt to IconButtonToggleGroups -interface IconButtonToggleGroupWrapperState { - selectedValue: string | number; -} - -export class IconButtonToggleGroupWrapper extends React.Component< - {}, - IconButtonToggleGroupWrapperState -> { - state = { - selectedValue: '', - }; - - public render() { - const child = this.props.children as React.ReactElement; - return React.cloneElement(child, { - value: this.state.selectedValue === '' ? child.props.value : this.state.selectedValue, - onChange: this.handleToggle, - }); - } - - public handleToggle = (selectedValue: string | number) => { - this.setState({selectedValue}); - }; -} - -storiesOf('Components|Buttons/Button/React/Icon Button', module) - .addParameters({component: IconButton}) - .addDecorator(withReadme(README)) - .add('Circle', () => ( - - Medium Default - - - Small Default - - - Toggleable Default - - - )) - .add('Square', () => ( - - Medium Square - - - Small Square - - - Toggleable Square - - - )) - .add('Square Filled', () => ( - - Medium Square - - - Small Square - - - Toggleable Square - - - )) - .add('Plain', () => ( - - Medium Plain - - - Small Plain - - - Toggleable Plain - - - )) - .add('Circle Filled', () => ( - - Medium Filled - - - Small Filled - - - Toggleable Filled - - - )) - .add('Inverse', () => ( - - Medium Inverse - - - - - Small Inverse - - - - - Toggleable Inverse - - - - - )) - .add('Inverse Filled', () => ( - - Medium Inverse Filled - - - - - Small Inverse Filled - - - - - Toggleable Inverse Filled - - - - - )); - -storiesOf('Components|Buttons/Button/React/Icon Button Toggle Group', module) - .addParameters({component: IconButtonToggleGroup}) - .addDecorator(withReadme(README)) - .add('Default', () => ( - - With Three Buttons - - - - - - - - With Four Buttons - - - - - - - - - Right To Left With Four Buttons - - - - - - - - - - )); diff --git a/modules/button/react/stories/stories_iconButtonToggle.tsx b/modules/button/react/stories/stories_iconButtonToggle.tsx deleted file mode 100644 index 6f6b4f5f92..0000000000 --- a/modules/button/react/stories/stories_iconButtonToggle.tsx +++ /dev/null @@ -1,159 +0,0 @@ -/// -/** @jsx jsx */ -import {css, jsx, CSSObject} from '@emotion/core'; -import * as React from 'react'; -import {storiesOf} from '@storybook/react'; -import withReadme from 'storybook-readme/with-readme'; - -import { - activityStreamIcon, - listViewIcon, - worksheetsIcon, - deviceTabletIcon, - percentageIcon, -} from '@workday/canvas-system-icons-web'; - -import { - IconButton, - IconButtonToggleGroup, - IconButtonToggleGroupProps, - IconButtonVariant, -} from '../index'; - -import README from '../README.md'; - -const blueBackground: CSSObject = { - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - backgroundColor: '#0875e1', - margin: '0 10px', - padding: '24px', - maxWidth: 'max-content', - borderRadius: '4px', -}; - -// Wrapper to add state mgmt to IconButtons -interface ToggleIconButtonWrapperState { - isToggled: boolean; -} - -interface ToggleIconButtonWrapperProps { - variant: IconButtonVariant; -} - -export class ToggleIconButtonWrapper extends React.Component< - ToggleIconButtonWrapperProps, - ToggleIconButtonWrapperState -> { - state = { - isToggled: false, - }; - - public render() { - return ( - - ); - } - - public handleToggle = () => { - this.setState({isToggled: !this.state.isToggled}); - }; -} - -// Wrapper to add state mgmt to IconButtonToggleGroups -interface IconButtonToggleGroupWrapperState { - selectedValue: string | number; -} - -export class IconButtonToggleGroupWrapper extends React.Component< - {}, - IconButtonToggleGroupWrapperState -> { - state = { - selectedValue: '', - }; - - public render() { - const child = this.props.children as React.ReactElement; - return React.cloneElement(child, { - value: this.state.selectedValue === '' ? child.props.value : this.state.selectedValue, - onChange: this.handleToggle, - }); - } - - public handleToggle = (selectedValue: string | number) => { - this.setState({selectedValue}); - }; -} - -storiesOf('Components|Buttons/Button/React/Icon Button', module) - .addParameters({component: IconButton}) - .addDecorator(withReadme(README)) - .add('Toggleable', () => ( - - Square Icon Buttons - - - Default Icon Buttons - - - Filled Icon Buttons - - - Inverse Icon Buttons - - - - - Inverse Filled Icon Buttons - - - - - Two buttons Grouped - - - { - alert("Here's your click event target: " + e.currentTarget); - }} - /> - - - - - Four buttons Grouped - - - - - - - - - - RTL - w/ initial selected item & disabled item - - - - - - - - - - )); diff --git a/modules/button/react/stories/stories_visualTesting.tsx b/modules/button/react/stories/stories_visualTesting.tsx index 0b6f333253..a38689ff96 100644 --- a/modules/button/react/stories/stories_visualTesting.tsx +++ b/modules/button/react/stories/stories_visualTesting.tsx @@ -5,8 +5,17 @@ import * as React from 'react'; import {storiesOf} from '@storybook/react'; import {StaticStates} from '@workday/canvas-kit-labs-react-core'; import {ComponentStatesTable, permutateProps} from '../../../../utils/storybook'; -import {playCircleIcon} from '@workday/canvas-system-icons-web'; -import {Button, DropdownButton, TextButton, IconButton} from '../index'; +import {playCircleIcon, activityStreamIcon} from '@workday/canvas-system-icons-web'; +import { + Button, + DropdownButton, + TextButton, + DeleteButton, + HighlightButton, + OutlineButton, + IconButton, + deprecated_Button as DeprecatedButton, +} from '../index'; const buttonLayout: CSSObject = { display: 'flex', @@ -25,41 +34,10 @@ const Container = (props: any) => ( {props.children} ); -const ButtonStates = () => ( +const getButtonStates = (rowProps: any, renderFn: (props: any) => React.ReactNode) => ( { - if (props.size === Button.Size.Small && (props.icon || props.dataLabel)) { - return false; - } - if (props.variant === Button.Variant.Highlight && !props.icon) { - return false; - } - if (props.variant === Button.Variant.Delete && (props.icon || props.dataLabel)) { - return false; - } - return true; - } - )} + rowProps={permutateProps(rowProps)} columnProps={permutateProps( { className: [ @@ -72,7 +50,7 @@ const ButtonStates = () => ( ], disabled: [{label: '', value: false}, {label: 'Disabled', value: true}], }, - props => { + (props: any) => { if (props.disabled && !['', 'hover'].includes(props.className)) { return false; } @@ -80,19 +58,104 @@ const ButtonStates = () => ( } )} > - {props => ( - - Test - - )} + {renderFn} ); -const DropdownButtonStates = () => ( - - + getButtonStates( + { + variant: [ + {value: Button.Variant.Primary, label: 'Primary'}, + {value: Button.Variant.Secondary, label: 'Secondary'}, + ], + size: [ + {value: Button.Size.Small, label: 'Small'}, + {value: Button.Size.Medium, label: 'Medium'}, + {value: Button.Size.Large, label: 'Large'}, + ], + icon: [{value: undefined, label: ''}, {value: playCircleIcon, label: 'w/ Icon'}], + dataLabel: [{value: undefined, label: ''}, {value: '1:23', label: 'w/ Data Label'}], + }, + (props: any) => ( + + Test + + ) + ) + ); + +storiesOf('Components|Buttons/Button/React/Visual Testing/Delete Button', module) + .addParameters({ + component: DeleteButton, + chromatic: { + disable: false, + }, + }) + .add('States', () => + getButtonStates( + { + size: [ + {value: DeleteButton.Size.Small, label: 'Small'}, + {value: DeleteButton.Size.Medium, label: 'Medium'}, + {value: DeleteButton.Size.Large, label: 'Large'}, + ], + }, + (props: any) => ( + + Test + + ) + ) + ); + +storiesOf('Components|Buttons/Button/React/Visual Testing/Deprecated Button', module) + .addParameters({ + component: DeprecatedButton, + chromatic: { + disable: false, + }, + }) + .add('States', () => + getButtonStates( + { + variant: [ + {value: DeprecatedButton.Variant.Primary, label: 'Primary'}, + {value: DeprecatedButton.Variant.Secondary, label: 'Secondary'}, + {value: DeprecatedButton.Variant.Delete, label: 'Delete'}, + ], + size: [ + {value: Button.Size.Small, label: 'Small'}, + {value: Button.Size.Medium, label: 'Medium'}, + {value: Button.Size.Large, label: 'Large'}, + ], + }, + (props: any) => ( + + Test + + ) + ) + ); + +storiesOf('Components|Buttons/Button/React/Visual Testing/Dropdown Button', module) + .addParameters({ + component: DropdownButton, + chromatic: { + disable: false, + }, + }) + .add('States', () => + getButtonStates( + { variant: [ {value: DropdownButton.Variant.Primary, label: 'Primary'}, {value: DropdownButton.Variant.Secondary, label: 'Secondary'}, @@ -101,176 +164,101 @@ const DropdownButtonStates = () => ( {value: DropdownButton.Size.Medium, label: 'Medium'}, {value: DropdownButton.Size.Large, label: 'Large'}, ], - })} - columnProps={permutateProps( - { - className: [ - {label: 'Default', value: ''}, - {label: 'Hover', value: 'hover'}, - {label: 'Focus', value: 'focus'}, - {label: 'Focus Hover', value: 'focus hover'}, - {label: 'Active', value: 'active'}, - {label: 'Active Hover', value: 'active hover'}, - ], - disabled: [{label: '', value: false}, {label: 'Disabled', value: true}], - }, - props => { - if (props.disabled && !['', 'hover'].includes(props.className)) { - return false; - } - return true; - } - )} - > - {props => ( + icon: [{value: undefined, label: ''}, {value: playCircleIcon, label: 'w/ Icon'}], + dataLabel: [{value: undefined, label: ''}, {value: '1:23', label: 'w/ Data Label'}], + }, + (props: any) => ( Test - )} - - -); + ) + ) + ); -const TextButtonStates = () => ( - - { - return true; - } - )} - columnProps={permutateProps( - { - className: [ - {label: 'Default', value: ''}, - {label: 'Hover', value: 'hover'}, - {label: 'Focus', value: 'focus'}, - {label: 'Focus Hover', value: 'focus hover'}, - {label: 'Active', value: 'active'}, - {label: 'Active Hover', value: 'active hover'}, - ], - disabled: [{label: '', value: false}, {label: 'Disabled', value: true}], - }, - props => { - if (props.disabled && !['', 'hover'].includes(props.className)) { - return false; - } - return true; - } - )} - > - {props => ( - - Test - - )} - - -); - -const IconButtonStates = () => ( - - {[false, true].map(toggled => ( - - Toggled {toggled ? 'On' : 'Off'} - - { - if (props.disabled && !['', 'hover'].includes(props.className)) { - return false; - } - return true; - } - )} - > - {props => ( - - {}} // eslint-disable-line no-empty-function - /> - - )} - - - - ))} - -); - -storiesOf('Components|Buttons/Button/React/Visual Testing/Button', module) +storiesOf('Components|Buttons/Button/React/Visual Testing/Highlight Button', module) .addParameters({ - component: Button, + component: HighlightButton, chromatic: { disable: false, }, }) - .add('States', () => ); + .add('States', () => + getButtonStates( + { + size: [ + {value: Button.Size.Small, label: 'Small'}, + {value: Button.Size.Medium, label: 'Medium'}, + {value: Button.Size.Large, label: 'Large'}, + ], + icon: [{value: undefined, label: ''}, {value: playCircleIcon, label: 'w/ Icon'}], + }, + (props: any) => ( + + Test + + ) + ) + ); -storiesOf('Components|Buttons/Button/React/Visual Testing/Dropdown', module) +storiesOf('Components|Buttons/Button/React/Visual Testing/Outline Button', module) .addParameters({ - component: DropdownButton, + component: Button, chromatic: { disable: false, }, }) - .add('States', () => ); + .add('States', () => + getButtonStates( + { + variant: [ + {value: OutlineButton.Variant.Primary, label: 'Outline Primary'}, + {value: OutlineButton.Variant.Secondary, label: 'Outline Secondary'}, + {value: OutlineButton.Variant.Inverse, label: 'Outline Inverse'}, + ], + size: [ + {value: Button.Size.Small, label: 'Small'}, + {value: Button.Size.Medium, label: 'Medium'}, + {value: Button.Size.Large, label: 'Large'}, + ], + icon: [{value: undefined, label: ''}, {value: playCircleIcon, label: 'w/ Icon'}], + dataLabel: [{value: undefined, label: ''}, {value: '1:23', label: 'w/ Data Label'}], + }, + (props: any) => ( + + Test + + ) + ) + ); -storiesOf('Components|Buttons/Button/React/Visual Testing/Text', module) +storiesOf('Components|Buttons/Button/React/Visual Testing/Text Button', module) .addParameters({ component: TextButton, chromatic: { disable: false, }, }) - .add('States', () => ); + .add('States', () => + getButtonStates( + { + variant: [ + {value: TextButton.Variant.Default, label: 'Default'}, + {value: TextButton.Variant.Inverse, label: 'Inverse'}, + ], + size: [ + {value: TextButton.Size.Small, label: 'Small'}, + {value: TextButton.Size.Medium, label: 'Medium'}, + ], + icon: [{value: undefined, label: ''}, {value: playCircleIcon, label: 'w/ Icon'}], + allCaps: [{value: undefined, label: ''}, {value: true, label: 'All Caps'}], + }, + (props: any) => ( + + Test + + ) + ) + ); storiesOf('Components|Buttons/Button/React/Visual Testing/Icon Button', module) .addParameters({ @@ -279,4 +267,44 @@ storiesOf('Components|Buttons/Button/React/Visual Testing/Icon Button', module) disable: false, }, }) - .add('States', () => ); + .add('States', () => ( + + {[false, true].map(toggled => ( + + Toggled {toggled ? 'On' : 'Off'} + {getButtonStates( + { + variant: [ + {value: IconButton.Variant.Inverse, label: 'Inverse'}, + {value: IconButton.Variant.InverseFilled, label: 'Inverse Filled'}, + {value: IconButton.Variant.Plain, label: 'Plain'}, + {value: IconButton.Variant.Circle, label: 'Circle'}, + {value: IconButton.Variant.CircleFilled, label: 'Circle Filled'}, + {value: IconButton.Variant.Square, label: 'Square'}, + {value: IconButton.Variant.SquareFilled, label: 'Square Filled'}, + ], + size: [ + {value: IconButton.Size.Small, label: 'Small'}, + {value: IconButton.Size.Medium, label: 'Medium'}, + ], + }, + (props: any) => ( + + {}} // eslint-disable-line no-empty-function + /> + + ) + )} + + ))} + + )); diff --git a/modules/button/react/tsconfig.cjs.json b/modules/button/react/tsconfig.cjs.json new file mode 100644 index 0000000000..261641fcc9 --- /dev/null +++ b/modules/button/react/tsconfig.cjs.json @@ -0,0 +1,11 @@ + +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "declaration": true, + "module": "commonjs", + "outDir": "dist/commonjs", + "skipLibCheck": true, + "tsBuildInfoFile": "./.build-info/tsconfig.cjs.tsbuildinfo" + } +} diff --git a/modules/button/react/tsconfig.es6.json b/modules/button/react/tsconfig.es6.json index 601797a826..cdf8edae25 100644 --- a/modules/button/react/tsconfig.es6.json +++ b/modules/button/react/tsconfig.es6.json @@ -1,3 +1,4 @@ + { "extends": "./tsconfig.json", "compilerOptions": { diff --git a/modules/modal/react/stories/stories.tsx b/modules/modal/react/stories/stories.tsx index 0bfaaf399b..ec23aa7ce4 100644 --- a/modules/modal/react/stories/stories.tsx +++ b/modules/modal/react/stories/stories.tsx @@ -3,7 +3,7 @@ import * as React from 'react'; import {storiesOf} from '@storybook/react'; import withReadme from 'storybook-readme/with-readme'; -import {Button} from '../../../button/react'; +import {Button, DeleteButton} from '../../../button/react'; import Modal, {useModal} from '..'; import README from '../README.md'; @@ -22,14 +22,14 @@ const DefaultModalExample = () => { return ( <> - + Delete Item - + Are you sure you'd like to delete the item titled 'My Item'? - + Delete - + Cancel @@ -43,14 +43,12 @@ const UseModalExample = () => { return ( <> - - Delete Item - + Delete Item Are you sure you'd like to delete the item titled 'My Item'? - + Delete - + Cancel @@ -64,9 +62,7 @@ const NoCloseModalExample = () => { return ( <> - - Delete Item - + Delete Item { handleClose={undefined} > Are you sure you'd like to delete the item titled 'My Item'? - + Delete - + Cancel @@ -91,9 +87,7 @@ const CustomFocusModalExample = () => { return ( <> - - Delete Item - + Delete Item { handleClose={undefined} > Are you sure you'd like to delete the item titled 'My Item'? - + Delete - + Cancel
Are you sure you'd like to delete the item titled 'My Item'?