diff --git a/design-tokens b/design-tokens index 1fc3a5667..429fa1476 160000 --- a/design-tokens +++ b/design-tokens @@ -1 +1 @@ -Subproject commit 1fc3a5667ed656c554624aefaaca3650fd0bf94a +Subproject commit 429fa14766b18a558bd17e69ddf9aef26398af81 diff --git a/package.json b/package.json index 264d89e64..09638c32b 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.44.1", + "@open-formulieren/design-tokens": "^0.45.0", "@sentry/react": "^6.13.2", "@sentry/tracing": "^6.13.2", "@trivago/prettier-plugin-sort-imports": "^4.0.0", diff --git a/src/components/EditGrid/EditGrid.js b/src/components/EditGrid/EditGrid.js new file mode 100644 index 000000000..48d5a9b11 --- /dev/null +++ b/src/components/EditGrid/EditGrid.js @@ -0,0 +1,39 @@ +import {ButtonGroup} from '@utrecht/component-library-react'; +import PropTypes from 'prop-types'; +import React from 'react'; +import {FormattedMessage} from 'react-intl'; + +import {OFButton} from 'components/Button'; +import FAIcon from 'components/FAIcon'; + +const DefaultAddButtonLabel = () => ( + <> + {' '} + + +); + +const EditGrid = ({children, onAddItem, addButtonLabel}) => ( +
+
{children}
+ + {onAddItem && ( + + + {addButtonLabel || } + + + )} +
+); + +EditGrid.propTypes = { + children: PropTypes.node.isRequired, + addButtonLabel: PropTypes.node, + onAddItem: PropTypes.func, +}; + +export default EditGrid; diff --git a/src/components/EditGrid/EditGrid.stories.js b/src/components/EditGrid/EditGrid.stories.js new file mode 100644 index 000000000..e4c5bc035 --- /dev/null +++ b/src/components/EditGrid/EditGrid.stories.js @@ -0,0 +1,56 @@ +import Body from 'components/Body'; +import {OFButton} from 'components/Button'; + +import {EditGrid, EditGridButtonGroup, EditGridItem} from '.'; + +export default { + title: 'Pure React components / EditGrid / EditGrid', + component: EditGrid, + argTypes: { + children: {control: false}, + onAddItem: {control: false}, + }, + args: { + children: ( + <> + + A button + + } + > + First item + + + A button + + } + > + Second item + + + ), + }, + parameters: { + controls: {sort: 'requiredFirst'}, + }, +}; + +export const WithAddButton = {}; + +export const WithCustomAddButtonLabel = { + args: { + addButtonLabel: 'Custom add button label', + }, +}; + +export const WithoutAddbutton = { + args: { + onAddItem: undefined, + }, +}; diff --git a/src/components/EditGrid/EditGridButtonGroup.js b/src/components/EditGrid/EditGridButtonGroup.js new file mode 100644 index 000000000..2e4210c10 --- /dev/null +++ b/src/components/EditGrid/EditGridButtonGroup.js @@ -0,0 +1,13 @@ +import {ButtonGroup} from '@utrecht/component-library-react'; +import PropTypes from 'prop-types'; +import React from 'react'; + +const EditGridButtonGroup = ({children}) => ( + {children} +); + +EditGridButtonGroup.propTypes = { + children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]).isRequired, +}; + +export default EditGridButtonGroup; diff --git a/src/components/EditGrid/EditGridItem.js b/src/components/EditGrid/EditGridItem.js new file mode 100644 index 000000000..492dfdac5 --- /dev/null +++ b/src/components/EditGrid/EditGridItem.js @@ -0,0 +1,21 @@ +import {Fieldset, FieldsetLegend} from '@utrecht/component-library-react'; +import PropTypes from 'prop-types'; +import React from 'react'; + +const EditGridItem = ({heading, children: body, buttons}) => { + return ( +
+ {heading} + {body} + {buttons} +
+ ); +}; + +EditGridItem.propTypes = { + heading: PropTypes.node.isRequired, + children: PropTypes.node, + buttons: PropTypes.node, +}; + +export default EditGridItem; diff --git a/src/components/EditGrid/EditGridItem.stories.js b/src/components/EditGrid/EditGridItem.stories.js new file mode 100644 index 000000000..d63ceca57 --- /dev/null +++ b/src/components/EditGrid/EditGridItem.stories.js @@ -0,0 +1,30 @@ +import Body from 'components/Body'; +import {OFButton} from 'components/Button'; + +import {EditGridButtonGroup, EditGridItem as EditGridItemComponent} from '.'; + +export default { + title: 'Pure React components / EditGrid / Item', + component: EditGridItemComponent, + argTypes: { + children: {control: false}, + buttons: {control: false}, + }, + args: { + heading: 'Item heading', + children: Item body, typically form fields, + buttons: ( + + Save + + Remove + + + ), + }, + parameters: { + controls: {sort: 'requiredFirst'}, + }, +}; + +export const Item = {}; diff --git a/src/components/EditGrid/design-tokens.mdx b/src/components/EditGrid/design-tokens.mdx new file mode 100644 index 000000000..db76170c1 --- /dev/null +++ b/src/components/EditGrid/design-tokens.mdx @@ -0,0 +1,73 @@ +import ofTokens from '@open-formulieren/design-tokens/dist/tokens'; +import {Meta} from '@storybook/addon-docs'; +import utrechtTokens from '@utrecht/design-tokens/dist/tokens'; +import TokensTable from 'design-token-editor/lib/esm/TokensTable'; + + + +## Available design tokens + +**Open Forms specific tokens** + + + +**Utrecht tokens** + +Under the hood, the following components & tokens are used & can be leveraged to change the +appearance of the editgrid component. + +- `utrecht.button-group` +- `utrecht.form-fieldset` + +## Updating your theme + +Before SDK 1.6, the CSS for edit grid was hardcoded. With 1.6, this is now parametrized, but there +are no backwards compatible defaults set. To recover the original styles, use the following tokens: + +```json +{ + "of": { + "editgrid": { + "line-height": {"value": 1.5}, + "gap": {"value": "12px"}, + + "item": { + "gap": {"value": "12px"}, + "border": {"value": "solid 1px {of.color.border}"}, + "padding-block-end": {"value": "24px"}, + "padding-block-start": {"value": "24px"}, + "padding-inline-end": {"value": "24px"}, + "padding-inline-start": {"value": "24px"} + }, + + "item-heading": { + "font-family": {"value": "{of.typography.sans-serif.font-family}"}, + "font-size": {"value": "0.875rem"}, + "line-height": {"value": "1.2"}, + "margin-block-end": {"value": "24px"} + } + } + } +} +``` + +or in CSS: + +```css +.your-theme { + --of-editgrid-item-border: solid 1px var(--of-color-border); + --of-editgrid-item-gap: 12px; + --of-editgrid-item-padding-block-end: 24px; + --of-editgrid-item-padding-block-start: 24px; + --of-editgrid-item-padding-inline-start: 24px; + --of-editgrid-item-padding-inline-end: 24px; + + --of-editgrid-item-heading-font-family: var(--of-typography-sans-serif-font-family); + --of-editgrid-item-heading-font-size: 0.875rem; + --of-editgrid-item-heading-line-height: 1.2; + --of-editgrid-item-heading-margin-block-end: 24px; + + --of-editgrid-line-height: 1.5; + --of-editgrid-gap: 12p; +} +``` diff --git a/src/components/EditGrid/index.js b/src/components/EditGrid/index.js new file mode 100644 index 000000000..9f94e8f0b --- /dev/null +++ b/src/components/EditGrid/index.js @@ -0,0 +1,3 @@ +export {default as EditGridButtonGroup} from './EditGridButtonGroup'; +export {default as EditGridItem} from './EditGridItem'; +export {default as EditGrid} from './EditGrid'; diff --git a/src/components/appointments/steps/ChooseProductStep.js b/src/components/appointments/steps/ChooseProductStep.js index f31f299c8..50691eb86 100644 --- a/src/components/appointments/steps/ChooseProductStep.js +++ b/src/components/appointments/steps/ChooseProductStep.js @@ -10,11 +10,10 @@ import {toFormikValidationSchema} from 'zod-formik-adapter'; import {OFButton} from 'components/Button'; import {CardTitle} from 'components/Card'; +import {EditGrid, EditGridButtonGroup, EditGridItem} from 'components/EditGrid'; import FAIcon from 'components/FAIcon'; -import {Toolbar, ToolbarList} from 'components/Toolbar'; import useQuery from 'hooks/useQuery'; import useTitle from 'hooks/useTitle'; -import {getBEMClassName} from 'utils'; import {AppointmentConfigContext} from '../Context'; import {useCreateAppointmentContext} from '../CreateAppointment/CreateAppointmentState'; @@ -61,37 +60,34 @@ const ChooseProductStepFields = ({values: {products = []}, validateForm}) => {
{arrayHelpers => ( -
-
- {products.map(({productId}, index) => ( - // blank blocks don't have a product selected yet -> so the index is added - // to make the key guaranteed unique - arrayHelpers.remove(index))} - > - - - ))} -
- - {supportsMultipleProducts && ( -
- arrayHelpers.push({productId: '', amount: 1}))} - > - - - -
- )} -
+ + {' '} + + + } + onAddItem={ + supportsMultipleProducts && + withValidate(() => arrayHelpers.push({productId: '', amount: 1})) + } + > + {products.map(({productId}, index) => ( + // blank blocks don't have a product selected yet -> so the index is added + // to make the key guaranteed unique + arrayHelpers.remove(index))} + > + + + ))} + )}
@@ -118,31 +114,31 @@ const ProductWrapper = ({index, numProducts, onRemove, children}) => { if (!supportsMultipleProducts) { return <>{children}; } + + const buttonRow = numProducts > 1 && ( + + + + + + ); + return ( -
-
+ -
- + } + buttons={buttonRow} + > {children} - - {numProducts > 1 && ( - - - - - - - - )} -
+ ); }; diff --git a/src/formio/templates/editGrid.ejs b/src/formio/templates/editGrid.ejs index f8ab8125d..040e04309 100644 --- a/src/formio/templates/editGrid.ejs +++ b/src/formio/templates/editGrid.ejs @@ -1,42 +1,53 @@ -
-
-
    - {% ctx.rows.forEach(function(row, rowIndex) { %} -
    - {% if (!!ctx.component.groupLabel) { %}
    {{ ctx.t(ctx.component.groupLabel, { _userInput: true }) }} {{ rowIndex + 1 }}
    {% } %} -
  • - {{row}} +
    + {% if(ctx.rows.length) { %} +
    + {% ctx.rows.forEach(function(row, rowIndex) { %} +
    +
    + {% if (!!ctx.component.groupLabel) { %} + + {{ ctx.t(ctx.component.groupLabel, { _userInput: true }) }} {{ rowIndex + 1 }} + + {% } %} + +
    {{row}}
    {% if (ctx.openRows[rowIndex] && !ctx.readOnly) { %} -
    -
      -
    • - -
    • +

      + + + {% if (ctx.component.removeRow) { %} -

    • - -
    • + {% } %} -
    -
    +

    {% } %} -
    +
    {{ctx.errors[rowIndex]}}
    -
  • + +
    - {% }) %} -
+ + {% }) %}
+ {% } %} {% if (!ctx.readOnly && ctx.hasAddButton) { %} -
- -
+

+ +

{% } %}
diff --git a/src/formio/templates/editGridRow.ejs b/src/formio/templates/editGridRow.ejs index c3c5b0c74..c36967b55 100644 --- a/src/formio/templates/editGridRow.ejs +++ b/src/formio/templates/editGridRow.ejs @@ -1,20 +1,16 @@ -
- {% for (const key in ctx.flattenedComponents) { %} - {% if (ctx.isVisibleInRow(ctx.flattenedComponents[key])) { %} -
{{ctx.flattenedComponents[key].label}}: {{ ctx.getView(ctx.flattenedComponents[key], ctx.row[key]) }}
- {% } %} - {% } %} - - {% if (!ctx.self.options.readOnly) { %} -
-
    -
  • - -
  • -
  • - -
  • -
-
+ {% for (const key in ctx.flattenedComponents) { %} + {% if (ctx.isVisibleInRow(ctx.flattenedComponents[key])) { %} +
{{ctx.flattenedComponents[key].label}}: {{ ctx.getView(ctx.flattenedComponents[key], ctx.row[key]) }}
+ {% } %} {% } %} -
+ +{% if (!ctx.self.options.readOnly) { %} +

+ + +

+{% } %} diff --git a/src/i18n/compiled/en.json b/src/i18n/compiled/en.json index f617a4153..3934b1d00 100644 --- a/src/i18n/compiled/en.json +++ b/src/i18n/compiled/en.json @@ -1175,6 +1175,12 @@ "value": "Your payment is received and processed." } ], + "cKFCTI": [ + { + "type": 0, + "value": "Add another" + } + ], "cxDC/G": [ { "type": 0, diff --git a/src/i18n/compiled/nl.json b/src/i18n/compiled/nl.json index c3d19a3e3..02671942d 100644 --- a/src/i18n/compiled/nl.json +++ b/src/i18n/compiled/nl.json @@ -1179,6 +1179,12 @@ "value": "Uw betaling is ontvangen en verwerkt." } ], + "cKFCTI": [ + { + "type": 0, + "value": "Nog één toevoegen" + } + ], "cxDC/G": [ { "type": 0, diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index 7777efebd..03e72a82a 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -544,6 +544,11 @@ "description": "payment registered status", "originalDefault": "Your payment is received and processed." }, + "cKFCTI": { + "defaultMessage": "Add another", + "description": "Edit grid add button, default label text", + "originalDefault": "Add another" + }, "cxDC/G": { "defaultMessage": "The required field is not filled out.", "description": "ZOD 'required' error message", diff --git a/src/i18n/messages/nl.json b/src/i18n/messages/nl.json index 36cd05c53..af0937c2b 100644 --- a/src/i18n/messages/nl.json +++ b/src/i18n/messages/nl.json @@ -550,6 +550,11 @@ "description": "payment registered status", "originalDefault": "Your payment is received and processed." }, + "cKFCTI": { + "defaultMessage": "Nog één toevoegen", + "description": "Edit grid add button, default label text", + "originalDefault": "Add another" + }, "cxDC/G": { "defaultMessage": "Het verplichte veld is niet ingevuld.", "description": "ZOD 'required' error message", diff --git a/src/scss/components/_button-group.scss b/src/scss/components/_button-group.scss new file mode 100644 index 000000000..bcc15b13f --- /dev/null +++ b/src/scss/components/_button-group.scss @@ -0,0 +1,23 @@ +/** + * Extensions on the Utrecht button-group community component. + */ +@use 'microscope-sass/lib/bem'; + +@import '@utrecht/components/button-group/css/index'; + +.utrecht-button-group { + // A button group used in an edit grid item + @include bem.modifier('openforms-editgrid') { + // there currently does not exist a design token in the upstream component, and + // the alignment is also contextual, so we opt for an open-forms extension that + // can be tweaked with proprietary design tokens. + justify-content: var(--of-button-group-editgrid-justify-content, flex-end); + } +} + +// Reference: https://nl-design-system.github.io/utrecht/storybook/?path=/docs/css_css-button-group--docs +@include mobile-only { + .utrecht-button-group { + @include utrecht-button-group--vertical; + } +} diff --git a/src/scss/components/_editgrid.scss b/src/scss/components/_editgrid.scss index 86398ab2d..9b4a14987 100644 --- a/src/scss/components/_editgrid.scss +++ b/src/scss/components/_editgrid.scss @@ -1,42 +1,54 @@ -@import '~microscope-sass/lib/color'; -@import '~microscope-sass/lib/typography'; - -@import '../mixins/prefix'; - -.#{prefix(editgrid)} { - @include body; - - ul { - padding: 0; - } - - &__group { - padding: $grid-margin-4; - border-style: solid; - border-width: thin; - border-color: $color-border; - - &:not(:first-child) { - border-top-style: none; +/** + * The Utrecht community components do not seem to have a ready-to-use component + * for the edit grid layout, so we can't shake of some custom CSS yet. + */ +@use 'microscope-sass/lib/bem'; + +.openforms-editgrid { + line-height: var(--of-editgrid-line-height, var(--utrecht-document-line-height)); + + display: flex; + flex-direction: column; + gap: var(--of-editgrid-gap); + + @include bem.element('item') { + // NL DS markup uses a field with nested fieldset element inside + .utrecht-form-fieldset__fieldset { + display: flex; + flex-direction: column; + gap: var(--of-editgrid-item-gap); } - } - &__group-label { - @include h4; - padding-bottom: $grid-margin-4; - } + border: var(--of-editgrid-item-border); + max-inline-size: var(--of-editgrid-item-max-inline-size); + padding-block-end: var(--of-editgrid-item-padding-block-end); + padding-block-start: var(--of-editgrid-item-padding-block-start); + padding-inline-end: var(--of-editgrid-item-padding-inline-end); + padding-inline-start: var(--of-editgrid-item-padding-inline-start); - &__list { - list-style: none; - } - - &__group-actions { - padding-top: $grid-margin-2; + // ensure borders are 'collapsed' + & + .openforms-editgrid__item { + border-block-start: none; + } } - &__add-button { - display: flex; - width: 100%; - padding: $grid-margin-2 0; + @include bem.element('item-heading') { + font-family: var( + --of-editgrid-item-heading-font-family, + var(--utrecht-form-fieldset-legend-font-family, var(--utrecht-document-font-family)) + ); + font-size: var( + --of-editgrid-item-heading-font-size, + var(--utrecht-form-fieldset-legend-font-size) + ); + line-height: var( + --of-editgrid-item-heading-line-height, + var(--utrecht-form-fieldset-legend-line-height) + ); + // display: contents was considered, but that doesn't allow specifying different + // margins/paddings at the bottom to create extra space :( + // legend element doesn't care about the fieldset itself having display: flex, so we + // must treat this specially + margin-block-end: var(--of-editgrid-item-heading-margin-block-end, var(--of-editgrid-item-gap)); } } diff --git a/src/scss/components/_language-selection.scss b/src/scss/components/_language-selection.scss index 3859fb71a..39b900f33 100644 --- a/src/scss/components/_language-selection.scss +++ b/src/scss/components/_language-selection.scss @@ -1,7 +1,6 @@ @import '~microscope-sass/lib/typography'; @import '@utrecht/components/dist/alternate-lang-nav/css/index.css'; -@import '@utrecht/components/dist/button-group/css/index.css'; /** * Allow using different spacing rules for this specific component diff --git a/src/styles.scss b/src/styles.scss index 1bc9e1e28..aa85986e3 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -24,6 +24,7 @@ @import './scss/components/alert'; @import './scss/components/anchor'; @import './scss/components/button'; +@import './scss/components/button-group'; @import './scss/components/card'; @import './scss/components/content'; @import './scss/components/errors';