diff --git a/packages/react/src/Grid/Grid.test.tsx b/packages/react/src/Grid/Grid.test.tsx index 475a1071b9..647dd4846a 100644 --- a/packages/react/src/Grid/Grid.test.tsx +++ b/packages/react/src/Grid/Grid.test.tsx @@ -1,7 +1,8 @@ -import { render } from '@testing-library/react' +import { render, screen } from '@testing-library/react' import { createRef } from 'react' -import { Grid } from './Grid' +import { Grid, gridTags } from './Grid' import type { GridPaddingSize } from './Grid' +import { ariaRoleForTag } from '../common/accessibility' import '@testing-library/jest-dom' const paddingSizes = ['small', 'medium', 'large'] @@ -87,8 +88,23 @@ describe('Grid', () => { }) }) + gridTags.forEach((tag) => { + it(`renders with a custom ${tag} tag`, () => { + const { container } = render() + + let component: HTMLElement | null + if (tag === 'div') { + component = container.querySelector(tag) + } else { + component = screen.queryByRole(ariaRoleForTag[tag]) + } + + expect(component).toBeInTheDocument() + }) + }) + it('supports ForwardRef in React', () => { - const ref = createRef() + const ref = createRef() const { container } = render() diff --git a/packages/react/src/Grid/Grid.tsx b/packages/react/src/Grid/Grid.tsx index f0b8227dee..1291b952ef 100644 --- a/packages/react/src/Grid/Grid.tsx +++ b/packages/react/src/Grid/Grid.tsx @@ -5,7 +5,7 @@ import clsx from 'clsx' import { forwardRef } from 'react' -import type { ForwardedRef, HTMLAttributes, PropsWithChildren } from 'react' +import type { HTMLAttributes, PropsWithChildren } from 'react' import { GridCell } from './GridCell' import { paddingClasses } from './paddingClasses' @@ -13,6 +13,9 @@ export type GridColumnNumber = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 export type GridColumnNumbers = { narrow: GridColumnNumber; medium: GridColumnNumber; wide: GridColumnNumber } export type GridPaddingSize = 'small' | 'medium' | 'large' +export const gridTags = ['article', 'aside', 'div', 'footer', 'header', 'main', 'nav', 'section'] as const +export type GridTag = (typeof gridTags)[number] + type GridPaddingVerticalProp = { paddingBottom?: never paddingTop?: never @@ -29,6 +32,8 @@ type GridPaddingTopAndBottomProps = { } export type GridProps = { + /** The HTML tag to use. */ + as?: GridTag /** The amount of space between rows. */ gapVertical?: 'none' | 'small' | 'large' } & (GridPaddingVerticalProp | GridPaddingTopAndBottomProps) & @@ -36,10 +41,19 @@ export type GridProps = { const GridRoot = forwardRef( ( - { children, className, gapVertical, paddingBottom, paddingTop, paddingVertical, ...restProps }: GridProps, - ref: ForwardedRef, + { + as: Tag = 'div', + children, + className, + gapVertical, + paddingBottom, + paddingTop, + paddingVertical, + ...restProps + }: GridProps, + ref: any, ) => ( -
{children} -
+ ), ) diff --git a/packages/react/src/Grid/GridCell.test.tsx b/packages/react/src/Grid/GridCell.test.tsx index d96fee4ec9..bb2f1351af 100644 --- a/packages/react/src/Grid/GridCell.test.tsx +++ b/packages/react/src/Grid/GridCell.test.tsx @@ -1,6 +1,8 @@ import { render, screen } from '@testing-library/react' import { createRef } from 'react' import { Grid } from './Grid' +import { gridCellTags } from './GridCell' +import { ariaRoleForTag } from '../common/accessibility' import '@testing-library/jest-dom' describe('Grid cell', () => { @@ -29,16 +31,6 @@ describe('Grid cell', () => { expect(component).toHaveClass('ams-grid__cell extra') }) - it('supports ForwardRef in React', () => { - const ref = createRef() - - const { container } = render() - - const component = container.querySelector(':only-child') - - expect(ref.current).toBe(component) - }) - it('renders no class names for undefined values for start and span', () => { const { container } = render() @@ -113,11 +105,30 @@ describe('Grid cell', () => { expect(component).toHaveClass('ams-grid__cell--span-all') }) - it('renders a custom tag', () => { - render() + gridCellTags.forEach((tag) => { + it(`renders with a custom ${tag} tag`, () => { + const { container } = render( + , + ) + + let component: HTMLElement | null + if (tag === 'div') { + component = container.querySelector(tag) + } else { + component = screen.queryByRole(ariaRoleForTag[tag]) + } + + expect(component).toBeInTheDocument() + }) + }) + + it('supports ForwardRef in React', () => { + const ref = createRef() + + const { container } = render() - const cell = screen.getByRole('article') + const component = container.querySelector(':only-child') - expect(cell).toBeInTheDocument() + expect(ref.current).toBe(component) }) }) diff --git a/packages/react/src/Grid/GridCell.tsx b/packages/react/src/Grid/GridCell.tsx index 88ce51ade1..ef3bdaa799 100644 --- a/packages/react/src/Grid/GridCell.tsx +++ b/packages/react/src/Grid/GridCell.tsx @@ -2,12 +2,16 @@ * @license EUPL-1.2+ * Copyright Gemeente Amsterdam */ + import clsx from 'clsx' import { forwardRef } from 'react' import type { HTMLAttributes, PropsWithChildren } from 'react' import type { GridColumnNumber, GridColumnNumbers } from './Grid' import { gridCellClasses } from './gridCellClasses' +export const gridCellTags = ['article', 'aside', 'div', 'footer', 'header', 'main', 'nav', 'section'] as const +export type GridCellTag = (typeof gridCellTags)[number] + type GridCellSpanAllProp = { /** Lets the cell span the full width of all grid variants. */ span: 'all' @@ -22,8 +26,8 @@ type GridCellSpanAndStartProps = { } export type GridCellProps = { - /** The HTML element to use. */ - as?: 'article' | 'div' | 'section' + /** The HTML tag to use. */ + as?: GridCellTag } & (GridCellSpanAllProp | GridCellSpanAndStartProps) & PropsWithChildren> diff --git a/packages/react/src/common/accessibility.ts b/packages/react/src/common/accessibility.ts new file mode 100644 index 0000000000..493bc79bcd --- /dev/null +++ b/packages/react/src/common/accessibility.ts @@ -0,0 +1,14 @@ +/** + * @license EUPL-1.2+ + * Copyright Gemeente Amsterdam + */ + +export const ariaRoleForTag: Record = { + article: 'article', + aside: 'complementary', + footer: 'contentinfo', + header: 'banner', + main: 'main', + nav: 'navigation', + section: 'region', +} diff --git a/storybook/src/components/Grid/Grid.docs.mdx b/storybook/src/components/Grid/Grid.docs.mdx index d3fcbf108c..5b2bceaa0f 100644 --- a/storybook/src/components/Grid/Grid.docs.mdx +++ b/storybook/src/components/Grid/Grid.docs.mdx @@ -71,8 +71,8 @@ Use the `start` prop with 3 values, e.g. `start={{ narrow: 2, medium: 4, wide: 6 ### Improve semantics -By default, a Grid Cell renders a `
` element in HTML. -Use the `as` prop to make your markup more semantic. +By default, both Grid and Grid Cell render a `
` element in HTML. +Use the `as` prop on either to make your markup more semantic. diff --git a/storybook/src/components/Grid/Grid.stories.tsx b/storybook/src/components/Grid/Grid.stories.tsx index 0e6582af49..c04b0b8a49 100644 --- a/storybook/src/components/Grid/Grid.stories.tsx +++ b/storybook/src/components/Grid/Grid.stories.tsx @@ -58,10 +58,6 @@ export default meta const cellMeta = { component: Grid.Cell, argTypes: { - as: { - control: { type: 'radio' }, - options: ['article', 'div', 'section'], - }, span: { control: { type: 'number', min: 1, max: 12 }, },