From c9984e3a5a9492b25e7b40bd9cf67945f4cd0fa1 Mon Sep 17 00:00:00 2001 From: Niels Roozemond Date: Tue, 13 Feb 2024 11:10:52 +0100 Subject: [PATCH] feat: Refactor button component (#1065) Co-authored-by: Aram <37216945+alimpens@users.noreply.github.com> --- .../css/src/components/button/button.scss | 70 +++++- packages/react/package.json | 1 - packages/react/src/Button/Button.test.tsx | 8 +- packages/react/src/Button/Button.tsx | 37 +-- pnpm-lock.yaml | 29 --- .../{utrecht => amsterdam}/action.tokens.json | 2 +- .../src/common/utrecht/focus.tokens.json | 7 - .../components/amsterdam/button.tokens.json | 45 +++- .../src/components/utrecht/button.tokens.json | 214 ------------------ .../src/Button/Button.docs.mdx | 4 + .../src/Button/Button.stories.tsx | 25 +- 11 files changed, 148 insertions(+), 294 deletions(-) rename proprietary/tokens/src/common/{utrecht => amsterdam}/action.tokens.json (95%) delete mode 100644 proprietary/tokens/src/common/utrecht/focus.tokens.json delete mode 100644 proprietary/tokens/src/components/utrecht/button.tokens.json diff --git a/packages/css/src/components/button/button.scss b/packages/css/src/components/button/button.scss index da42a5a6c5..904df02a46 100644 --- a/packages/css/src/components/button/button.scss +++ b/packages/css/src/components/button/button.scss @@ -3,42 +3,94 @@ * Copyright (c) 2023 Gemeente Amsterdam */ -@import "../../../node_modules/@utrecht/components/button/css"; - @mixin reset { -webkit-text-size-adjust: 100%; } .amsterdam-button { + border: none; + cursor: var(--amsterdam-button-cursor); + display: inline-flex; + font-family: var(--amsterdam-button-font-family); font-size: var(--amsterdam-button-font-size); + gap: var(--amsterdam-button-gap); line-height: var(--amsterdam-button-line-height); + outline-offset: var(--amsterdam-button-outline-offset); + padding-block-end: var(--amsterdam-button-padding-block-end); + padding-block-start: var(--amsterdam-button-padding-block-start); + padding-inline-end: var(--amsterdam-button-padding-inline-end); + padding-inline-start: var(--amsterdam-button-padding-inline-start); + touch-action: manipulation; + + &:disabled, + &[aria-disabled="true"] { + cursor: var(--amsterdam-button-disabled-cursor); + } @include reset; } +@mixin amsterdam-button-forced-color-mode { + @media screen and (-ms-high-contrast: active), screen and (forced-colors: active) { + border: 2px solid ButtonBorder; // add border because forced colors changes box-shadow to none + } +} + +.amsterdam-button--busy { + cursor: var(--amsterdam-button-busy-cursor); +} + +.amsterdam-button--primary { + background-color: var(--amsterdam-button-primary-background-color); + box-shadow: var(--amsterdam-button-primary-box-shadow); + color: var(--amsterdam-button-primary-color); + + &:disabled, + [aria-disabled="true"] { + background-color: var(--amsterdam-button-primary-disabled-background-color); + box-shadow: var(--amsterdam-button-primary-disabled-box-shadow); + } + + &:hover:not(:disabled, [aria-disabled="true"]) { + background-color: var(--amsterdam-button-primary-hover-background-color); + box-shadow: var(--amsterdam-button-primary-hover-box-shadow); + } + + @include amsterdam-button-forced-color-mode; +} + .amsterdam-button--secondary { + background-color: var(--amsterdam-button-secondary-background-color); box-shadow: var(--amsterdam-button-secondary-box-shadow); + color: var(--amsterdam-button-secondary-color); &:disabled, [aria-disabled="true"] { + background-color: var(--amsterdam-button-secondary-disabled-background-color); box-shadow: var(--amsterdam-button-secondary-disabled-box-shadow); + color: var(--amsterdam-button-secondary-disabled-color); } &:hover:not(:disabled, [aria-disabled="true"]) { box-shadow: var(--amsterdam-button-secondary-hover-box-shadow); + color: var(--amsterdam-button-secondary-hover-color); } -} -.amsterdam-button--secondary:focus:not(:hover, [aria-disabled="true"]) { - box-shadow: var(--amsterdam-button-secondary-focus-box-shadow); + @include amsterdam-button-forced-color-mode; } .amsterdam-button--tertiary { + background-color: var(--amsterdam-button-tertiary-background-color); + color: var(--amsterdam-button-tertiary-color); + + &:disabled, + [aria-disabled="true"] { + background-color: var(--amsterdam-button-tertiary-disabled-background-color); + color: var(--amsterdam-button-tertiary-disabled-color); + } + &:hover:not(:disabled, [aria-disabled="true"]) { box-shadow: var(--amsterdam-button-tertiary-hover-box-shadow); + color: var(--amsterdam-button-tertiary-hover-color); } } - -.amsterdam-button--tertiary:focus:not(:hover, [aria-disabled="true"]) { - box-shadow: none; -} diff --git a/packages/react/package.json b/packages/react/package.json index 972bb6aa68..031cfa2089 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -33,7 +33,6 @@ ], "dependencies": { "@amsterdam/design-system-react-icons": "workspace:*", - "@utrecht/component-library-react": "3.0.0", "clsx": "2.1.0" }, "devDependencies": { diff --git a/packages/react/src/Button/Button.test.tsx b/packages/react/src/Button/Button.test.tsx index 46b35b9ee2..f782144b71 100644 --- a/packages/react/src/Button/Button.test.tsx +++ b/packages/react/src/Button/Button.test.tsx @@ -24,7 +24,7 @@ describe('Button', () => { expect(button).toBeInTheDocument() expect(button).toHaveAttribute('type', 'button') - expect(button).toHaveClass('utrecht-button--primary-action') + expect(button).toHaveClass('amsterdam-button--primary') }) it('renders a button with a specified variant', () => { @@ -49,11 +49,11 @@ describe('Button', () => { }) expect(buttonPrimary).toBeInTheDocument() - expect(buttonPrimary).toHaveClass('utrecht-button--primary-action') + expect(buttonPrimary).toHaveClass('amsterdam-button--primary') expect(buttonSecondary).toBeInTheDocument() - expect(buttonSecondary).toHaveClass('utrecht-button--secondary-action') + expect(buttonSecondary).toHaveClass('amsterdam-button--secondary') expect(buttonTertiary).toBeInTheDocument() - expect(buttonTertiary).toHaveClass('utrecht-button--subtle') + expect(buttonTertiary).toHaveClass('amsterdam-button--tertiary') }) it('renders a disabled button with a specified variant', () => { diff --git a/packages/react/src/Button/Button.tsx b/packages/react/src/Button/Button.tsx index 43859e9d68..e450f77625 100644 --- a/packages/react/src/Button/Button.tsx +++ b/packages/react/src/Button/Button.tsx @@ -1,47 +1,34 @@ /** * @license EUPL-1.2+ - * Copyright (c) 2021 Robbert Broersma - * Copyright (c) 2023 Gemeente Amsterdam + * Copyright (c) 2024 Gemeente Amsterdam */ -import { Button as CommunityButton } from '@utrecht/component-library-react' import clsx from 'clsx' import { forwardRef } from 'react' import type { ButtonHTMLAttributes, ForwardedRef, PropsWithChildren } from 'react' export type ButtonProps = { variant?: 'primary' | 'secondary' | 'tertiary' + /** Render the button in a busy state to indicate something has to finish loading */ + busy?: boolean } & PropsWithChildren> -type CommunityButtonAppearance = 'primary-action-button' | 'secondary-action-button' | 'subtle-button' - -function getAppearance(variant: ButtonProps['variant']): CommunityButtonAppearance { - switch (variant) { - case 'secondary': - return 'secondary-action-button' - case 'tertiary': - return 'subtle-button' - default: - return 'primary-action-button' - } -} - export const Button = forwardRef( - ({ children, disabled, variant = 'primary', ...restProps }: ButtonProps, ref: ForwardedRef) => { + ( + { children, type, disabled, busy, variant = 'primary', ...restProps }: ButtonProps, + ref: ForwardedRef, + ) => { return ( - {children} - + ) }, ) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d14e140ec4..adcb809b76 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -114,9 +114,6 @@ importers: '@amsterdam/design-system-react-icons': specifier: workspace:* version: link:../../proprietary/react-icons - '@utrecht/component-library-react': - specifier: 3.0.0 - version: 3.0.0(react-dom@18.2.0)(react@18.2.0) clsx: specifier: 2.1.0 version: 2.1.0 @@ -5730,28 +5727,6 @@ packages: resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} dev: true - /@utrecht/component-library-react@3.0.0(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-7ftpjtQ6ygIMqlB+pWQZAT7psFo1oXkbBSlbVw/uMV5OABRDX5t3V2lN/UDTPjdMRtcj8ZBYl19zPcfkV+Gc5w==} - peerDependencies: - date-fns: ^2.30.0 - react: '18' - react-dom: '18' - react-vega: ^7.6.0 - vega: ^5.25.0 - peerDependenciesMeta: - date-fns: - optional: true - react-vega: - optional: true - vega: - optional: true - dependencies: - clsx: 1.2.1 - lodash.chunk: 4.2.0 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - /@utrecht/components@3.0.0: resolution: {integrity: sha512-TO39Ti6Af50l+I2gt4S3iacGVuVoxBK/+G/bsK9wvii53fO35NYJul7xB98/IYSBmewaN4GtmyWGphVDdFMt1A==} dependencies: @@ -11115,10 +11090,6 @@ packages: p-locate: 5.0.0 dev: true - /lodash.chunk@4.2.0: - resolution: {integrity: sha512-ZzydJKfUHJwHa+hF5X66zLFCBrWn5GeF28OHEr4WVWtNDXlQ/IjWKPBiikqKo2ne0+v6JgCgJ0GzJp8k8bHC7w==} - dev: false - /lodash.debounce@4.0.8: resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} dev: true diff --git a/proprietary/tokens/src/common/utrecht/action.tokens.json b/proprietary/tokens/src/common/amsterdam/action.tokens.json similarity index 95% rename from proprietary/tokens/src/common/utrecht/action.tokens.json rename to proprietary/tokens/src/common/amsterdam/action.tokens.json index 2343257e6d..d726b9b7f6 100644 --- a/proprietary/tokens/src/common/utrecht/action.tokens.json +++ b/proprietary/tokens/src/common/amsterdam/action.tokens.json @@ -1,5 +1,5 @@ { - "utrecht": { + "amsterdam": { "action": { "activate": { "cursor": { "value": "pointer" } }, "busy": { "cursor": { "value": "wait" } }, diff --git a/proprietary/tokens/src/common/utrecht/focus.tokens.json b/proprietary/tokens/src/common/utrecht/focus.tokens.json deleted file mode 100644 index b4d2fe2f06..0000000000 --- a/proprietary/tokens/src/common/utrecht/focus.tokens.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "utrecht": { - "focus": { - "outline-offset": { "value": "2px" } - } - } -} diff --git a/proprietary/tokens/src/components/amsterdam/button.tokens.json b/proprietary/tokens/src/components/amsterdam/button.tokens.json index 0ebe731227..0288aecbdc 100644 --- a/proprietary/tokens/src/components/amsterdam/button.tokens.json +++ b/proprietary/tokens/src/components/amsterdam/button.tokens.json @@ -1,23 +1,62 @@ { "amsterdam": { "button": { + "cursor": { "value": "{amsterdam.action.activate.cursor}" }, + "font-family": { "value": "{amsterdam.typography.font-family}" }, "font-size": { "value": "{amsterdam.typography.text-level.5.font-size}" }, "line-height": { "value": "{amsterdam.typography.text-level.5.line-height}" }, + "gap": { "value": "1rem" }, + "padding-inline-start": { "value": "1rem" }, + "padding-inline-end": { "value": "1rem" }, + "padding-block-start": { "value": "0.5rem" }, + "padding-block-end": { "value": "0.5rem" }, + "outline-offset": { "value": "{amsterdam.focus.outline-offset}" }, + "busy": { + "cursor": { "value": "{amsterdam.action.busy.cursor}" } + }, + "disabled": { + "cursor": { "value": "{amsterdam.action.disabled.cursor}" } + }, + "primary": { + "background-color": { "value": "{amsterdam.color.primary-blue}" }, + "box-shadow": { "value": "inset 0 0 0 2px {amsterdam.color.primary-blue}" }, + "color": { "value": "{amsterdam.color.primary-white}" }, + "disabled": { + "background-color": { "value": "{amsterdam.color.neutral-grey2}" }, + "box-shadow": { "value": "inset 0 0 0 2px {amsterdam.color.neutral-grey2}" } + }, + "hover": { + "background-color": { "value": "{amsterdam.color.dark-blue}" }, + "box-shadow": { "value": "inset 0 0 0 2px {amsterdam.color.dark-blue}" } + } + }, "secondary": { + "background-color": { "value": "{amsterdam.color.primary-white}" }, + "color": { "value": "{amsterdam.color.primary-blue}" }, "box-shadow": { "value": "inset 0 0 0 2px {amsterdam.color.primary-blue}" }, "hover": { - "box-shadow": { "value": "inset 0 0 0 3px {amsterdam.color.dark-blue}" } + "box-shadow": { "value": "inset 0 0 0 3px {amsterdam.color.dark-blue}" }, + "color": { "value": "{amsterdam.color.dark-blue}" } }, "disabled": { - "box-shadow": { "value": "inset 0 0 0 2px {amsterdam.color.neutral-grey2}" } + "background-color": { "value": "{amsterdam.color.primary-white}" }, + "box-shadow": { "value": "inset 0 0 0 2px {amsterdam.color.neutral-grey2}" }, + "color": { "value": "{amsterdam.color.neutral-grey2}" } }, "focus": { "box-shadow": { "value": "inset 0 0 0 2px {amsterdam.color.primary-blue}" } } }, "tertiary": { + "background-color": { "value": "transparent" }, + "color": { "value": "{amsterdam.color.primary-blue}" }, "hover": { - "box-shadow": { "value": "inset 0 0 0 2px {amsterdam.color.dark-blue}" } + "box-shadow": { "value": "inset 0 0 0 2px {amsterdam.color.dark-blue}" }, + "color": { "value": "{amsterdam.color.dark-blue}" } + }, + "disabled": { + "background-color": { "value": "transparent" }, + "color": { "value": "{amsterdam.color.neutral-grey2}" } } } } diff --git a/proprietary/tokens/src/components/utrecht/button.tokens.json b/proprietary/tokens/src/components/utrecht/button.tokens.json deleted file mode 100644 index 3f65bac80f..0000000000 --- a/proprietary/tokens/src/components/utrecht/button.tokens.json +++ /dev/null @@ -1,214 +0,0 @@ -{ - "utrecht": { - "button": { - "background-color": { "value": "{amsterdam.color.primary-blue}" }, - "border-color": {}, - "border-radius": {}, - "border-width": {}, - "color": { "value": "{amsterdam.color.primary-white}" }, - "font-family": { "value": "{amsterdam.typography.font-family}" }, - "font-size": {}, - "line-height": {}, - "padding-inline-start": { "value": "{amsterdam.spacing.inset.md}" }, - "padding-inline-end": { "value": "{amsterdam.spacing.inset.md}" }, - "padding-block-start": { "value": "{amsterdam.spacing.inset.sm}" }, - "padding-block-end": { "value": "{amsterdam.spacing.inset.sm}" }, - "margin-inline-start": {}, - "margin-inline-end": {}, - "margin-block-start": {}, - "margin-block-end": {}, - "icon": { - "gap": {} - }, - "disabled": { - "background-color": { "value": "{amsterdam.color.neutral-grey2}" }, - "color": { "value": "{amsterdam.color.primary-white}" }, - "border-color": {} - }, - "hover": { - "background-color": {} - }, - "focus": { - "scale": {} - }, - "primary-action": { - "background-color": { "value": "{amsterdam.color.primary-blue}" }, - "border-color": {}, - "border-width": {}, - "color": { "value": "{amsterdam.color.primary-white}" }, - "disabled": { - "background-color": { "value": "{amsterdam.color.neutral-grey2}" }, - "color": {}, - "border-color": {} - }, - "hover": { - "background-color": { "value": "{amsterdam.color.dark-blue}" }, - "border-color": {}, - "color": {} - }, - "focus": { - "background-color": {}, - "border-color": {}, - "border-width": {} - }, - "ready": { - "background-color": {}, - "border-color": {}, - "color": {}, - "disabled": { - "border-color": {} - }, - "hover": { - "background-color": {}, - "border-color": {}, - "color": {} - }, - "focus": { - "background-color": {}, - "border-color": {} - } - }, - "warning": { - "background-color": {}, - "border-color": {}, - "color": {}, - "disabled": { - "border-color": {} - }, - "hover": { - "background-color": {}, - "border-color": {}, - "color": {} - }, - "focus": { - "background-color": {}, - "border-color": {} - } - }, - "danger": { - "background-color": {}, - "border-color": {}, - "color": {}, - "disabled": { - "border-color": {} - }, - "hover": { - "background-color": {}, - "border-color": {}, - "color": {} - }, - "focus": { - "background-color": {}, - "border-color": {} - } - } - }, - "secondary-action": { - "background-color": { "value": "{amsterdam.color.primary-white}" }, - "color": { "value": "{amsterdam.color.primary-blue}" }, - "border-color": {}, - "border-width": {}, - "hover": { - "background-color": {}, - "color": { "value": "{amsterdam.color.dark-blue}" }, - "border-color": {} - }, - "disabled": { - "background-color": { "value": "{amsterdam.color.primary-white}" }, - "color": { "value": "{amsterdam.color.neutral-grey2}" }, - "border-color": {} - }, - "danger": { - "background-color": {}, - "border-color": {}, - "color": {}, - "disabled": { - "border-color": {} - }, - "hover": { - "background-color": {}, - "border-color": {}, - "color": {} - }, - "focus": { - "background-color": {}, - "border-color": {} - } - }, - "warning": { - "background-color": {}, - "border-color": {}, - "color": {}, - "disabled": { - "border-color": {} - }, - "hover": { - "background-color": {}, - "border-color": {}, - "color": {} - }, - "focus": { - "background-color": {}, - "border-color": {} - } - }, - "ready": { - "background-color": {}, - "border-color": {}, - "color": {}, - "disabled": { - "border-color": {} - }, - "hover": { - "background-color": {}, - "border-color": {}, - "color": {} - }, - "focus": { - "background-color": {}, - "border-color": {} - } - } - }, - "subtle": { - "background-color": { "value": "transparent" }, - "border-color": {}, - "border-width": {}, - "color": { "value": "{amsterdam.color.primary-blue}" }, - "font-weight": {}, - "hover": { - "background-color": {}, - "color": { "value": "{amsterdam.color.dark-blue}" }, - "border-color": {} - }, - "focus": { - "background-color": {}, - "color": { "value": "{amsterdam.color.dark-blue}" }, - "border-color": {} - }, - "disabled": { - "background-color": { "value": "transparent" }, - "color": { "value": "{amsterdam.color.neutral-grey2}" } - }, - "danger": { - "color": {}, - "hover": { - "color": {} - }, - "focus": { - "color": {} - } - }, - "ready": { - "color": {}, - "hover": { - "color": {} - }, - "focus": { - "color": {} - } - } - } - } - } -} diff --git a/storybook/storybook-react/src/Button/Button.docs.mdx b/storybook/storybook-react/src/Button/Button.docs.mdx index a503ec4333..785cba5c7a 100644 --- a/storybook/storybook-react/src/Button/Button.docs.mdx +++ b/storybook/storybook-react/src/Button/Button.docs.mdx @@ -34,3 +34,7 @@ It is possible to use more than 1 secondary button. Use tertiary buttons for unimportant calls to action – as many as necessary. + +### Button with an icon + + diff --git a/storybook/storybook-react/src/Button/Button.stories.tsx b/storybook/storybook-react/src/Button/Button.stories.tsx index b4ecb9c145..55c7b3d0dc 100644 --- a/storybook/storybook-react/src/Button/Button.stories.tsx +++ b/storybook/storybook-react/src/Button/Button.stories.tsx @@ -3,7 +3,8 @@ * Copyright (c) 2023 Gemeente Amsterdam */ -import { Button } from '@amsterdam/design-system-react' +import { Button, Icon } from '@amsterdam/design-system-react' +import { ShareIcon } from '@amsterdam/design-system-react-icons' import { Meta, StoryObj } from '@storybook/react' const meta = { @@ -11,7 +12,23 @@ const meta = { component: Button, args: { children: 'Default', + variant: 'primary', disabled: false, + busy: false, + }, + argTypes: { + children: { + table: { + disable: true, + }, + }, + disabled: { + control: 'boolean', + }, + variant: { + control: 'select', + options: ['primary', 'secondary', 'tertiary'], + }, }, } satisfies Meta @@ -38,3 +55,9 @@ export const Tertiary: Story = { variant: 'tertiary', }, } + +export const ButtonWithAnIcon: Story = { + args: { + children: ['Primary', ], + }, +}