From 804dbffdd133967451a0aabb49f7c777373c71f6 Mon Sep 17 00:00:00 2001 From: marcoskolodny Date: Fri, 22 Nov 2024 15:26:01 +0100 Subject: [PATCH] add aria-hidden to cards and update a11y for AdvancedDataCard --- src/__tests__/advanced-data-card-test.tsx | 124 +++++++++++++++++ src/card.tsx | 14 +- .../__stories__/advanced-data-card-story.tsx | 17 +++ src/community/advanced-data-card.css.ts | 5 + src/community/advanced-data-card.tsx | 131 +++++++++++++----- 5 files changed, 248 insertions(+), 43 deletions(-) create mode 100644 src/__tests__/advanced-data-card-test.tsx diff --git a/src/__tests__/advanced-data-card-test.tsx b/src/__tests__/advanced-data-card-test.tsx new file mode 100644 index 000000000..3a3f81989 --- /dev/null +++ b/src/__tests__/advanced-data-card-test.tsx @@ -0,0 +1,124 @@ +import * as React from 'react'; +import {AdvancedDataCard} from '../community'; +import {makeTheme} from './test-utils'; +import {render, screen} from '@testing-library/react'; +import ThemeContextProvider from '../theme-context-provider'; +import Tag from '../tag'; +import Stack from '../stack'; +import {Text2} from '../text'; + +const titleFirst = + 'Title Headline Pretitle Subtitle Description Extra line 1Extra line 2Extra line 3Extra line 4'; +const pretitleFirst = + 'Pretitle Headline Title Subtitle Description Extra line 1Extra line 2Extra line 3Extra line 4'; + +test.each` + pretitleAs | titleAs | expectedLabel + ${undefined} | ${undefined} | ${titleFirst} + ${'h1'} | ${'h2'} | ${pretitleFirst} + ${'h2'} | ${'h1'} | ${titleFirst} +`( + 'AdvancedDataCard "href" label with pretitleAs={$pretitleAs} and titleAs={$titleAs}', + async ({pretitleAs, titleAs, expectedLabel}) => { + render( + + Headline} + pretitle="Pretitle" + pretitleAs={pretitleAs} + titleAs={titleAs} + subtitle="Subtitle" + title="Title" + description="Description" + extra={[ + + Extra line 1 + Extra line 2 + , + + Extra line 3 + Extra line 4 + , + ]} + /> + + ); + + await screen.findByRole('link', {name: expectedLabel}); + } +); + +test.each` + pretitleAs | titleAs | expectedLabel + ${undefined} | ${undefined} | ${titleFirst} + ${'h1'} | ${'h2'} | ${pretitleFirst} + ${'h2'} | ${'h1'} | ${titleFirst} +`( + 'AdvancedDataCard "to" label with pretitleAs={$pretitleAs} and titleAs={$titleAs}', + async ({pretitleAs, titleAs, expectedLabel}) => { + render( + + Headline} + pretitle="Pretitle" + pretitleAs={pretitleAs} + titleAs={titleAs} + subtitle="Subtitle" + title="Title" + description="Description" + extra={[ + + Extra line 1 + Extra line 2 + , + + Extra line 3 + Extra line 4 + , + ]} + /> + + ); + + await screen.findByRole('link', {name: expectedLabel}); + } +); + +test.each` + pretitleAs | titleAs | expectedLabel + ${undefined} | ${undefined} | ${titleFirst} + ${'h1'} | ${'h2'} | ${pretitleFirst} + ${'h2'} | ${'h1'} | ${titleFirst} +`( + 'AdvancedDataCard "onPress" label with pretitleAs={$pretitleAs} and titleAs={$titleAs}', + async ({pretitleAs, titleAs, expectedLabel}) => { + render( + + {}} + headline={Headline} + pretitle="Pretitle" + pretitleAs={pretitleAs} + titleAs={titleAs} + subtitle="Subtitle" + title="Title" + description="Description" + extra={[ + + Extra line 1 + Extra line 2 + , + + Extra line 3 + Extra line 4 + , + ]} + /> + + ); + + await screen.findByRole('button', {name: expectedLabel}); + } +); diff --git a/src/card.tsx b/src/card.tsx index 4824f7ae2..5c2f570df 100644 --- a/src/card.tsx +++ b/src/card.tsx @@ -43,7 +43,7 @@ import type {VideoElement, VideoSource} from './video'; import type {ButtonLink, ButtonPrimary, ButtonSecondary} from './button'; import type {ExclusifyUnion} from './utils/utility-types'; -const useInnerText = () => { +export const useInnerText = (): {text: string; ref: (instance: HTMLElement | null) => void} => { const [text, setText] = React.useState(''); const ref: React.LegacyRef = React.useCallback((node: HTMLElement) => { @@ -638,7 +638,7 @@ export const MediaCard = React.forwardRef( aria-label={isTouchable ? ariaLabel : undefined} > {isTouchable &&
} -
+
{media}
@@ -742,7 +742,7 @@ export const NakedCard = React.forwardRef( className={styles.touchable} aria-label={isTouchable ? ariaLabel : undefined} > -
+
{isTouchable && (
-
+
{isTouchable && (
( aria-label={isTouchable ? ariaLabel : undefined} > {isTouchable &&
} -
+
{asset && ( @@ -1146,7 +1146,7 @@ export const SnapCard = React.forwardRef( aria-label={isTouchable ? ariaLabel : undefined} > {isTouchable &&
} -
+
{asset && (
( > {isTouchable &&
} -
+
{(hasImage || hasVideo) && (
diff --git a/src/community/__stories__/advanced-data-card-story.tsx b/src/community/__stories__/advanced-data-card-story.tsx index 08a254842..414a5e028 100644 --- a/src/community/__stories__/advanced-data-card-story.tsx +++ b/src/community/__stories__/advanced-data-card-story.tsx @@ -7,6 +7,7 @@ import Image from '../../image'; import StackingGroup from '../../stacking-group'; import {Placeholder} from '../../placeholder'; +import type {HeadingType} from '../../utils/types'; import type {TagType} from '../../tag'; export default { @@ -17,7 +18,9 @@ type Args = { headlineType: TagType; headline: string; pretitle: string; + pretitleAs: HeadingType; title: string; + titleAs: HeadingType; subtitle: string; description: string; stackingGroup: boolean; @@ -33,7 +36,9 @@ export const Default: StoryComponent = ({ headlineType, headline, pretitle, + pretitleAs, title, + titleAs, subtitle, description, stackingGroup, @@ -86,7 +91,9 @@ export const Default: StoryComponent = ({ } headline={headline ? {headline} : undefined} pretitle={pretitle} + pretitleAs={pretitleAs} title={title} + titleAs={titleAs} subtitle={subtitle} description={description} aria-label="aria label" @@ -109,7 +116,9 @@ Default.args = { headlineType: 'promo', headline: 'headline', pretitle: 'pretitle', + pretitleAs: 'span', title: 'title', + titleAs: 'h3', subtitle: 'subtitle', description: 'description', stackingGroup: true, @@ -138,4 +147,12 @@ Default.argTypes = { ], control: {type: 'select'}, }, + pretitleAs: { + options: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'span'], + control: {type: 'select'}, + }, + titleAs: { + options: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'span'], + control: {type: 'select'}, + }, }; diff --git a/src/community/advanced-data-card.css.ts b/src/community/advanced-data-card.css.ts index ff3951678..0fbea9ffd 100644 --- a/src/community/advanced-data-card.css.ts +++ b/src/community/advanced-data-card.css.ts @@ -264,3 +264,8 @@ export const topActionsWithoutIcon = style({ }, }, }); + +export const flexColumn = style({ + display: 'flex', + flexDirection: 'column', +}); diff --git a/src/community/advanced-data-card.tsx b/src/community/advanced-data-card.tsx index 1f945d056..4c35f89b2 100644 --- a/src/community/advanced-data-card.tsx +++ b/src/community/advanced-data-card.tsx @@ -13,11 +13,13 @@ import {vars} from '../skins/skin-contract.css'; import Box from '../box'; import Touchable from '../touchable'; import classNames from 'classnames'; -import {CardActionsGroup} from '../card'; +import {CardActionsGroup, useInnerText} from '../card'; import {useTheme} from '../hooks'; import {getPrefixedDataAttributes} from '../utils/dom'; import Inline from '../inline'; import {applyCssVars} from '../utils/css'; +import Tag from '../tag'; +import {isBiggerHeading} from '../utils/headings'; import type {PressHandler} from '../touchable'; import type {ExclusifyUnion} from '../utils/utility-types'; @@ -27,7 +29,6 @@ import type Image from '../image'; import type {ButtonPrimary, ButtonLink} from '../button'; import type {DataAttributes, HeadingType, TrackingEvent} from '../utils/types'; import type {RendersNullableElement} from '../utils/renders-element'; -import type Tag from '../tag'; import type { HighlightedValueBlock, InformationBlock, @@ -59,11 +60,12 @@ type MaybeTouchableCard = ExclusifyUnion | T>; type CardContentProps = { headline?: string | RendersNullableElement; + headlineRef?: (instance: HTMLElement | null) => void; pretitle?: string; - pretitleAs?: string; + pretitleAs?: HeadingType; pretitleLinesMax?: number; title?: string; - titleAs?: string; + titleAs: HeadingType; titleLinesMax?: number; subtitle?: string; subtitleLinesMax?: number; @@ -73,11 +75,12 @@ type CardContentProps = { const CardContent = ({ headline, + headlineRef, pretitle, - pretitleAs = 'p', + pretitleAs, pretitleLinesMax, title, - titleAs = 'h3', + titleAs, titleLinesMax, subtitle, subtitleLinesMax, @@ -87,36 +90,78 @@ const CardContent = ({ const {textPresets} = useTheme(); return ( - - {headline} - {pretitle && ( - - {pretitle} - + /** using flex instead of nested Stacks, this way we can rearrange texts so the DOM structure makes more sense for screen reader users */ +
+ {isBiggerHeading(titleAs, pretitleAs) ? ( + <> + {title && ( +
+ + {title} + +
+ )} + {headline && ( + // assuming that the headline will always be followed by one of: pretitle, title, subtitle, description +
+ {typeof headline === 'string' ? {headline} : headline} +
+ )} + {pretitle && ( +
+ + {pretitle} + +
+ )} + + ) : ( + <> + {pretitle && ( +
+ + {pretitle} + +
+ )} + {headline && ( + // assuming that the headline will always be followed by one of: pretitle, title, subtitle, description +
+ {typeof headline === 'string' ? {headline} : headline} +
+ )} + {title && ( +
+ + {title} + +
+ )} + + )} + {subtitle && ( +
+ + {subtitle} + +
)} - - - {title} - - - {subtitle} - {description && ( )} - +
); }; @@ -246,7 +291,7 @@ export const AdvancedDataCard = React.forwardRef @@ -308,6 +366,7 @@ export const AdvancedDataCard = React.forwardRef
{hasExtras && ( -
+
{extra.map((item, index) => { return (