-
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.
feat: Add Skip link component (#988)
Co-authored-by: Vincent Smedinga <[email protected]>
- Loading branch information
1 parent
1b2e87d
commit 82323b5
Showing
13 changed files
with
255 additions
and
4 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
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,33 @@ | ||
# Skip Link | ||
|
||
Gebruik een Skip Link om makkelijk met het toetsenbord naar de belangrijkste inhoud te navigeren. | ||
Met een Skip Link kun je terugkerende navigatieblokken (zoals het hoofdmenu of het kruimelpad) overslaan. | ||
|
||
De Skip Link staat boven de header. | ||
De link is verborgen totdat deze met de tab-toets geactiveerd wordt. | ||
Als de link getoond wordt, duwt deze de hele pagina omlaag. | ||
|
||
## Richtlijnen | ||
|
||
### Zo gebruiken | ||
|
||
- Plaats de Skip Link als eerste element in `<body>`, tenzij je een cookie-banner hebt. | ||
Plaats de Skip Link dan direct na de cookie-banner. | ||
- Gebruik de Skip Link om naar de belangrijkste inhoud te navigeren. | ||
Op een artikelpagina is dat bijvoorbeeld de titel van het artikel, op een zoekpagina is dat het zoekveld. | ||
- Voor complexe pagina's met meerdere secties kun je meer dan 1 Skip Link gebruiken. | ||
In de meeste gevallen is dit niet nodig. | ||
|
||
### Dit vermijden | ||
|
||
- Skip Links zijn niet nodig op een simpele pagina waar alleen tekst staat en weinig navigatie. | ||
Het doel van een Skip Link is om terugkerende navigatieblokken over te slaan. | ||
Als die blokken er niet zijn, is een Skip Link niet nodig. | ||
- Plaats de Skip Link niet in een `nav` regio, of in de Header. | ||
|
||
## Relevante WCAG eisen | ||
|
||
- Voor dit component gelden dezelfde WCAG eisen als voor [het link component](https://amsterdam.github.io/design-system/?path=/docs/react_navigation-link--docs). | ||
- [WCAG 2.4.1](https://www.w3.org/TR/WCAG22/#bypass-blocks): gebruik een Skip Link op elke pagina die begint met een terugkerend navigatieblok. | ||
- [WCAG 3.2.3](https://www.w3.org/TR/WCAG22/#consistent-navigation): een Skip Link staat op elke pagina op dezelfde plek. | ||
- [WCAG 3.2.4](https://www.w3.org/TR/WCAG22/#consistent-identification): een Skip Link heeft dezelfde labels op alle pagina's. Bijvoorbeeld niet: "Navigatie overslaan" op een gedeelte van de site, en "Naar de inhoud" op andere pagina's. |
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,28 @@ | ||
/** | ||
* @license EUPL-1.2+ | ||
* Copyright (c) 2023 Gemeente Amsterdam | ||
*/ | ||
|
||
.amsterdam-skip-link { | ||
background-color: var(--amsterdam-skip-link-background-color); | ||
color: var(--amsterdam-skip-link-color); | ||
display: block; | ||
font-family: var(--amsterdam-skip-link-font-family); | ||
font-size: var(--amsterdam-skip-link-font-size); | ||
font-weight: var(--amsterdam-skip-link-font-weight); | ||
line-height: var(--amsterdam-skip-link-line-height); | ||
outline-offset: var(--amsterdam-skip-link-outline-offset); | ||
padding-block: 0.5rem; | ||
padding-inline: 1rem; | ||
text-align: center; | ||
text-decoration: none; | ||
|
||
&:hover { | ||
background-color: var(--amsterdam-skip-link-hover-background-color); | ||
} | ||
|
||
.amsterdam-theme--compact & { | ||
font-size: var(--amsterdam-skip-link-compact-font-size); | ||
line-height: var(--amsterdam-skip-link-compact-line-height); | ||
} | ||
} |
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,3 @@ | ||
# React Skip Link component | ||
|
||
[Skip Link documentation](../../../css/src/skip-link/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,41 @@ | ||
import { render, screen } from '@testing-library/react' | ||
import { createRef } from 'react' | ||
import { SkipLink } from './SkipLink' | ||
import '@testing-library/jest-dom' | ||
|
||
describe('Skip Link', () => { | ||
it('renders', () => { | ||
render(<SkipLink href="/" />) | ||
|
||
const component = screen.getByRole('link') | ||
|
||
expect(component).toBeInTheDocument() | ||
expect(component).toBeVisible() | ||
}) | ||
|
||
it('renders a design system BEM class name', () => { | ||
render(<SkipLink href="/" />) | ||
|
||
const component = screen.getByRole('link') | ||
|
||
expect(component).toHaveClass('amsterdam-skip-link') | ||
}) | ||
|
||
it('renders an additional class name', () => { | ||
render(<SkipLink href="/" className="extra" />) | ||
|
||
const component = screen.getByRole('link') | ||
|
||
expect(component).toHaveClass('amsterdam-skip-link extra') | ||
}) | ||
|
||
it('supports ForwardRef in React', () => { | ||
const ref = createRef<HTMLAnchorElement>() | ||
|
||
render(<SkipLink href="/" ref={ref} />) | ||
|
||
const component = screen.getByRole('link') | ||
|
||
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,20 @@ | ||
/** | ||
* @license EUPL-1.2+ | ||
* Copyright (c) 2023 Gemeente Amsterdam | ||
*/ | ||
|
||
import clsx from 'clsx' | ||
import { forwardRef } from 'react' | ||
import type { AnchorHTMLAttributes, ForwardedRef, PropsWithChildren } from 'react' | ||
|
||
export interface SkipLinkProps extends PropsWithChildren<AnchorHTMLAttributes<HTMLAnchorElement>> {} | ||
|
||
export const SkipLink = forwardRef( | ||
({ children, className, ...restProps }: SkipLinkProps, ref: ForwardedRef<HTMLAnchorElement>) => ( | ||
<a {...restProps} ref={ref} className={clsx('amsterdam-skip-link', 'amsterdam-visually-hidden', className)}> | ||
{children} | ||
</a> | ||
), | ||
) | ||
|
||
SkipLink.displayName = 'SkipLink' |
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 { SkipLink } from './SkipLink' | ||
export type { SkipLinkProps } from './SkipLink' |
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
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
20 changes: 20 additions & 0 deletions
20
proprietary/tokens/src/components/amsterdam/skip-link.tokens.json
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,20 @@ | ||
{ | ||
"amsterdam": { | ||
"skip-link": { | ||
"background-color": { "value": "{amsterdam.color.primary-blue}" }, | ||
"color": { "value": "{amsterdam.color.primary-white}" }, | ||
"font-family": { "value": "{amsterdam.typography.font-family}" }, | ||
"font-weight": { "value": "{amsterdam.typography.font-weight.normal}" }, | ||
"font-size": { "value": "{amsterdam.typography.spacious.text-level.6.font-size}" }, | ||
"line-height": { "value": "{amsterdam.typography.spacious.text-level.6.line-height}" }, | ||
"outline-offset": { "value": "{amsterdam.focus.outline-offset}" }, | ||
"compact": { | ||
"font-size": { "value": "{amsterdam.typography.compact.text-level.6.font-size}" }, | ||
"line-height": { "value": "{amsterdam.typography.compact.text-level.6.line-height}" } | ||
}, | ||
"hover": { | ||
"background-color": { "value": "{amsterdam.color.dark-blue}" } | ||
} | ||
} | ||
} | ||
} |
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 @@ | ||
import { Canvas, Controls, Markdown, Meta, Primary } from "@storybook/blocks"; | ||
import * as SkipLinkStories from "./SkipLink.stories.tsx"; | ||
import README from "../../../../packages/css/src/components/skip-link/README.md?raw"; | ||
|
||
<Meta of={SkipLinkStories} /> | ||
|
||
<Markdown>{README}</Markdown> | ||
|
||
<Primary /> | ||
|
||
<Controls /> | ||
|
||
## Toon bij focus | ||
|
||
Een Skip Link wordt pas getoond als deze focus krijgt. | ||
|
||
<Canvas of={SkipLinkStories.OnFocus} /> | ||
|
||
## Meerdere links | ||
|
||
Als je een complexe pagina met meerdere secties hebt, kun je meer dan 1 Skip Link gebruiken. | ||
In de meeste gevallen is dit niet nodig. | ||
|
||
<Canvas of={SkipLinkStories.MultipleLinks} /> |
79 changes: 79 additions & 0 deletions
79
storybook/storybook-react/src/SkipLink/SkipLink.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,79 @@ | ||
/** | ||
* @license EUPL-1.2+ | ||
* Copyright (c) 2023 Gemeente Amsterdam | ||
*/ | ||
|
||
import { Grid, Paragraph, Screen, SkipLink } from '@amsterdam/design-system-react' | ||
import { Meta, StoryObj } from '@storybook/react' | ||
|
||
const meta = { | ||
title: 'Navigation/Skip Link', | ||
component: SkipLink, | ||
args: { | ||
children: 'Direct naar inhoud', | ||
href: '#', | ||
}, | ||
argTypes: { | ||
style: { | ||
table: { | ||
disable: true, | ||
}, | ||
}, | ||
}, | ||
decorators: [ | ||
(Story) => ( | ||
<Screen> | ||
<Grid> | ||
<Grid.Cell span="all"> | ||
<Story /> | ||
</Grid.Cell> | ||
</Grid> | ||
</Screen> | ||
), | ||
], | ||
} satisfies Meta<typeof SkipLink> | ||
|
||
export default meta | ||
|
||
type Story = StoryObj<typeof meta> | ||
|
||
export const Default: Story = { | ||
args: { | ||
// This resets the default behaviour of only showing the link | ||
// on focus, in order to always show the link in Storybook | ||
style: { | ||
clip: 'initial', | ||
clipPath: 'initial', | ||
height: 'initial', | ||
overflow: 'initial', | ||
position: 'initial', | ||
whiteSpace: 'initial', | ||
width: 'initial', | ||
}, | ||
}, | ||
} | ||
|
||
export const OnFocus: Story = { | ||
decorators: [ | ||
(Story) => ( | ||
<> | ||
<Paragraph size="small" style={{ marginBottom: '2rem' }}> | ||
Klik op deze tekst en druk vervolgens op tab om de Skip Link te tonen. | ||
</Paragraph> | ||
<Story /> | ||
</> | ||
), | ||
], | ||
} | ||
|
||
export const MultipleLinks: Story = { | ||
render: () => ( | ||
<> | ||
<Paragraph size="small" style={{ marginBottom: '2rem' }}> | ||
Klik op deze tekst en druk vervolgens twee keer op tab om de Skip Links te tonen. | ||
</Paragraph> | ||
<SkipLink href="#">Direct naar inhoud</SkipLink> | ||
<SkipLink href="#">Direct naar contactgegevens</SkipLink> | ||
</> | ||
), | ||
} |