From a91da021cb9fafc58fda13ed33f8303a4dbbea10 Mon Sep 17 00:00:00 2001 From: Robbert Broersma Date: Thu, 22 Jun 2023 13:58:12 +0200 Subject: [PATCH] feat: actions slot for alert --- components/alert/README.md | 58 +++++++++++++ components/alert/_alert-actions.md | 9 ++ components/alert/src/_mixin.scss | 4 + components/alert/src/index.scss | 4 + .../component-library-react/src/Alert.tsx | 4 +- packages/storybook-css/src/Alert.stories.tsx | 85 +++++++++++++++++++ packages/storybook-css/src/Alert.tsx | 4 +- .../src/stories/Alert.stories.tsx | 43 +++++++++- 8 files changed, 207 insertions(+), 4 deletions(-) create mode 100644 components/alert/_alert-actions.md diff --git a/components/alert/README.md b/components/alert/README.md index c6334606463..0c113015b2d 100644 --- a/components/alert/README.md +++ b/components/alert/README.md @@ -3,3 +3,61 @@ # Alert Belangrijk bericht dat informeert over de huidige activiteit van de gebruiker. + +## Toepassing + +De _alert component_ is er voor berichten die de gebruiker snel moet weten, omdat ze belangrijk zijn voor het uitvoeren van de huidige taak. De alert is alleen voor eenvoudige berichten. Gebruik in de alert geen buttons, geen formulier-componenten en geen complexe opmaak zoals tabellen. + +Let op: de alert component gebruiken kan essentieël zijn voor gebruikers van een schermvoorlezer, maar onjuist gebruik kan heel erg vervelend zijn. + +Gebruik niet een alert voor een algemene aankondiging die op meerdere pagina's staat, als het niet per se relevant is voor de huidige taak van de bezoeker. De alert wordt door schermvoorlezers als eerste voorgelezen op elke pagina waar de alert staat, het kan lastig zijn de website te gebruiken als de schermlezer elke keer wordt geblokkeerd met steeds dezelfde melding. Gebruik in die situaties de notification banner component. + +## Componenten die lijken op alert + +- de _alert_: wordt zo snel mogelijk aangekondigd. +- de _notification banner_: wordt ook snel aangekondigd, omdat het één van eerste onderdelen van de pagina is als je de banner plaatst aan het begin van de page header. Je kunt de banner overslaan een _skip link_. +- de _error appearance_ van de _form field description_: voor feedback in een formulier die hoort bij een form control. Wordt aangekondigd door schermlezers wanneer de form control focus heeft. + +## Tekst + +Schrijf een bericht voor de alert die ook duidelijk is als de gebruiker geen icoon of signaalkleur ziet. + +Let op: als de tekst met een script aangepast wordt dan wordt de alert in zijn geheel nogmaals voorgelezen door een schermvoorlezer. Een tekst met een veranderend onderdeel zoals "Over 14 minuten en 59 seconden verloopt je sessie.", heeft als effect dat de schermlezer alleen nog de alert voorleest en de pagina verder onbruikbaar is. + +## HTML + +Het belangrijkste onderdeel van de alert is het bericht, plaats die in een `
`. + +Gebruik bij voorkeur een _paragraph component_ voor de inhoud van het bericht, voor een goede `font-size` en `line-height` en zodat de alert `margin` heeft wanneer de CSS niet geladen kan worden. + +Als je wel een button gebruikt (bijvoorbeeld om de alert te verbergen), plaats die dan nooit binnen het element met `role="alert"`, maar daarbuiten. + +```html +
+
+ +
+ +
+ +
+
+``` + +## _Actions_ + +Zorg dat er alternatieve manieren zijn om de acties in een alert uit te voeren. Als er geen alternatieve manier is, gebruik dan een _alert dialog_ in plaats van een _alert_. + +Actions zoals buttons worden in de praktijk vaak gebruikt bij een alert, maar er is nog niet een duidelijk stappenplan om de acties toegankelijk te maken. + +Screen reader gebruikers krijgen alleen het bericht in de _message_, omdat die `aria-live="true"` heeft. Links en buttons in de _message_ worden niet aangekondigd als link of button. De toetsenbord focus kan heel ergens anders op de pagina zijn, waardoor het voor een gebruiker onduidelijk hoe je een _call to action_ moet uitvoeren. + +## Relevante WCAG eisen + +- [WCAG eis 1.1.1](https://www.w3.org/TR/WCAG21/#non-text-content): als de alert een icoon gebruikt met een bepaalde betekenis, moet de betekenis ook uit de tekst blijken. +- [WCAG eis 1.4.1](https://www.w3.org/TR/WCAG21/#use-of-color): als de alert een signaalkleur gebruikt moet de tekst datzelfde ook duidelijk maken, bijvoorbeeld met signaalwoorden. Gebruik bijvoorbeeld "Let op:" voor een waarschuwing. +- [WCAG eis 2.2.1](https://www.w3.org/TR/WCAG21/#timing-adjustable): laat de alert niet automatisch verdwijnen na een bepaalde tijd (lees de WCAG specificatie voor enkele uitzonderingen). + > > > > > > > e9a92eb92f (feat: actions slot for alert) diff --git a/components/alert/_alert-actions.md b/components/alert/_alert-actions.md new file mode 100644 index 00000000000..ef7cf9faf84 --- /dev/null +++ b/components/alert/_alert-actions.md @@ -0,0 +1,9 @@ + + + + +Zorg dat er alternatieve manieren zijn om de acties in een alert uit te voeren. Als er geen alternatieve manier is, gebruik dan een _alert dialog_ in plaats van een _alert_. + +Actions zoals buttons worden in de praktijk vaak gebruikt bij een alert, maar er is nog niet een duidelijk stappenplan om de acties toegankelijk te maken. + +Screen reader gebruikers krijgen alleen het bericht in de _message_, omdat die `aria-live="true"` heeft. Links en buttons in de _message_ worden niet aangekondigd als link of button. De toetsenbord focus kan heel ergens anders op de pagina zijn, waardoor het voor een gebruiker onduidelijk hoe je een _call to action_ moet uitvoeren. diff --git a/components/alert/src/_mixin.scss b/components/alert/src/_mixin.scss index 0622c5759d1..c7a95e0da9a 100644 --- a/components/alert/src/_mixin.scss +++ b/components/alert/src/_mixin.scss @@ -37,6 +37,10 @@ row-gap: var(--utrecht-alert-message-row-gap); } +@mixin utrecht-alert__actions { + grid-area: actions; +} + @mixin utrecht-alert-type($type) { --_utrecht-alert-icon-color: var(--utrecht-alert-icon-#{$type}-color); --_utrecht-alert-background-color: var(--utrecht-alert-#{$type}-background-color); diff --git a/components/alert/src/index.scss b/components/alert/src/index.scss index 6253c264e71..1f73c0344a5 100644 --- a/components/alert/src/index.scss +++ b/components/alert/src/index.scss @@ -10,6 +10,10 @@ @include utrecht-alert; } +.utrecht-alert__actions { + @include utrecht-alert__actions; +} + .utrecht-alert__icon { @include utrecht-alert__icon; } diff --git a/packages/component-library-react/src/Alert.tsx b/packages/component-library-react/src/Alert.tsx index f1795912612..37ce7a85b27 100644 --- a/packages/component-library-react/src/Alert.tsx +++ b/packages/component-library-react/src/Alert.tsx @@ -10,13 +10,14 @@ import { ForwardedRef, forwardRef, HTMLAttributes, PropsWithChildren, ReactNode export type AlertType = 'info' | 'ok' | 'warning' | 'error'; export interface AlertProps extends HTMLAttributes { + actions?: ReactNode; icon?: ReactNode; type?: string | AlertType; } export const Alert = forwardRef( ( - { children, className, icon, type, ...restProps }: PropsWithChildren, + { actions, children, className, icon, type, ...restProps }: PropsWithChildren, ref: ForwardedRef, ) => (
{children}
+ {actions &&
{actions}
}
), diff --git a/packages/storybook-css/src/Alert.stories.tsx b/packages/storybook-css/src/Alert.stories.tsx index 5c18b579734..a364b5a5da0 100644 --- a/packages/storybook-css/src/Alert.stories.tsx +++ b/packages/storybook-css/src/Alert.stories.tsx @@ -8,6 +8,14 @@ import technologyHtmlDocs from '@utrecht/alert-css/docs/technology-html.nl.md?ra import usageDocs from '@utrecht/alert-css/docs/usage.nl.md?raw'; import wcagDocs from '@utrecht/alert-css/docs/wcag.nl.md?raw'; import tokensDefinition from '@utrecht/alert-css/src/tokens.json'; +import { + Button, + ButtonGroup, + Link, + UnorderedList, + UnorderedListItem, +} from '@utrecht/component-library-react/src/css-module'; +import alertActions from '@utrecht/components/alert/_alert-actions.md?raw'; import tokens from '@utrecht/design-tokens/dist/index.json'; import iconSet from '@utrecht/icon/dist/index.json'; import { mergeMarkdown } from '@utrecht/storybook-helpers/src/markdown'; @@ -93,24 +101,101 @@ export const Info: Story = { args: { type: 'info', }, + parameters: { + status: { + type: 'ALPHA', + }, + }, }; export const OK: Story = { args: { type: 'ok', }, + parameters: { + status: { + type: 'ALPHA', + }, + }, }; export const Warning: Story = { args: { type: 'warning', }, + parameters: { + status: { + type: 'ALPHA', + }, + }, }; export const Error: Story = { args: { type: 'error', }, + parameters: { + status: { + type: 'ALPHA', + }, + }, +}; + +export const CallToActionLinks: Story = { + parameters: { + docs: { + description: { + story: `De link-teksten in dit voorbeeld zijn speciaal zo geschreven dat het ook duidelijke instructies zijn wanneer je niet weet dat het links zijn. + +Als je de link-teksten goed schrijft dan is de alert ook duidelijk wanneer een _screen reader_ de alert aankondigt.`, + }, + }, + status: { + type: 'WORK IN PROGRESS', + }, + }, + args: { + type: 'error', + children: ( + <> + Probleem met ingevulde gegevens + + + Vul je voornaam in. + + + Vul een postcode in, bijvoorbeeld: 1011 AB. + + + + ), + }, +}; + +export const ActionsWarning: Story = { + parameters: { + docs: { + description: { + story: alertActions, + }, + }, + status: { + type: 'WORK IN PROGRESS', + }, + }, + args: { + type: 'warning', + children: ( + + De sessie is afgelopen, omdat je 15 minuten niets hebt gedaan. Je kan weer opnieuw beginnen. + + ), + actions: ( + + + + ), + }, }; export const WithIcon: Story = { diff --git a/packages/storybook-css/src/Alert.tsx b/packages/storybook-css/src/Alert.tsx index 0e17aaea943..c43e728887d 100644 --- a/packages/storybook-css/src/Alert.tsx +++ b/packages/storybook-css/src/Alert.tsx @@ -4,11 +4,12 @@ import clsx from 'clsx'; import React, { PropsWithChildren, ReactNode } from 'react'; export interface AlertProps extends PropsWithChildren { + actions?: ReactNode; icon?: ReactNode; type?: string; } -export const Alert = ({ children, icon = null, type }: PropsWithChildren) => ( +export const Alert = ({ actions, children, icon = null, type }: PropsWithChildren) => (
+ {actions &&
{actions}
} ); diff --git a/packages/storybook-react/src/stories/Alert.stories.tsx b/packages/storybook-react/src/stories/Alert.stories.tsx index 484ff2b0953..56df5be9fd7 100644 --- a/packages/storybook-react/src/stories/Alert.stories.tsx +++ b/packages/storybook-react/src/stories/Alert.stories.tsx @@ -1,7 +1,15 @@ import type { Meta, StoryObj } from '@storybook/react'; import readme from '@utrecht/alert-css/README.md?raw'; import tokensDefinition from '@utrecht/alert-css/dist/tokens.mjs'; -import { Alert, AlertProps, Heading1, Paragraph } from '@utrecht/component-library-react/dist/css-module'; +import { + Alert, + AlertProps, + Button, + ButtonGroup, + Heading1, + Link, + Paragraph, +} from '@utrecht/component-library-react/dist/css-module'; import tokens from '@utrecht/design-tokens/dist/list.mjs'; import iconSet from '@utrecht/icon/dist/iconset.mjs'; import React from 'react'; @@ -13,7 +21,11 @@ interface AlertStoryProps extends AlertProps { const AlertStory = ({ children, icon, ...props }: AlertStoryProps) => { const IconElement = icon; - return : null}>{children}; + return ( + : null} {...props}> + {children} + + ); }; const meta = { @@ -93,4 +105,31 @@ export const WithIcon: Story = { }, }; +export const ActionsWarning: Story = { + args: { + type: 'warning', + children: ( + + De sessie is afgelopen, omdat je 15 minuten niets hebt gedaan. Je kan weer opnieuw beginnen. + + ), + actions: ( + + + + ), + }, +}; +export const LinkActionsWarning: Story = { + args: { + type: 'warning', + children: Uw sessie is verlopen., + actions: ( + + Gebruik deze link om opnieuw te beginnen. + + ), + }, +}; + export const DesignTokens = designTokenStory(meta);