Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/design-field-errors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@elixir-cloud/design": minor
---

Show field errors returned by APIs.

- Input, textarea, select and multi-select get the properties `invalid` and `error`: `error` shows a message under the control (`aria-invalid`, `aria-describedby`, announced with `aria-live`), `invalid` marks the control as invalid without a message.
- The checkbox gets `invalid` only; show its message elsewhere.
- New utilities read RFC 9457 problem details (`readProblemDetails()`, `parseProblemDetails()`) and map their field errors to a component's fields (`mapFieldErrors()`). Import them from the new subpath `@elixir-cloud/design/utils`, which registers no components; the package root exports them too.
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,10 @@ jobs:
- name: Build
run: npm run build

# The design package's `./utils` subpath must load without components
- name: Check design utils entry
run: node scripts/check-design-utils.mjs

# With coverage (in Chromium, in `coverage/lcov.info` of each package
# with tests; see `web-test.runner-config.mjs`) for the uploaded run
- name: Run tests
Expand Down
17 changes: 14 additions & 3 deletions apps/documentation/pages/docs/contributing.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,27 @@ This page covers what is specific to this repository.

Keep the checks green: `CI passed` sums up the PR title, linting, tests
(Node.js 22 and 24) and the other CI jobs. Run `npm run lint`,
`npm run build` and `npm test` (and
`node --test scripts/release.test.js` after changes to
`scripts/release.js`) locally before you push. CI runs the tests with
`npm run build` and `npm test` locally before you push. After changes to
`scripts/release.js`, also run `node --test scripts/release.test.js`; after
changes to `scripts/utils.js` or `scripts/build.js`, also run
`node --test scripts/utils.test.js`. CI runs the tests with
coverage (`npm run test -- -- --coverage`, which passes `--coverage`
through npm and turbo to the test runner; Chromium only; each package's
report is in its `coverage/` folder) and reports it to
[Codecov](https://app.codecov.io/gh/elixir-cloud-aai/cloud-components);
coverage of the packages with tests must not drop by more than 1 %
(`codecov.yml`).

`scripts/build.js` builds every package from the entries that
`getBuildEntries()` in `scripts/utils.js` lists: `src/index.ts`, the
components, providers and React wrappers, and `src/utils/index.ts` if a
package has one. That file is the entry of the package's `./utils` subpath
(add it to `exports` and, for TypeScript's `node` module resolution, to
`typesVersions` in the package's `package.json`): utilities that
apps can import without registering the components, so it must not import
them. For the design package, CI checks this after the build
(`node scripts/check-design-utils.mjs`).

Changesets are not used; versions are set in the version bump pull
request of each release (see "Releases").

Expand Down
3 changes: 2 additions & 1 deletion apps/documentation/pages/docs/design/_meta.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"introduction": "Introduction",
"architecture": "Architecture",
"components": "Components"
"components": "Components",
"field-errors": "Field errors"
}
17 changes: 17 additions & 0 deletions apps/documentation/pages/docs/design/components/checkbox.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ This example shows a checkbox in a disabled state.
| `disabled` | `boolean` | `false` | When `true`, disables the checkbox. |
| `name` | `string` | `""` | The name of the checkbox for form submission. |
| `value` | `string` | `""` | The value of the checkbox for form submission. |
| `invalid` | `boolean` | `false` | When `true`, marks the checkbox as invalid, e.g. after an API rejected its value. |

### `checked`
Controls the checked state of the checkbox.
Expand Down Expand Up @@ -192,6 +193,22 @@ The value attribute, used when submitting the checkbox as part of a form.
</Tabs.Tab>
</Tabs>

### `invalid`
Marks the checkbox as invalid, e.g. when an API rejected its value (see [Field errors](/docs/design/field-errors)): it gets a red border and `aria-invalid="true"`. Clear it when the user changes the value or submits again. Unlike the other form controls, the checkbox has no `error` property: a message under the box wouldn't wrap next to its label, e.g. when the checkbox is at the end of a row. Render the message yourself, e.g. next to the label or above the form: with `mapFieldErrors()`, set `invalid` when `fieldErrors` has a message for the checkbox's field key, and show that message (it is not part of `generalError`, which holds only the errors that match no field).

<Tabs items={['React', 'HTML']}>
<Tabs.Tab>
```tsx copy
<EccUtilsDesignCheckbox invalid={true} />
```
</Tabs.Tab>
<Tabs.Tab>
```html copy
<ecc-utils-design-checkbox invalid></ecc-utils-design-checkbox>
```
</Tabs.Tab>
</Tabs>

## Events

| Event Name | React Event Name | Detail Type | Description |
Expand Down
18 changes: 18 additions & 0 deletions apps/documentation/pages/docs/design/components/input.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ This example shows an input field associated with a label.
| `placeholder` | `string` | `""` | Placeholder text for the input. |
| `value` | `string` | `""` | The current value of the input. |
| `disabled` | `boolean` | `false` | When `true`, disables the input. |
| `invalid` | `boolean` | `false` | When `true`, marks the input as invalid (also implied by `error`). |
| `error` | `string` | `""` | Error message shown under the input, e.g. from an API. |

### `type`
Specifies the input type, determining the data format and keyboard layout on mobile devices.
Expand Down Expand Up @@ -181,6 +183,22 @@ When `true`, the input is non-interactive and visually de-emphasized.
</Tabs.Tab>
</Tabs>

### `invalid` and `error`
`error` shows a message under the input, e.g. one that an API returned for this field (see [Field errors](/docs/design/field-errors)), and marks the input as invalid: it gets a red border, `aria-invalid="true"` and `aria-describedby` pointing to the message, and screen readers announce the message (`aria-live="polite"`). `invalid` marks the input as invalid without a message, e.g. when the message is shown elsewhere. Clear both when the user changes the value or submits again. The message has the part `error` for styling (`ecc-utils-design-input::part(error)`).

<Tabs items={['React', 'HTML']}>
<Tabs.Tab>
```tsx copy
<EccUtilsDesignInput error="Must be a valid URI." />
```
</Tabs.Tab>
<Tabs.Tab>
```html copy
<ecc-utils-design-input error="Must be a valid URI."></ecc-utils-design-input>
```
</Tabs.Tab>
</Tabs>

## Events

| Event Name | React Event Name | Detail Type | Description |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,8 @@ This example shows a multi-select with some values pre-selected.
| `placeholder` | `string` | `"Select options..."` | Placeholder text when no options are selected. |
| `disabled` | `boolean` | `false` | When `true`, disables the multi-select. |
| `name` | `string` | `""` | The name attribute for form submissions. |
| `invalid` | `boolean` | `false` | When `true`, marks the multi-select as invalid (also implied by `error`). |
| `error` | `string` | `""` | Error message shown under the multi-select, e.g. from an API. |

#### `value`
Controls the selected values in the multi-select. This should be an array of strings matching the values of selected items.
Expand Down Expand Up @@ -274,6 +276,26 @@ The name attribute used when submitting the multi-select as part of a form.
</Tabs.Tab>
</Tabs>

#### `invalid` and `error`
`error` shows a message under the multi-select, e.g. one that an API returned for this field (see [Field errors](/docs/design/field-errors)), and marks the multi-select as invalid: its trigger gets a red border, `aria-invalid="true"` and `aria-describedby` pointing to the message, and screen readers announce the message (`aria-live="polite"`). `invalid` marks the multi-select as invalid without a message, e.g. when the message is shown elsewhere. Clear both when the user changes the value or submits again. The message has the part `error` for styling (`ecc-utils-design-multi-select::part(error)`).

<Tabs items={['React', 'HTML']}>
<Tabs.Tab>
```tsx copy
<EccUtilsDesignMultiSelect error="Must have at least 1 item.">
...
</EccUtilsDesignMultiSelect>
```
</Tabs.Tab>
<Tabs.Tab>
```html copy
<ecc-utils-design-multi-select error="Must have at least 1 item.">
...
</ecc-utils-design-multi-select>
```
</Tabs.Tab>
</Tabs>

### `EccUtilsDesignMultiSelectTrigger`

| Property | Type | Default | Description |
Expand Down
22 changes: 22 additions & 0 deletions apps/documentation/pages/docs/design/components/select.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,8 @@ The `Select` is built by combining these components:
|-------------|-----------|---------|-------------------------------------------|
| `value` | `string` | `""` | The value of the currently selected option. |
| `disabled` | `boolean` | `false` | When `true`, disables the select. |
| `invalid` | `boolean` | `false` | When `true`, marks the select as invalid (also implied by `error`). |
| `error` | `string` | `""` | Error message shown under the select, e.g. from an API. |

#### `value`
Controls the currently selected option. Should match the `value` of one of the SelectItem components.
Expand Down Expand Up @@ -260,6 +262,26 @@ When `true`, disables the entire select, preventing user interaction.
</Tabs.Tab>
</Tabs>

#### `invalid` and `error`
`error` shows a message under the select, e.g. one that an API returned for this field (see [Field errors](/docs/design/field-errors)), and marks the select as invalid: its trigger gets a red border, `aria-invalid="true"` and `aria-describedby` pointing to the message, and screen readers announce the message (`aria-live="polite"`). `invalid` marks the select as invalid without a message, e.g. when the message is shown elsewhere. Clear both when the user changes the value or submits again. The message has the part `error` for styling (`ecc-utils-design-select::part(error)`).

<Tabs items={['React', 'HTML']}>
<Tabs.Tab>
```tsx copy
<EccUtilsDesignSelect error="Must be one of: a, b.">
...
</EccUtilsDesignSelect>
```
</Tabs.Tab>
<Tabs.Tab>
```html copy
<ecc-utils-design-select error="Must be one of: a, b.">
...
</ecc-utils-design-select>
```
</Tabs.Tab>
</Tabs>

### `EccUtilsDesignSelectItem`

| Property | Type | Default | Description |
Expand Down
18 changes: 18 additions & 0 deletions apps/documentation/pages/docs/design/components/textarea.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ This example shows a textarea with a corresponding label.
| `placeholder` | `string` | `""` | Placeholder text for the textarea. |
| `value` | `string` | `""` | The current value of the textarea. |
| `disabled` | `boolean` | `false` | When `true`, disables the textarea. |
| `invalid` | `boolean` | `false` | When `true`, marks the textarea as invalid (also implied by `error`). |
| `error` | `string` | `""` | Error message shown under the textarea, e.g. from an API. |
| `rows` | `number` | `3` | The number of visible text lines. |
| `cols` | `number` | `undefined` | The visible width of the text control. |

Expand Down Expand Up @@ -198,6 +200,22 @@ Specifies the visible width of the textarea in average character widths.
</Tabs.Tab>
</Tabs>

### `invalid` and `error`
`error` shows a message under the textarea, e.g. one that an API returned for this field (see [Field errors](/docs/design/field-errors)), and marks the textarea as invalid: it gets a red border, `aria-invalid="true"` and `aria-describedby` pointing to the message, and screen readers announce the message (`aria-live="polite"`). `invalid` marks the textarea as invalid without a message, e.g. when the message is shown elsewhere. Clear both when the user changes the value or submits again. The message has the part `error` for styling (`ecc-utils-design-textarea::part(error)`).

<Tabs items={['React', 'HTML']}>
<Tabs.Tab>
```tsx copy
<EccUtilsDesignTextarea error="Must be at most 300 characters long." />
```
</Tabs.Tab>
<Tabs.Tab>
```html copy
<ecc-utils-design-textarea error="Must be at most 300 characters long."></ecc-utils-design-textarea>
```
</Tabs.Tab>
</Tabs>

## Events

| Event Name | React Event Name | Detail Type | Description |
Expand Down
Loading
Loading