From 4b2dbeb18c79175bc0bfe0cf50a0e9d0429544d6 Mon Sep 17 00:00:00 2001 From: Danny Banks Date: Mon, 5 Dec 2022 11:15:41 -0800 Subject: [PATCH] feat(button): add warning and destructive variations (#3133) --- .changeset/popular-plums-mate.md | 6 + .../__snapshots__/props-table.test.ts.snap | 10 +- .../[platform]/components/button/demo.tsx | 2 + .../[platform]/components/button/react.mdx | 6 +- .../Button/__tests__/Button.test.tsx | 15 +++ packages/react/src/primitives/types/button.ts | 7 +- .../src/theme/__tests__/defaultTheme.test.ts | 41 +++++++ .../ui/src/theme/__tests__/overrides.test.ts | 41 +++++++ .../ui/src/theme/css/component/button.scss | 106 ++++++++++++++++++ .../ui/src/theme/tokens/components/button.ts | 69 ++++++++++++ 10 files changed, 296 insertions(+), 7 deletions(-) create mode 100644 .changeset/popular-plums-mate.md diff --git a/.changeset/popular-plums-mate.md b/.changeset/popular-plums-mate.md new file mode 100644 index 00000000000..2c1afd0d80a --- /dev/null +++ b/.changeset/popular-plums-mate.md @@ -0,0 +1,6 @@ +--- +"@aws-amplify/ui-react": minor +"@aws-amplify/ui": minor +--- + +feat(button): add `warning` and `destructive` variations to the React Button component diff --git a/docs/src/data/test/__snapshots__/props-table.test.ts.snap b/docs/src/data/test/__snapshots__/props-table.test.ts.snap index cd49ea028d7..f905500f805 100644 --- a/docs/src/data/test/__snapshots__/props-table.test.ts.snap +++ b/docs/src/data/test/__snapshots__/props-table.test.ts.snap @@ -982,7 +982,7 @@ exports[`Props Table 1`] = ` }, \\"variation\\": { \\"name\\": \\"variation\\", - \\"type\\": \\"'primary' | 'link' | 'menu'\\", + \\"type\\": \\"| 'primary'\\\\n | 'link'\\\\n | 'menu'\\\\n | 'warning'\\\\n | 'destructive'\\", \\"description\\": \\"Changes the visual weight of the button.\\", \\"category\\": \\"ButtonProps\\", \\"isOptional\\": true @@ -4432,7 +4432,7 @@ exports[`Props Table 1`] = ` }, \\"variation\\": { \\"name\\": \\"variation\\", - \\"type\\": \\"'primary' | 'link' | 'menu'\\", + \\"type\\": \\"| 'primary'\\\\n | 'link'\\\\n | 'menu'\\\\n | 'warning'\\\\n | 'destructive'\\", \\"description\\": \\"Changes the visual weight of the button.\\", \\"category\\": \\"ButtonProps\\", \\"isOptional\\": true @@ -4715,7 +4715,7 @@ exports[`Props Table 1`] = ` }, \\"variation\\": { \\"name\\": \\"variation\\", - \\"type\\": \\"'primary' | 'link' | 'menu'\\", + \\"type\\": \\"| 'primary'\\\\n | 'link'\\\\n | 'menu'\\\\n | 'warning'\\\\n | 'destructive'\\", \\"description\\": \\"Changes the visual weight of the button.\\", \\"category\\": \\"ButtonProps\\", \\"isOptional\\": true @@ -11420,7 +11420,7 @@ exports[`Props Table 1`] = ` }, \\"variation\\": { \\"name\\": \\"variation\\", - \\"type\\": \\"'primary' | 'link' | 'menu'\\", + \\"type\\": \\"| 'primary'\\\\n | 'link'\\\\n | 'menu'\\\\n | 'warning'\\\\n | 'destructive'\\", \\"description\\": \\"Changes the visual weight of the button.\\", \\"category\\": \\"ButtonProps\\", \\"isOptional\\": true @@ -11724,7 +11724,7 @@ exports[`Props Table 1`] = ` }, \\"variation\\": { \\"name\\": \\"variation\\", - \\"type\\": \\"'primary' | 'link' | 'menu'\\", + \\"type\\": \\"| 'primary'\\\\n | 'link'\\\\n | 'menu'\\\\n | 'warning'\\\\n | 'destructive'\\", \\"description\\": \\"Changes the visual weight of the button.\\", \\"category\\": \\"ButtonProps\\", \\"isOptional\\": true diff --git a/docs/src/pages/[platform]/components/button/demo.tsx b/docs/src/pages/[platform]/components/button/demo.tsx index f97c6dcb54e..46cfd7b40ab 100644 --- a/docs/src/pages/[platform]/components/button/demo.tsx +++ b/docs/src/pages/[platform]/components/button/demo.tsx @@ -48,6 +48,8 @@ const PropControls = (props) => { + + + + @@ -92,6 +94,8 @@ Use the `variation` prop to change the Button variation. Available options are ` + + ``` diff --git a/packages/react/src/primitives/Button/__tests__/Button.test.tsx b/packages/react/src/primitives/Button/__tests__/Button.test.tsx index 75e6ac0c032..026928bf3c9 100644 --- a/packages/react/src/primitives/Button/__tests__/Button.test.tsx +++ b/packages/react/src/primitives/Button/__tests__/Button.test.tsx @@ -15,14 +15,29 @@ describe('Button test suite', () => { + + + ); const primary = await screen.findByTestId('primary'); const link = await screen.findByTestId('link'); + const menu = await screen.findByTestId('menu'); + const warning = await screen.findByTestId('warning'); + const destructive = await screen.findByTestId('destructive'); expect(primary.classList).toContain('amplify-button--primary'); expect(link.classList).toContain('amplify-button--link'); + expect(menu.classList).toContain('amplify-button--menu'); + expect(warning.classList).toContain('amplify-button--warning'); + expect(destructive.classList).toContain('amplify-button--destructive'); }); it('should add the disabled class with the disabled attribute', async () => { diff --git a/packages/react/src/primitives/types/button.ts b/packages/react/src/primitives/types/button.ts index 5567d267923..822d0efcbd5 100644 --- a/packages/react/src/primitives/types/button.ts +++ b/packages/react/src/primitives/types/button.ts @@ -4,7 +4,12 @@ import { FlexContainerStyleProps } from './flex'; export type ButtonSizes = Sizes; export type ButtonTypes = 'button' | 'reset' | 'submit'; -export type ButtonVariations = 'primary' | 'link' | 'menu'; +export type ButtonVariations = + | 'primary' + | 'link' + | 'menu' + | 'warning' + | 'destructive'; export interface ButtonProps extends ViewProps, FlexContainerStyleProps { /** diff --git a/packages/ui/src/theme/__tests__/defaultTheme.test.ts b/packages/ui/src/theme/__tests__/defaultTheme.test.ts index 722836b6151..4ab206315de 100644 --- a/packages/ui/src/theme/__tests__/defaultTheme.test.ts +++ b/packages/ui/src/theme/__tests__/defaultTheme.test.ts @@ -182,6 +182,47 @@ describe('@aws-amplify/ui', () => { --amplify-components-button-link-loading-border-color: transparent; --amplify-components-button-link-loading-background-color: transparent; --amplify-components-button-link-loading-color: var(--amplify-colors-font-disabled); + --amplify-components-button-warning-background-color: transparent; + --amplify-components-button-warning-border-color: var(--amplify-colors-red-60); + --amplify-components-button-warning-border-width: var(--amplify-border-widths-small); + --amplify-components-button-warning-color: var(--amplify-colors-red-60); + --amplify-components-button-warning-hover-border-color: var(--amplify-colors-red-80); + --amplify-components-button-warning-hover-background-color: var(--amplify-colors-red-10); + --amplify-components-button-warning-hover-color: var(--amplify-colors-font-error); + --amplify-components-button-warning-focus-border-color: var(--amplify-colors-red-80); + --amplify-components-button-warning-focus-background-color: var(--amplify-colors-red-10); + --amplify-components-button-warning-focus-color: var(--amplify-colors-red-80); + --amplify-components-button-warning-focus-box-shadow: var(--amplify-components-fieldcontrol-error-focus-box-shadow); + --amplify-components-button-warning-active-border-color: var(--amplify-colors-red-100); + --amplify-components-button-warning-active-background-color: var(--amplify-colors-red-20); + --amplify-components-button-warning-active-color: var(--amplify-colors-red-100); + --amplify-components-button-warning-disabled-border-color: var(--amplify-colors-border-tertiary); + --amplify-components-button-warning-disabled-background-color: transparent; + --amplify-components-button-warning-disabled-color: var(--amplify-colors-font-disabled); + --amplify-components-button-warning-loading-border-color: var(--amplify-colors-border-tertiary); + --amplify-components-button-warning-loading-background-color: transparent; + --amplify-components-button-warning-loading-color: var(--amplify-colors-font-disabled); + --amplify-components-button-destructive-border-color: transparent; + --amplify-components-button-destructive-border-width: var(--amplify-border-widths-small); + --amplify-components-button-destructive-border-style: solid; + --amplify-components-button-destructive-background-color: var(--amplify-colors-red-60); + --amplify-components-button-destructive-color: var(--amplify-colors-font-inverse); + --amplify-components-button-destructive-disabled-border-color: transparent; + --amplify-components-button-destructive-disabled-background-color: var(--amplify-colors-background-disabled); + --amplify-components-button-destructive-disabled-color: var(--amplify-colors-font-disabled); + --amplify-components-button-destructive-loading-border-color: transparent; + --amplify-components-button-destructive-loading-background-color: var(--amplify-colors-background-disabled); + --amplify-components-button-destructive-loading-color: var(--amplify-colors-font-disabled); + --amplify-components-button-destructive-hover-border-color: transparent; + --amplify-components-button-destructive-hover-background-color: var(--amplify-colors-red-80); + --amplify-components-button-destructive-hover-color: var(--amplify-colors-font-inverse); + --amplify-components-button-destructive-focus-border-color: transparent; + --amplify-components-button-destructive-focus-background-color: var(--amplify-colors-red-80); + --amplify-components-button-destructive-focus-color: var(--amplify-colors-font-inverse); + --amplify-components-button-destructive-focus-box-shadow: var(--amplify-components-fieldcontrol-error-focus-box-shadow); + --amplify-components-button-destructive-active-border-color: transparent; + --amplify-components-button-destructive-active-background-color: var(--amplify-colors-red-100); + --amplify-components-button-destructive-active-color: var(--amplify-colors-font-inverse); --amplify-components-button-small-font-size: var(--amplify-components-fieldcontrol-small-font-size); --amplify-components-button-small-padding-block-start: var(--amplify-components-fieldcontrol-small-padding-block-start); --amplify-components-button-small-padding-block-end: var(--amplify-components-fieldcontrol-small-padding-block-end); diff --git a/packages/ui/src/theme/__tests__/overrides.test.ts b/packages/ui/src/theme/__tests__/overrides.test.ts index f46f49594de..2abffc7c99d 100644 --- a/packages/ui/src/theme/__tests__/overrides.test.ts +++ b/packages/ui/src/theme/__tests__/overrides.test.ts @@ -216,6 +216,47 @@ describe('@aws-amplify/ui', () => { --amplify-components-button-link-loading-border-color: transparent; --amplify-components-button-link-loading-background-color: transparent; --amplify-components-button-link-loading-color: var(--amplify-colors-font-disabled); + --amplify-components-button-warning-background-color: transparent; + --amplify-components-button-warning-border-color: var(--amplify-colors-red-60); + --amplify-components-button-warning-border-width: var(--amplify-border-widths-small); + --amplify-components-button-warning-color: var(--amplify-colors-red-60); + --amplify-components-button-warning-hover-border-color: var(--amplify-colors-red-80); + --amplify-components-button-warning-hover-background-color: var(--amplify-colors-red-10); + --amplify-components-button-warning-hover-color: var(--amplify-colors-font-error); + --amplify-components-button-warning-focus-border-color: var(--amplify-colors-red-80); + --amplify-components-button-warning-focus-background-color: var(--amplify-colors-red-10); + --amplify-components-button-warning-focus-color: var(--amplify-colors-red-80); + --amplify-components-button-warning-focus-box-shadow: var(--amplify-components-fieldcontrol-error-focus-box-shadow); + --amplify-components-button-warning-active-border-color: var(--amplify-colors-red-100); + --amplify-components-button-warning-active-background-color: var(--amplify-colors-red-20); + --amplify-components-button-warning-active-color: var(--amplify-colors-red-100); + --amplify-components-button-warning-disabled-border-color: var(--amplify-colors-border-tertiary); + --amplify-components-button-warning-disabled-background-color: transparent; + --amplify-components-button-warning-disabled-color: var(--amplify-colors-font-disabled); + --amplify-components-button-warning-loading-border-color: var(--amplify-colors-border-tertiary); + --amplify-components-button-warning-loading-background-color: transparent; + --amplify-components-button-warning-loading-color: var(--amplify-colors-font-disabled); + --amplify-components-button-destructive-border-color: transparent; + --amplify-components-button-destructive-border-width: var(--amplify-border-widths-small); + --amplify-components-button-destructive-border-style: solid; + --amplify-components-button-destructive-background-color: var(--amplify-colors-red-60); + --amplify-components-button-destructive-color: var(--amplify-colors-font-inverse); + --amplify-components-button-destructive-disabled-border-color: transparent; + --amplify-components-button-destructive-disabled-background-color: var(--amplify-colors-background-disabled); + --amplify-components-button-destructive-disabled-color: var(--amplify-colors-font-disabled); + --amplify-components-button-destructive-loading-border-color: transparent; + --amplify-components-button-destructive-loading-background-color: var(--amplify-colors-background-disabled); + --amplify-components-button-destructive-loading-color: var(--amplify-colors-font-disabled); + --amplify-components-button-destructive-hover-border-color: transparent; + --amplify-components-button-destructive-hover-background-color: var(--amplify-colors-red-80); + --amplify-components-button-destructive-hover-color: var(--amplify-colors-font-inverse); + --amplify-components-button-destructive-focus-border-color: transparent; + --amplify-components-button-destructive-focus-background-color: var(--amplify-colors-red-80); + --amplify-components-button-destructive-focus-color: var(--amplify-colors-font-inverse); + --amplify-components-button-destructive-focus-box-shadow: var(--amplify-components-fieldcontrol-error-focus-box-shadow); + --amplify-components-button-destructive-active-border-color: transparent; + --amplify-components-button-destructive-active-background-color: var(--amplify-colors-red-100); + --amplify-components-button-destructive-active-color: var(--amplify-colors-font-inverse); --amplify-components-button-small-font-size: var(--amplify-components-fieldcontrol-small-font-size); --amplify-components-button-small-padding-block-start: var(--amplify-components-fieldcontrol-small-padding-block-start); --amplify-components-button-small-padding-block-end: var(--amplify-components-fieldcontrol-small-padding-block-end); diff --git a/packages/ui/src/theme/css/component/button.scss b/packages/ui/src/theme/css/component/button.scss index f0013601e99..d6df024ffe7 100644 --- a/packages/ui/src/theme/css/component/button.scss +++ b/packages/ui/src/theme/css/component/button.scss @@ -203,6 +203,112 @@ color: var(--amplify-components-button-link-active-color); } } + + &--destructive { + border-width: var(--amplify-components-button-destructive-border-width); + background-color: var(--amplify-components-button-destructive-background-color); + border-color: var(--amplify-components-button-destructive-border-color); + color: var(--amplify-components-button-destructive-color); + + &:hover { + background-color: var( + --amplify-components-button-destructive-hover-background-color + ); + border-color: var(--amplify-components-button-destructive-hover-border-color); + color: var(--amplify-components-button-destructive-hover-color); + } + + &:focus { + background-color: var( + --amplify-components-button-destructive-focus-background-color + ); + border-color: var(--amplify-components-button-destructive-focus-border-color); + color: var(--amplify-components-button-destructive-focus-color); + box-shadow: var(--amplify-components-button-destructive-focus-box-shadow); + } + + &:active { + background-color: var( + --amplify-components-button-destructive-active-background-color + ); + border-color: var( + --amplify-components-button-destructive-active-border-color + ); + color: var(--amplify-components-button-destructive-active-color); + } + + --amplify-internal-button-disabled-border-color: var( + --amplify-components-button-destructive-disabled-border-color + ); + --amplify-internal-button-disabled-background-color: var( + --amplify-components-button-destructive-disabled-background-color + ); + --amplify-internal-button-disabled-color: var( + --amplify-components-button-destructive-disabled-color + ); + --amplify-internal-button-loading-background-color: var( + --amplify-components-button-destructive-loading-background-color + ); + --amplify-internal-button-loading-border-color: var( + --amplify-components-button-destructive-loading-border-color + ); + --amplify-internal-button-loading-color: var( + --amplify-components-button-destructive-loading-color + ); + } + + &--warning { + background-color: var(--amplify-components-button-warning-background-color); + border-color: var(--amplify-components-button-warning-border-color); + border-width: var(--amplify-components-button-warning-border-width); + color: var(--amplify-components-button-warning-color); + + --amplify-internal-button-disabled-text-decoration: none; + --amplify-internal-button-disabled-border-color: var( + --amplify-components-button-warning-disabled-border-color + ); + --amplify-internal-button-disabled-background-color: var( + --amplify-components-button-warning-disabled-background-color + ); + --amplify-internal-button-disabled-color: var( + --amplify-components-button-warning-disabled-color + ); + --amplify-internal-button-loading-background-color: var( + --amplify-components-button-warning-loading-background-color + ); + --amplify-internal-button-loading-border-color: var( + --amplify-components-button-warning-loading-border-color + ); + --amplify-internal-button-loading-color: var( + --amplify-components-button-warning-loading-color + ); + --amplify-internal-button-loading-text-decoration: none; + + &:hover { + background-color: var( + --amplify-components-button-warning-hover-background-color + ); + border-color: var(--amplify-components-button-warning-hover-border-color); + color: var(--amplify-components-button-warning-hover-color); + } + + &:focus { + background-color: var( + --amplify-components-button-warning-focus-background-color + ); + border-color: var(--amplify-components-button-warning-focus-border-color); + color: var(--amplify-components-button-warning-focus-color); + box-shadow: var(--amplify-components-button-warning-focus-box-shadow); + } + + &:active { + background-color: var( + --amplify-components-button-warning-active-background-color + ); + border-color: var(--amplify-components-button-warning-active-border-color); + color: var(--amplify-components-button-warning-active-color); + } + } &--small { font-size: var(--amplify-components-button-small-font-size); diff --git a/packages/ui/src/theme/tokens/components/button.ts b/packages/ui/src/theme/tokens/components/button.ts index f60a06f59ab..5eabae4f2a2 100644 --- a/packages/ui/src/theme/tokens/components/button.ts +++ b/packages/ui/src/theme/tokens/components/button.ts @@ -72,6 +72,8 @@ export type ButtonTokens = _loading?: StateTokens; _disabled?: StateTokens; primary?: PrimaryVariationTokens; + warning?: LinkVariationTokens; + destructive?: PrimaryVariationTokens; menu?: MenuVariationTokens; link?: LinkVariationTokens; small?: ButtonSizeTokens; @@ -224,6 +226,73 @@ export const button: Required> = { }, }, + warning: { + backgroundColor: { value: 'transparent' }, + borderColor: { value: '{colors.red.60}' }, + borderWidth: { value: '{borderWidths.small}' }, + color: { value: '{colors.red.60}' }, + _hover: { + borderColor: { value: '{colors.red.80}' }, + backgroundColor: { value: '{colors.red.10}' }, + color: { value: '{colors.font.error}' }, + }, + _focus: { + borderColor: { value: '{colors.red.80}' }, + backgroundColor: { value: '{colors.red.10}' }, + color: { value: '{colors.red.80}' }, + boxShadow: { value: '{components.fieldcontrol._error._focus.boxShadow}' }, + }, + _active: { + borderColor: { value: '{colors.red.100}' }, + backgroundColor: { value: '{colors.red.20}' }, + color: { value: '{colors.red.100}' }, + }, + _disabled: { + borderColor: { value: '{colors.border.tertiary}' }, + backgroundColor: { value: 'transparent' }, + color: { value: '{colors.font.disabled}' }, + }, + _loading: { + borderColor: { value: '{colors.border.tertiary}' }, + backgroundColor: { value: 'transparent' }, + color: { value: '{colors.font.disabled}' }, + }, + }, + + destructive: { + borderColor: { value: 'transparent' }, + borderWidth: { value: '{borderWidths.small}' }, + borderStyle: { value: 'solid' }, + backgroundColor: { value: '{colors.red.60}' }, + color: { value: '{colors.font.inverse}' }, + _disabled: { + borderColor: { value: 'transparent' }, + backgroundColor: { value: '{colors.background.disabled}' }, + color: { value: '{colors.font.disabled}' }, + }, + _loading: { + borderColor: { value: 'transparent' }, + backgroundColor: { value: '{colors.background.disabled}' }, + color: { value: '{colors.font.disabled}' }, + }, + _hover: { + borderColor: { value: 'transparent' }, + backgroundColor: { value: '{colors.red.80}' }, + color: { value: '{colors.font.inverse}' }, + }, + _focus: { + borderColor: { value: 'transparent' }, + backgroundColor: { value: '{colors.red.80}' }, + color: { value: '{colors.font.inverse}' }, + boxShadow: { value: '{components.fieldcontrol._error._focus.boxShadow}' }, + }, + _active: { + borderColor: { value: 'transparent' }, + backgroundColor: { value: '{colors.red.100}' }, + color: { value: '{colors.font.inverse}' }, + }, + }, + // sizes small: { fontSize: { value: '{components.fieldcontrol.small.fontSize.value}' },