-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Co-authored-by: Vincent Smedinga <[email protected]>
- Loading branch information
1 parent
bbec4de
commit e879942
Showing
11 changed files
with
247 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
<!-- @license CC0-1.0 --> | ||
|
||
# Form Error List | ||
|
||
Use this component at the top of a page to summarise any errors a user has made. | ||
When a user makes an error, you must show both a Form Error List and an Error Message above each answer that contains an error. | ||
|
||
## Guidelines | ||
|
||
- Always show a Form Error List when there is a validation error, even if there’s only one. | ||
- You must link the errors in the Form Error List to the answer they relate to (see below). | ||
|
||
## Linking from the Form Error List to each answer | ||
|
||
For questions that require a user to answer using a single field, like a file upload, select, textarea, text input or character count, link to the `id` of that field. | ||
|
||
When a user has to enter their answer into multiple fields, such as day, month and year fields, link to the `id` of the first field that contains an error. | ||
If you do not know which field contains an error, link to the `id` of the first field. | ||
|
||
For questions that require a user to select one or more options from a list using Radios or Checkboxes, link to the `id` of the first Radio or Checkbox. | ||
|
||
## Where to put the Form Error List | ||
|
||
Put the Form Error List at the top of the main container, outside of the `<form>`-tag. If your page includes breadcrumbs or a back link, place it below these, but above the `<h1>`. | ||
|
||
## Relevant WCAG requirements | ||
|
||
Pay extra attention to these parts: | ||
|
||
- [WCAG requirement 1.3.1](https://www.w3.org/TR/WCAG21/#info-and-relationships): the heading level of the Form Error List depends on where in the page it is placed, this may differ per page. | ||
|
||
## References | ||
|
||
- [Show an error summary above the form - NL Design System](https://www.nldesignsystem.nl/richtlijnen/formulieren/foutmeldingen#zet-een-samenvatting-van-de-foutmeldingen-boven-het-formulier) | ||
- [Error Summary component - Gov.uk](https://design-system.service.gov.uk/components/error-summary/) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
import { render, screen } from '@testing-library/react' | ||
import { createRef } from 'react' | ||
import { FormErrorList } from './FormErrorList' | ||
import '@testing-library/jest-dom' | ||
|
||
describe('Form error list', () => { | ||
const testErrors = [ | ||
{ id: '#', label: 'Vul een geldige datum in (bijvoorbeeld 6 januari 2030).' }, | ||
{ id: '#', label: 'De geldigheidsdatum van uw paspoort moet in de toekomst liggen.' }, | ||
] | ||
|
||
it('renders', () => { | ||
render(<FormErrorList errors={testErrors} />) | ||
|
||
const component = screen.getByRole('alert') | ||
|
||
expect(component).toBeInTheDocument() | ||
expect(component).toBeVisible() | ||
}) | ||
|
||
it('does not render when there are no errors', () => { | ||
render(<FormErrorList errors={[]} />) | ||
|
||
const component = screen.queryByRole('alert') | ||
|
||
expect(component).not.toBeInTheDocument() | ||
}) | ||
|
||
it('renders a design system BEM class name', () => { | ||
render(<FormErrorList errors={testErrors} />) | ||
|
||
const component = screen.getByRole('alert') | ||
|
||
expect(component).toHaveClass('ams-form-error-list') | ||
}) | ||
|
||
it('renders an additional class name', () => { | ||
render(<FormErrorList errors={testErrors} className="extra" />) | ||
|
||
const component = screen.getByRole('alert') | ||
|
||
expect(component).toHaveClass('ams-form-error-list extra') | ||
}) | ||
|
||
it('renders a list item and link for every error', () => { | ||
render(<FormErrorList errors={testErrors} />) | ||
|
||
const listitems = screen.getAllByRole('listitem') | ||
const links = screen.getAllByRole('link') | ||
|
||
expect(listitems.length).toBe(2) | ||
expect(links.length).toBe(2) | ||
}) | ||
|
||
it('renders a link with the correct name and href for every error', () => { | ||
render(<FormErrorList errors={testErrors} />) | ||
|
||
const link1 = screen.getByRole('link', { name: testErrors[0].label }) | ||
const link2 = screen.getByRole('link', { name: testErrors[1].label }) | ||
|
||
expect(link1).toHaveAttribute('href', testErrors[0].id) | ||
expect(link2).toHaveAttribute('href', testErrors[1].id) | ||
}) | ||
|
||
it('renders a custom heading', () => { | ||
render(<FormErrorList errors={testErrors} heading="Test heading" />) | ||
|
||
const component = screen.getByRole('heading', { name: 'Test heading' }) | ||
|
||
expect(component).toBeInTheDocument() | ||
}) | ||
|
||
it('renders the correct heading level', () => { | ||
render(<FormErrorList errors={testErrors} headingLevel={4} />) | ||
|
||
const component = screen.getByRole('heading', { level: 4 }) | ||
|
||
expect(component).toBeInTheDocument() | ||
}) | ||
|
||
it('supports ForwardRef in React', () => { | ||
const ref = createRef<HTMLDivElement>() | ||
|
||
render(<FormErrorList errors={testErrors} ref={ref} />) | ||
|
||
const component = screen.getByRole('alert') | ||
|
||
expect(ref.current).toBe(component) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
/** | ||
* @license EUPL-1.2+ | ||
* Copyright Gemeente Amsterdam | ||
*/ | ||
|
||
import clsx from 'clsx' | ||
import { forwardRef } from 'react' | ||
import type { ForwardedRef, HTMLAttributes } from 'react' | ||
import { Alert } from '../Alert' | ||
import type { HeadingLevel } from '../Heading' | ||
import { LinkList } from '../LinkList' | ||
|
||
export type FormError = { | ||
id: string | ||
label: string | ||
} | ||
|
||
export type FormErrorListProps = { | ||
/** The list of error messages to display. */ | ||
errors: FormError[] | ||
/** The text for the Heading. */ | ||
heading?: string | ||
/** | ||
* The hierarchical level of the Heading within the document. | ||
* Note: this intentionally does not change the font size. | ||
*/ | ||
headingLevel?: HeadingLevel | ||
} & HTMLAttributes<HTMLDivElement> | ||
|
||
export const FormErrorList = forwardRef( | ||
( | ||
{ | ||
className, | ||
errors, | ||
heading = 'Verbeter de fouten voor u verder gaat', | ||
headingLevel = 2, | ||
...restProps | ||
}: FormErrorListProps, | ||
ref: ForwardedRef<HTMLDivElement>, | ||
) => { | ||
if (errors.length === 0) return undefined | ||
|
||
return ( | ||
<Alert | ||
{...restProps} | ||
className={clsx('ams-form-error-list', className)} | ||
heading={heading} | ||
headingLevel={headingLevel} | ||
ref={ref} | ||
role="alert" | ||
severity="error" | ||
> | ||
<LinkList> | ||
{errors.map(({ id, label }) => ( | ||
<LinkList.Link href={id} key={`${id}-${label}`}> | ||
{label} | ||
</LinkList.Link> | ||
))} | ||
</LinkList> | ||
</Alert> | ||
) | ||
}, | ||
) | ||
|
||
FormErrorList.displayName = 'FormErrorList' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
<!-- @license CC0-1.0 --> | ||
|
||
# React Form Error List component | ||
|
||
[Form Error List documentation](../../../css/src/components/form-error-list/README.md) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export { FormErrorList } from './FormErrorList' | ||
export type { FormError, FormErrorListProps } from './FormErrorList' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,2 @@ | ||
export type { HeadingProps } from './Heading' | ||
export { Heading } from './Heading' | ||
export type { HeadingLevel, HeadingProps } from './Heading' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
11 changes: 11 additions & 0 deletions
11
storybook/src/components/FormErrorList/FormErrorList.docs.mdx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { Controls, Markdown, Meta, Primary } from "@storybook/blocks"; | ||
import * as FormErrorListStories from "./FormErrorList.stories.tsx"; | ||
import README from "../../../../packages/css/src/components/form-error-list/README.md?raw"; | ||
|
||
<Meta of={FormErrorListStories} /> | ||
|
||
<Markdown>{README}</Markdown> | ||
|
||
<Primary /> | ||
|
||
<Controls /> |
24 changes: 24 additions & 0 deletions
24
storybook/src/components/FormErrorList/FormErrorList.stories.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
/** | ||
* @license EUPL-1.2+ | ||
* Copyright Gemeente Amsterdam | ||
*/ | ||
|
||
import { FormErrorList } from '@amsterdam/design-system-react/src' | ||
import { Meta, StoryObj } from '@storybook/react' | ||
|
||
const meta = { | ||
title: 'Components/Forms/Form Error List', | ||
component: FormErrorList, | ||
args: { | ||
errors: [ | ||
{ id: '#', label: 'Vul een geldige datum in (bijvoorbeeld 6 januari 2030).' }, | ||
{ id: '#', label: 'De geldigheidsdatum van uw paspoort moet in de toekomst liggen.' }, | ||
], | ||
}, | ||
} satisfies Meta<typeof FormErrorList> | ||
|
||
export default meta | ||
|
||
type Story = StoryObj<typeof meta> | ||
|
||
export const Default: Story = {} |