diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 1b7cdcc52..9fdab16ad 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -7,6 +7,36 @@ SDK Changelog .. warning:: SDK 1.6.0 requires at least version 2.4.0 of the Open Formulieren API. +Updating stylesheets +-------------------- + +In this version, the button component has been refactored to use the Utrecht button components. +In order to maintain the same style as in previous versions, the following Utrecht design tokens +should be set: + +* ``--utrecht-button-primary-action-focus-border-color`` has ``#000000`` (black) in the Open Forms + theme. +* ``--utrecht-button-primary-action-danger-focus-border-color`` has ``#000000`` (black) in the + Open Forms theme. +* ``--utrecht-button-secondary-action-danger-background-color`` takes the value of the old ``--of-button-danger-bg`` +* ``--utrecht-button-secondary-action-danger-color`` takes the value of the old ``--of-button-danger-fg`` +* ``--utrecht-button-secondary-action-focus-border-color`` takes the value of the old ``--of-color-focus-border`` +* ``--utrecht-button-subtle-danger-background-color`` takes the value of ``--of-color-danger`` +* ``--utrecht-button-subtle-danger-hover-background-color`` takes the value ``--of-color-bg`` +* ``--utrecht-button-subtle-danger-active-background-color`` takes the value of the old ``--of-button-danger-active-bg`` +* ``--utrecht-button-disabled-color``. This does not take the value of an old token. For the + Open Forms theme this is now ``#ffffff``. +* ``--utrecht-button-disabled-background-color``. This does not take the value of an old token, + the colour was previously obtained by graying out the primary button. For the Open Forms theme, + this is now ``#a02017``. +* ``--utrecht-action-disabled-cursor``. This does not take the value of an old token. It controls + the looks of the cursor when hovering a disabled button. For the Open Forms theme, this is now + ``not-allowed``. +* ``--utrecht-action-submit-cursor``. This does not take the value of an old token. It controls the + looks of the cursor when hovering over a submit button. For the Open Forms theme, this is now + ``pointer``. + + 1.6.0-alpha.0 (2023-10-02) ========================== diff --git a/design-tokens b/design-tokens index 8fa3b82ae..1fc3a5667 160000 --- a/design-tokens +++ b/design-tokens @@ -1 +1 @@ -Subproject commit 8fa3b82aeafdee6199edc6ad8f619bc884771bd1 +Subproject commit 1fc3a5667ed656c554624aefaaca3650fd0bf94a diff --git a/package.json b/package.json index dc2e99693..264d89e64 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "@floating-ui/react": "^0.24.2", "@formio/protected-eval": "^1.2.1", "@fortawesome/fontawesome-free": "^6.1.1", - "@open-formulieren/design-tokens": "^0.41.0", + "@open-formulieren/design-tokens": "^0.44.1", "@sentry/react": "^6.13.2", "@sentry/tracing": "^6.13.2", "@trivago/prettier-plugin-sort-imports": "^4.0.0", diff --git a/src/components/Button/Button.js b/src/components/Button/Button.js deleted file mode 100644 index 700146178..000000000 --- a/src/components/Button/Button.js +++ /dev/null @@ -1,98 +0,0 @@ -import { - Button as UtrechtButton, - ButtonLink as UtrechtButtonLink, - LinkButton as UtrechtLinkButton, -} from '@utrecht/component-library-react'; -import PropTypes from 'prop-types'; -import React from 'react'; - -import {getBEMClassName} from 'utils'; - -export const VARIANTS = ['', 'primary', 'secondary', 'anchor', 'danger', 'icon-only']; - -const Button = ({ - type = '', - component: Component = UtrechtButton, - variant = '', - extraVariants = [], - onDisabledClick, - children, - ...extra -}) => { - let modifiers = variant ? [variant, ...extraVariants] : extraVariants; - const className = getBEMClassName('button', modifiers); - - // https://www.a11y-101.com/development/aria-disabled - const {disabled, ...remainingProps} = extra; - - const props = { - className, - ...remainingProps, - }; - if (type) { - props.type = type; - } - - if (disabled) { - props['aria-disabled'] = 'true'; - // force the onClick handler to do nothing so we can't submit - props.onClick = event => { - if (onDisabledClick) onDisabledClick(); - event.preventDefault(); - }; - } - - switch (variant) { - case 'primary': { - props.appearance = 'primary-action-button'; - break; - } - case 'secondary': { - props.appearance = 'secondary-action-button'; - break; - } - case 'danger': { - props.appearance = 'primary-action-button'; - props.hint = 'danger'; - break; - } - case 'anchor': { - switch (Component) { - case 'a': { - // ButtonLink uses element - Component = UtrechtButtonLink; - break; - } - case 'button': { - // LinkButton link uses ; - -export const ExtraProps = { - name: 'Extra props', - render, - args: { - title: 'Title attribute', - label: 'Hover me', - }, -}; - -export const Default = { - render, - args: {label: 'Default'}, -}; - -export const Primary = { - render, - args: { - label: 'Primary', - variant: 'primary', - }, -}; - -export const Secondary = { - render, - args: { - label: 'Secondary', - variant: 'secondary', - }, -}; - -export const Danger = { - render, - args: { - label: 'Danger', - variant: 'danger', - }, -}; - -export const Anchor = { - render, - args: { - label: 'UtrechtButton', - variant: 'anchor', - }, -}; - -export const AnchorButton = { - name: 'Anchor/button', - render, - args: { - label: 'button tag', - variant: 'anchor', - component: 'button', - }, -}; - -export const AnchorA = { - name: 'Anchor/a', - render, - args: { - label: 'anchor tag', - variant: 'anchor', - component: 'a', - href: '#', - }, -}; - -export const IconOnly = { - name: 'Icon only', - args: { - title: 'Icon only', - variant: 'icon-only', - icon: 'pen', - }, - render: ({icon, ...args}) => ( - - ), -}; - -export const IconOnlyDanger = { - name: 'Icon only danger', - render: IconOnly.render, - args: { - ...IconOnly.args, - title: 'Icon only danger', - variant: 'icon-only', - extraVariants: ['danger'], - }, -}; - -export const Disabled = { - render, - args: { - label: 'Button', - disabled: true, - onDisabledClick: () => alert('clicked disabled button'), - }, -}; - -export const CustomComponent = { - render, - args: { - label: 'Button', - component: 'div', - value: 'div element', - }, -}; - -export const Link = { - render, - args: { - label: 'Link', - component: AnchorComponent, - }, -}; diff --git a/src/components/Button/OFButton.js b/src/components/Button/OFButton.js new file mode 100644 index 000000000..4ed129a7a --- /dev/null +++ b/src/components/Button/OFButton.js @@ -0,0 +1,29 @@ +import {Button as UtrechtButton} from '@utrecht/component-library-react'; +import PropTypes from 'prop-types'; +import React from 'react'; + +// Temporary until the aria-disabled is set on the Utrecht button +const OFButton = ({disabled, children, ...extraProps}) => { + const {onClick: onClickHandler, ...otherProps} = extraProps; + + if (disabled) otherProps.className = 'utrecht-button--disabled'; + + const onClick = event => { + if (disabled) event.preventDefault(); + + if (onClickHandler) onClickHandler(event); + }; + + return ( + + {children} + + ); +}; + +OFButton.propTypes = { + disabled: PropTypes.bool, + children: PropTypes.node, +}; + +export default OFButton; diff --git a/src/components/Button/OFButton.mdx b/src/components/Button/OFButton.mdx new file mode 100644 index 000000000..68e09c49d --- /dev/null +++ b/src/components/Button/OFButton.mdx @@ -0,0 +1,55 @@ +import {ArgTypes, Canvas, Meta, Story} from '@storybook/blocks'; + +import * as OFButtonStories from './OFButton.stories'; + + + +# Buttons + +## Variants + +The Open Forms button wraps the Utrecht Button to deal with the disabled state. Currently, the +Utrecht button sets the 'disabled attribute' on the button, but this means that it is not possible +to tab navigate to it and screen readers will skip it. With our wrapper we set the 'aria-disabled' +aria attribute instead. + +More information on the Utrecht buttons can be found +[here](https://nl-design-system.github.io/themes/?path=/docs/button--gemeente-utrecht). + + + + + + + + +### Links that look like buttons + + + + + + + +### Button that looks like a link + + + + + +## Icon buttons + + + + + + +## Disabled state + +Passing the `disabled` prop sets the appropriate `aria-disabled` attribute. + + + +## Props + + diff --git a/src/components/Button/OFButton.stories.js b/src/components/Button/OFButton.stories.js new file mode 100644 index 000000000..c242ccd26 --- /dev/null +++ b/src/components/Button/OFButton.stories.js @@ -0,0 +1,137 @@ +import { + ButtonLink as UtrechtButtonLink, + LinkButton as UtrechtLinkButton, +} from '@utrecht/component-library-react'; +import React from 'react'; + +import FAIcon from '../FAIcon'; +import OFButton from './OFButton'; + +export default { + title: 'Pure React components / OF Button', + component: OFButton, + argTypes: { + children: {table: {disable: true}}, + }, + parameters: { + controls: {sort: 'requiredFirst'}, + }, +}; + +const render = ({label, component, ...args}) => { + const ButtonComponent = component; + return {label}; +}; + +export const UtrechtDefault = { + render, + args: { + label: 'Default', + component: OFButton, + }, +}; + +export const UtrechtPrimary = { + render, + args: { + label: 'Primary', + component: OFButton, + appearance: 'primary-action-button', + }, +}; + +export const UtrechtSecondary = { + render, + args: { + label: 'Secondary', + component: OFButton, + appearance: 'secondary-action-button', + }, +}; + +export const UtrechtDanger = { + render, + args: { + label: 'Danger', + component: OFButton, + appearance: 'primary-action-button', + hint: 'danger', + }, +}; + +export const UtrechtLinkLooksLikeDefaultButton = { + render, + args: { + label: 'Default', + component: UtrechtButtonLink, + href: '#', + }, +}; + +export const UtrechtLinkLooksLikePrimaryButton = { + render, + args: { + label: 'Primary', + component: UtrechtButtonLink, + href: '#', + appearance: 'primary-action-button', + }, +}; + +export const UtrechtLinkLooksLikeSecondaryButton = { + render, + args: { + label: 'Secondary', + component: UtrechtButtonLink, + href: '#', + appearance: 'secondary-action-button', + }, +}; + +export const UtrechtLinkLooksLikeDangerButton = { + render, + args: { + label: 'Danger', + component: UtrechtButtonLink, + href: '#', + appearance: 'primary-action-button', + hint: 'danger', + }, +}; + +export const UtrechtButtonLooksLikeLink = { + render, + args: { + label: 'Link-like button', + component: UtrechtLinkButton, + }, +}; + +export const UtrechtIconButton = { + render, + args: { + label: , + component: OFButton, + appearance: 'subtle-button', + }, +}; + +export const UtrechtIconButtonDanger = { + render, + args: { + label: , + component: OFButton, + appearance: 'subtle-button', + hint: 'danger', + }, +}; + +export const UtrechtButtonDisabled = { + render, + args: { + label: 'Disabled', + component: OFButton, + appearance: 'primary-action-button', + disabled: true, + }, +}; diff --git a/src/components/Button/index.js b/src/components/Button/index.js index f332246b2..2bbef43fa 100644 --- a/src/components/Button/index.js +++ b/src/components/Button/index.js @@ -1,4 +1,3 @@ -import {default as Button} from './Button'; +import OFButton from './OFButton'; -export default Button; -export {VARIANTS} from './Button'; +export {OFButton}; diff --git a/src/components/ButtonsToolbar/index.js b/src/components/ButtonsToolbar/index.js index 59597cbb2..06767d356 100644 --- a/src/components/ButtonsToolbar/index.js +++ b/src/components/ButtonsToolbar/index.js @@ -1,7 +1,8 @@ +import {LinkButton as UtrechtLinkButton} from '@utrecht/component-library-react'; import PropTypes from 'prop-types'; import React from 'react'; -import Button from 'components/Button'; +import {OFButton} from 'components/Button'; import {Literal, LiteralsProvider} from 'components/Literal'; import Loader from 'components/Loader'; import LogoutButton from 'components/LogoutButton'; @@ -28,27 +29,37 @@ const ButtonsToolbar = ({ {onNavigatePrevPage && ( - + )} {/* TODO: refactor: `const canSuspendForm = onFormSave === undefined` - this does not need to be its own prop */} {canSuspendForm && ( - + )} {showSubmitButton && ( - + )} diff --git a/src/components/ExistingSubmissionOptions.js b/src/components/ExistingSubmissionOptions.js index c004897f1..5cb11daab 100644 --- a/src/components/ExistingSubmissionOptions.js +++ b/src/components/ExistingSubmissionOptions.js @@ -3,7 +3,7 @@ import React from 'react'; import {FormattedMessage} from 'react-intl'; import {useNavigate} from 'react-router-dom'; -import Button from 'components/Button'; +import {OFButton} from 'components/Button'; import {Toolbar, ToolbarList} from 'components/Toolbar'; import Types from 'types'; @@ -15,20 +15,20 @@ const ExistingSubmissionOptions = ({form, onFormAbort}) => { return ( - + - + ); diff --git a/src/components/LanguageSelection/LanguageSelectionDisplay.js b/src/components/LanguageSelection/LanguageSelectionDisplay.js index 6093fae58..68d997290 100644 --- a/src/components/LanguageSelection/LanguageSelectionDisplay.js +++ b/src/components/LanguageSelection/LanguageSelectionDisplay.js @@ -1,9 +1,7 @@ -import {ButtonGroup, Heading} from '@utrecht/component-library-react'; +import {ButtonGroup, Heading, LinkButton} from '@utrecht/component-library-react'; import PropTypes from 'prop-types'; import {Fragment} from 'react'; -import Button from 'components/Button'; - const Spacer = () => ; const LanguageSelectionDisplay = ({heading, headingLevel, headingId, items, onLanguageChange}) => ( @@ -14,9 +12,7 @@ const LanguageSelectionDisplay = ({heading, headingLevel, headingId, items, onLa {items.map(({current, label, lang, textContent}, i, a) => ( - + {i + 1 < a.length ? : null} ))} diff --git a/src/components/Loader.js b/src/components/Loader.js index bcd9eddf4..55bf6d5f3 100644 --- a/src/components/Loader.js +++ b/src/components/Loader.js @@ -3,7 +3,7 @@ import React from 'react'; import {getBEMClassName} from 'utils'; -export const MODIFIERS = ['centered', 'only-child', 'small']; +export const MODIFIERS = ['centered', 'only-child', 'small', 'gray']; const Loader = ({modifiers = []}) => { const className = getBEMClassName('loading', modifiers); diff --git a/src/components/LoginButton/index.js b/src/components/LoginButton/index.js index ca2890d59..54fe6deab 100644 --- a/src/components/LoginButton/index.js +++ b/src/components/LoginButton/index.js @@ -1,6 +1,7 @@ +import {ButtonLink as UtrechtButtonLink} from '@utrecht/component-library-react'; import React from 'react'; -import Button from 'components/Button'; +import {OFButton} from 'components/Button'; import FormattedLoginOption from 'types/FormattedLoginOption'; import {getBEMClassName} from 'utils'; @@ -9,19 +10,19 @@ import LoginButtonIcon from './LoginButtonIcon'; const LoginButton = ({option, ...extra}) => { let url = option.url; let extraProps = {...extra}; - let component = 'a'; + let ButtonComponent = UtrechtButtonLink; if (!url) { url = '#'; extraProps = {...extraProps, type: 'submit'}; - component = 'button'; + ButtonComponent = OFButton; } return (
- +
); diff --git a/src/components/LogoutButton.js b/src/components/LogoutButton.js index 4353fabca..6c476fd7f 100644 --- a/src/components/LogoutButton.js +++ b/src/components/LogoutButton.js @@ -2,16 +2,16 @@ import PropTypes from 'prop-types'; import React from 'react'; import {FormattedMessage} from 'react-intl'; -import Button from 'components/Button'; +import {OFButton} from 'components/Button'; import {Toolbar, ToolbarList} from 'components/Toolbar'; const LogoutButton = ({onLogout}) => { return ( - + ); diff --git a/src/components/PaymentForm.js b/src/components/PaymentForm.js index b5f84a0fd..5bf2fb363 100644 --- a/src/components/PaymentForm.js +++ b/src/components/PaymentForm.js @@ -2,7 +2,7 @@ import PropTypes from 'prop-types'; import React, {useEffect, useRef} from 'react'; import {FormattedMessage} from 'react-intl'; -import Button from 'components/Button'; +import {OFButton} from 'components/Button'; const AUTOSUBMIT_AFTER = 5000; @@ -31,9 +31,9 @@ const PaymentForm = ({method, url, data, autoSubmit = true}) => { return (
{dataFields} - +
); }; diff --git a/src/components/Sessions/RequireSession.js b/src/components/Sessions/RequireSession.js index b573f9676..33388115d 100644 --- a/src/components/Sessions/RequireSession.js +++ b/src/components/Sessions/RequireSession.js @@ -5,7 +5,7 @@ import {useTimeout, useTimeoutFn} from 'react-use'; import {ConfigContext} from 'Context'; import {apiCall} from 'api'; -import Button from 'components/Button'; +import {OFButton} from 'components/Button'; import Card from 'components/Card'; import ErrorMessage from 'components/ErrorMessage'; import Link from 'components/Link'; @@ -148,9 +148,9 @@ const ExpiryModal = ({showWarning, secondsToExpiry, setWarningDismissed}) => { - + diff --git a/src/components/SubmissionConfirmation.js b/src/components/SubmissionConfirmation.js index 46cc41519..175a79244 100644 --- a/src/components/SubmissionConfirmation.js +++ b/src/components/SubmissionConfirmation.js @@ -6,7 +6,7 @@ import {useAsync} from 'react-use'; import {post} from 'api'; import Anchor from 'components/Anchor'; import Body from 'components/Body'; -import Button from 'components/Button'; +import {OFButton} from 'components/Button'; import Card from 'components/Card'; import ErrorBoundary from 'components/ErrorBoundary'; import FAIcon from 'components/FAIcon'; @@ -175,12 +175,12 @@ const SubmissionConfirmation = ({statusUrl, onFailure, onConfirmed, donwloadPDFT - + diff --git a/src/components/Summary/Summary.stories.js b/src/components/Summary/Summary.stories.js index c918d273c..b1687b1d6 100644 --- a/src/components/Summary/Summary.stories.js +++ b/src/components/Summary/Summary.stories.js @@ -243,7 +243,8 @@ export const MultipleRequiredStatements = { expect( canvas.queryByText('U moet akkoord gaan met het privacybeleid om door te gaan') ).toBeNull(); - expect(submitButton).not.toHaveAttribute('aria-disabled', 'true'); + + expect(submitButton).toHaveAttribute('aria-disabled', 'false'); } ); }, diff --git a/src/components/SummaryConfirmation/index.js b/src/components/SummaryConfirmation/index.js index 02494c4ad..2b6a01563 100644 --- a/src/components/SummaryConfirmation/index.js +++ b/src/components/SummaryConfirmation/index.js @@ -1,8 +1,9 @@ +import {LinkButton as UtrechtLinkButton} from '@utrecht/component-library-react'; import {useFormikContext} from 'formik'; import PropTypes from 'prop-types'; import React, {useState} from 'react'; -import Button from 'components/Button'; +import {OFButton} from 'components/Button'; import {Literal} from 'components/Literal'; import StatementCheckboxes from 'components/StatementCheckboxes'; import {Toolbar, ToolbarList} from 'components/Toolbar'; @@ -36,22 +37,22 @@ const SummaryConfirmation = ({submissionAllowed, onPrevPage}) => { {!!onPrevPage && ( - + )} {canSubmit ? ( - + ) : null} diff --git a/src/components/appointments/CreateAppointment/CreateAppointment.spec.js b/src/components/appointments/CreateAppointment/CreateAppointment.spec.js index ce230e5ff..c8c3f02a3 100644 --- a/src/components/appointments/CreateAppointment/CreateAppointment.spec.js +++ b/src/components/appointments/CreateAppointment/CreateAppointment.spec.js @@ -167,7 +167,8 @@ describe('Create appointment status checking', () => { await user.click(checkbox); } const submitButton = screen.getByRole('button', {name: 'Confirm'}); - expect(submitButton).not.toHaveAttribute('aria-disabled'); + expect(submitButton).toHaveAttribute('aria-disabled', 'false'); + await user.click(submitButton); await screen.findByText(/Processing/); // wait for summary page to be rendered again diff --git a/src/components/appointments/cancel/CancelAppointment.js b/src/components/appointments/cancel/CancelAppointment.js index 8e7df798c..d53b57321 100644 --- a/src/components/appointments/cancel/CancelAppointment.js +++ b/src/components/appointments/cancel/CancelAppointment.js @@ -6,7 +6,7 @@ import {useNavigate} from 'react-router-dom'; import {ConfigContext} from 'Context'; import {post} from 'api'; import Body from 'components/Body'; -import Button from 'components/Button'; +import {OFButton} from 'components/Button'; import Card from 'components/Card'; import ErrorMessage from 'components/ErrorMessage'; import {Toolbar, ToolbarList} from 'components/Toolbar'; @@ -126,12 +126,12 @@ out your email address for verification purposes.`} - + diff --git a/src/components/appointments/steps/ChooseProductStep.js b/src/components/appointments/steps/ChooseProductStep.js index acf93ca8f..f31f299c8 100644 --- a/src/components/appointments/steps/ChooseProductStep.js +++ b/src/components/appointments/steps/ChooseProductStep.js @@ -8,7 +8,7 @@ import {useNavigate} from 'react-router-dom'; import {z} from 'zod'; import {toFormikValidationSchema} from 'zod-formik-adapter'; -import Button from 'components/Button'; +import {OFButton} from 'components/Button'; import {CardTitle} from 'components/Card'; import FAIcon from 'components/FAIcon'; import {Toolbar, ToolbarList} from 'components/Toolbar'; @@ -79,9 +79,8 @@ const ChooseProductStepFields = ({values: {products = []}, validateForm}) => { {supportsMultipleProducts && (
- +
)} @@ -134,12 +133,12 @@ const ProductWrapper = ({index, numProducts, onRemove, children}) => { {numProducts > 1 && ( - + )} diff --git a/src/components/modals/FormStepSaveModal.js b/src/components/modals/FormStepSaveModal.js index 27eb14bc7..c7bee8e8f 100644 --- a/src/components/modals/FormStepSaveModal.js +++ b/src/components/modals/FormStepSaveModal.js @@ -1,6 +1,7 @@ /** * Display a modal to allow the user to save the form step in it's current state. */ +import {Button as UtrechtButton} from '@utrecht/component-library-react'; import {Formik} from 'formik'; import PropTypes from 'prop-types'; import React, {useContext} from 'react'; @@ -10,7 +11,6 @@ import {useImmerReducer} from 'use-immer'; import {ConfigContext} from 'Context'; import {destroy, post} from 'api'; import Body from 'components/Body'; -import Button from 'components/Button'; import ErrorMessage from 'components/ErrorMessage'; import Loader from 'components/Loader'; import {Toolbar, ToolbarList} from 'components/Toolbar'; @@ -158,12 +158,12 @@ const FormStepSaveModal = ({ - + diff --git a/src/components/modals/FormStepSaveModal.stories.js b/src/components/modals/FormStepSaveModal.stories.js index 666dd54e3..8404fe75f 100644 --- a/src/components/modals/FormStepSaveModal.stories.js +++ b/src/components/modals/FormStepSaveModal.stories.js @@ -3,8 +3,8 @@ import {rest} from 'msw'; import React from 'react'; import {BASE_URL} from 'api-mocks'; -import Button from 'components/Button'; -import {ConfigDecorator, DeprecatedRouterDecorator} from 'story-utils/decorators'; +import {OFButton} from 'components/Button'; +import {ConfigDecorator} from 'story-utils/decorators'; import {default as FormStepSaveModalComponent} from './FormStepSaveModal'; @@ -49,9 +49,9 @@ export const FormStepSaveModal = { const [_, updateArgs] = useArgs(); return ( <> - + { diff --git a/src/formio/components/Button.js b/src/formio/components/Button.js index 29f5cdfc6..4939a7c28 100644 --- a/src/formio/components/Button.js +++ b/src/formio/components/Button.js @@ -1,7 +1,5 @@ import {Formio} from 'react-formio'; -import {applyPrefix} from '../utils'; - /** * Extend the default button field to modify it to our needs. */ @@ -9,9 +7,7 @@ class Button extends Formio.Components.components.button { get inputInfo() { const info = super.inputInfo; // change the default CSS classes - info.attr.class = [applyPrefix('button'), 'utrecht-button', 'utrecht-button--html-button'].join( - ' ' - ); + info.attr.class = 'utrecht-button utrecht-button--secondary-action'; return info; } } diff --git a/src/formio/templates/editGrid.ejs b/src/formio/templates/editGrid.ejs index 12a770e2d..f8ab8125d 100644 --- a/src/formio/templates/editGrid.ejs +++ b/src/formio/templates/editGrid.ejs @@ -11,11 +11,11 @@
  • - +
  • {% if (ctx.component.removeRow) { %}
  • - +
  • {% } %}
@@ -34,7 +34,7 @@ {% if (!ctx.readOnly && ctx.hasAddButton) { %}
-
diff --git a/src/formio/templates/editGridRow.ejs b/src/formio/templates/editGridRow.ejs index f2ff77e34..c3c5b0c74 100644 --- a/src/formio/templates/editGridRow.ejs +++ b/src/formio/templates/editGridRow.ejs @@ -9,10 +9,10 @@
  • - +
  • - +
diff --git a/src/formio/templates/file.ejs b/src/formio/templates/file.ejs index 3ec3648d4..5642a1574 100644 --- a/src/formio/templates/file.ejs +++ b/src/formio/templates/file.ejs @@ -18,7 +18,7 @@
{% if (!ctx.disabled) { %}
-
diff --git a/src/formio/templates/multiValueRow.ejs b/src/formio/templates/multiValueRow.ejs index e00a77679..b8e58b3d3 100644 --- a/src/formio/templates/multiValueRow.ejs +++ b/src/formio/templates/multiValueRow.ejs @@ -4,7 +4,7 @@ {% if (!ctx.disabled) { %} - diff --git a/src/formio/templates/multiValueTable.ejs b/src/formio/templates/multiValueTable.ejs index f73d48aad..9a35f681a 100644 --- a/src/formio/templates/multiValueTable.ejs +++ b/src/formio/templates/multiValueTable.ejs @@ -4,7 +4,7 @@ {% if (!ctx.disabled) {%} - diff --git a/src/i18n-zod.stories.js b/src/i18n-zod.stories.js index 9f58867fc..b52b7986e 100644 --- a/src/i18n-zod.stories.js +++ b/src/i18n-zod.stories.js @@ -5,7 +5,7 @@ import {useIntl} from 'react-intl'; import {z} from 'zod'; import {toFormikValidationSchema} from 'zod-formik-adapter'; -import Button from 'components/Button'; +import {OFButton} from 'components/Button'; import {EmailField, TextField} from 'components/forms'; import useZodErrorMap from 'hooks/useZodErrorMap'; @@ -105,9 +105,9 @@ const AccessibleErrorsExample = ({onSubmit}) => { - +
diff --git a/src/scss/components/_button.scss b/src/scss/components/_button.scss index d482f9bd1..90c0e05d1 100644 --- a/src/scss/components/_button.scss +++ b/src/scss/components/_button.scss @@ -1,229 +1,19 @@ -/** - * TODO: properly refactor these components, the styles are bonkers... - */ -@use 'sass:math'; -@use 'microscope-sass/lib/bem'; - -@import '~microscope-sass/lib/grid'; -@import '~microscope-sass/lib/typography'; - @import '@utrecht/components/dist/button/css/index.css'; @import '@utrecht/components/dist/button-link/css/index.css'; -@import '../mixins/prefix'; - -$button-line-height: $typography-line-height-text-big * $typography-font-size-text-big; -$button-line-height-px: math.div($button-line-height, ($button-line-height * 0 + 1)) * 16px; -$button-padding-v: math.div(($grid-row-height - $button-line-height-px), 2) - - $typography-size-border; -$button-padding-h: $grid-margin-2; - -@mixin button-theme( - $name, - $color, - $color-background, - $color-border: var(--of-color-bg), - $color-background-hover: null, - $color-border-hover: null, - $color-active: null, - $color-background-active: null, - $color-border-active: null, - $force-colors: false, - $color-border-focus: null -) { - // build the selector either as just the parent or the parent with modifier, - // based on the theme name - $sel: #{&}; - - @if $name != '' { - $sel: '#{&}--#{$name}'; - } - - // now output the actual CSS rules - @at-root #{$sel} { - @if $force-colors { - color: $color !important; - background-color: $color-background !important; - } @else { - color: $color; - background-color: $color-background; - } - - // TODO: can be removed once there are utrecht design tokens for the bottom/right - // border color - border-right-color: $color-border !important; - border-bottom-color: $color-border !important; - - &:focus, - &:focus-visible { - // TODO: can be removed once there are utrecht design tokens for the bottom/right border color - // issue: https: //github.com/nl-design-system/utrecht/issues/1406 - border-color: $color-border-focus !important; - border-right-color: $color-border-focus !important; - border-bottom-color: $color-border-focus !important; - // TODO: can be removed once there are utrecht design tokens for the border-width - border-width: 2px !important; - } - - &:not([aria-disabled='true']):hover { - @if $color-background-hover { - background-color: $color-background-hover; - } - - @if $color-border-hover { - // TODO: can be removed once there are utrecht design tokens for the bottom/right - // border color - border-right-color: $color-border-hover !important; - border-bottom-color: $color-border-hover !important; - } - } - - &:active { - // TODO: can be removed once there are utrecht design tokens for the bottom/right - // border color - @if $color-active { - color: $color-active !important; - } - - @if $color-background-active { - background-color: $color-background-active !important; - } - - @if $color-border-active { - // TODO: can be removed once there are utrecht design tokens for the bottom/right - // border color - border-top-color: $color-border-active !important; - border-right-color: transparent !important; - border-left-color: $color-border-active !important; - border-bottom-color: transparent !important; - } - } - } -} - -.#{prefix(button)} { - @include body; - @include body--big; - @include border(all, $color: transparent, $size: 2px); - @include button-theme( - '', - $typography-color-text, - $color-background, - $color-border, - $color-background-hover: var(--of-button-hover-bg), - $color-border-hover: var(--of-button-hover-color-border), - $color-active: var(--of-button-active-fg), - $color-background-active: - var(--of-button-active-bg, var(--utrecht-button-active-background-color)), - $color-border-active: - var(--of-button-active-color-border, var(--utrecht-button-active-border-color)), - $color-border-focus: - var(--of-button-focus-color-border, var(--utrecht-button-focus-border-color)) - ); - @include button-theme( - 'primary', - var(--of-button-primary-fg), - var(--of-button-primary-bg), - var(--of-button-primary-color-border), - $color-background-hover: var(--of-button-primary-hover-bg), - $color-border-hover: var(--of-button-primary-hover-color-border), - $color-active: var(--of-button-primary-active-fg), - $color-background-active: var(--of-button-primary-active-bg), - $color-border-active: var(--of-button-primary-active-color-border), - $color-border-focus: - var( - --of-button-primary-focus-color-border, - var(--utrecht-button-primary-action-focus-border-color) - ) - ); - @include button-theme( - 'danger', - var(--of-button-danger-fg), - var(--of-button-danger-bg), - var(--of-button-danger-color-border), - $color-background-hover: var(--of-button-danger-hover-bg), - $color-border-hover: var(--of-button-danger-hover-color-border), - $color-active: var(--of-button-danger-active-fg), - $color-background-active: var(--of-button-danger-active-bg), - $color-border-active: var(--of-button-danger-active-color-border), - $color-border-focus: - var( - --of-button-danger-focus-color-border, - var(--utrecht-button-primary-action-danger-focus-border-color) - ) - ); - - appearance: none; - border-radius: 0; - display: flex; - align-items: center; - padding: $button-padding-v $button-padding-h; - text-decoration: none; - float: left; - - @media print { - display: none; - } - - &:not([aria-disabled='true']) { - cursor: pointer; - } - - // styling for disabled button - &[aria-disabled='true'] { - cursor: not-allowed; - filter: saturate(0); - opacity: 0.5; - } - - .fa-icon:not(:last-child) { - margin-right: $button-padding-h; - } - - @include bem.modifier('icon-only') { - color: var(--of-button-fg); - background-color: transparent; - border-style: none; - --utrecht-button-hover-background-color: transparent; - - &:hover, - &:active { - --utrecht-button-hover-color: var(--of-button-fg); - color: var(--of-button-fg); - background-color: transparent; - border-style: none; - } - - &.#{prefix(button)}--danger { - color: var(--of-button-danger-bg); - } - } - - // anchor variants, these map to utrecht components: - // * UtrechtButton (the default) - // * UtrechtButtonLink (`a` component) - // * UtrechtLinkButton (`button` component) - @include bem.modifier('anchor') { - @include button-theme( - '', - var(--of-button-anchor-fg, var(--utrecht-link-color)), - var(--of-button-anchor-bg), - var(--of-button-anchor-color-border), - $color-background-hover: var(--of-button-anchor-hover-bg, var(--utrecht-link-hover-color)), - $color-border-hover: var(--of-button-anchor-hover-color-border), - $color-active: var(--of-button-anchor-active-fg), - $color-background-active: var(--of-button-anchor-active-bg), - $color-border-active: var(--of-button-anchor-active-color-border), - $color-border-focus: - var(--of-button-anchor-focus-color-border, var(--utrecht-button-focus-border-color)), - $force-colors: true // there will not be an anchor variant - ); - - // the OPPOSITE of what we normally do - text-decoration: var(--utrecht-link-hover-text-decoration, none); +.openforms-theme { + .utrecht-button, + .utrecht-button-link { + // For buttons without any state or buttons being hovered over, apply a 3d effect + // by making the top and left border transparent. + &, &:hover { - --of-button-anchor-fg: var(--utrecht-link-hover-color); - text-decoration: var(--utrecht-link-text-decoration, underline); + // However, if the button has visible focus, apply the border color to all borders + // otherwise it looks a bit broken. + &:not(:focus-visible) { + border-block-start-color: transparent; + border-inline-start-color: transparent; + } } } } diff --git a/src/scss/components/_file-upload.scss b/src/scss/components/_file-upload.scss index d11c1be73..9d669a0ab 100644 --- a/src/scss/components/_file-upload.scss +++ b/src/scss/components/_file-upload.scss @@ -153,8 +153,6 @@ we don't have strict BEM naming here. button[ref='takePictureButton'], button[ref='toggleCameraMode'] { - @extend .#{prefix('button')}; - @extend .#{prefix('button--primary')}; display: inline-block; float: none; @include mobile-only { diff --git a/src/scss/components/_loading.scss b/src/scss/components/_loading.scss index 569c3c392..9b1696671 100644 --- a/src/scss/components/_loading.scss +++ b/src/scss/components/_loading.scss @@ -34,6 +34,13 @@ } } + @include bem.modifier('gray') { + @include bem.element('spinner') { + border-color: var(--utrecht-button-disabled-color); + border-top-color: var(--utrecht-button-disabled-background-color); + } + } + @include bem.element('spinner') { border: 6px solid var(--of-color-secondary); border-top: 6px solid var(--of-color-primary);