From 9bbb43a42963b5985de9f3b9bd3a76531b74f799 Mon Sep 17 00:00:00 2001 From: Vincent Smedinga Date: Mon, 11 Nov 2024 10:13:23 +0100 Subject: [PATCH] feat: Let Grid and Grid Cell render any structural tag (#1662) Co-authored-by: Aram <37216945+alimpens@users.noreply.github.com> --- packages/react/src/Breakout/BreakoutCell.tsx | 5 ++- packages/react/src/Grid/Grid.test.tsx | 22 +++++++++-- packages/react/src/Grid/Grid.tsx | 22 +++++++++-- packages/react/src/Grid/GridCell.test.tsx | 39 ++++++++++++------- packages/react/src/Grid/GridCell.tsx | 8 +++- packages/react/src/common/accessibility.ts | 14 +++++++ storybook/src/components/Grid/Grid.docs.mdx | 4 +- .../src/components/Grid/Grid.stories.tsx | 4 -- 8 files changed, 87 insertions(+), 31 deletions(-) create mode 100644 packages/react/src/common/accessibility.ts diff --git a/packages/react/src/Breakout/BreakoutCell.tsx b/packages/react/src/Breakout/BreakoutCell.tsx index 76855d267b..48bf05327d 100644 --- a/packages/react/src/Breakout/BreakoutCell.tsx +++ b/packages/react/src/Breakout/BreakoutCell.tsx @@ -2,9 +2,10 @@ * @license EUPL-1.2+ * Copyright Gemeente Amsterdam */ + import clsx from 'clsx' import { forwardRef } from 'react' -import type { HTMLAttributes, PropsWithChildren } from 'react' +import type { ForwardedRef, HTMLAttributes, PropsWithChildren } from 'react' import type { BreakoutRowNumber, BreakoutRowNumbers } from './Breakout' import { breakoutCellClasses } from './breakoutCellClasses' import type { GridColumnNumber, GridColumnNumbers } from '../Grid/Grid' @@ -54,7 +55,7 @@ export const BreakoutCell = forwardRef( rowStart, ...restProps }: BreakoutCellProps, - ref: any, + ref: ForwardedRef, ) => ( { }) }) + 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.getByRole(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 1bd23eb7c5..e39108ff32 100644 --- a/packages/react/src/Grid/Grid.tsx +++ b/packages/react/src/Grid/Grid.tsx @@ -17,6 +17,9 @@ export type GridColumnNumbers = { } 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 @@ -33,6 +36,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) & @@ -40,10 +45,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: ForwardedRef, ) => ( -
{children} -
+
), ) diff --git a/packages/react/src/Grid/GridCell.test.tsx b/packages/react/src/Grid/GridCell.test.tsx index d96fee4ec9..88360fcb3e 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.getByRole(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 65116c5bf5..e38db74efd 100644 --- a/storybook/src/components/Grid/Grid.stories.tsx +++ b/storybook/src/components/Grid/Grid.stories.tsx @@ -57,10 +57,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 }, },