From 7af7971d9b2d02d29d3bb3e813265b5342aaa7f5 Mon Sep 17 00:00:00 2001 From: marcoskolodny Date: Mon, 11 Nov 2024 16:49:45 +0100 Subject: [PATCH 1/9] update header --- src/header.css.ts | 5 +++ src/header.tsx | 89 +++++++++++++++++++++++++++++++++------------ src/utils/types.tsx | 7 ++++ 3 files changed, 77 insertions(+), 24 deletions(-) diff --git a/src/header.css.ts b/src/header.css.ts index cae9bbbaeb..f2e5dfe137 100644 --- a/src/header.css.ts +++ b/src/header.css.ts @@ -23,3 +23,8 @@ export const hideOnDesktop = style({ }, }, }); + +export const flexColumn = style({ + display: 'flex', + flexDirection: 'column', +}); diff --git a/src/header.tsx b/src/header.tsx index 3c2b3743ac..ba57177ff5 100644 --- a/src/header.tsx +++ b/src/header.tsx @@ -10,10 +10,16 @@ import {vars} from './skins/skin-contract.css'; import * as styles from './header.css'; import {getPrefixedDataAttributes} from './utils/dom'; import {Title3, Title4} from './title'; +import { + isBiggerHeading, + type DataAttributes, + type HeadingType, + type RendersElement, + type RendersNullableElement, +} from './utils/types'; import type NavigationBreadcrumbs from './navigation-breadcrumbs'; import type {ButtonPrimary, ButtonSecondary} from './button'; -import type {DataAttributes, HeadingType, RendersElement, RendersNullableElement} from './utils/types'; import type {TextPresetProps} from './text'; type OverridableTextProps = { @@ -61,31 +67,66 @@ export const Header = ({ ); }; + const pretitleContent = pretitle + ? renderRichText(pretitle, {color: vars.colors.textPrimary, as: pretitleAs}) + : undefined; + + const titleContent = title ? ( + small ? ( + {title} + ) : ( + {title} + ) + ) : undefined; + return ( {(title || pretitle || description) && ( - - {headline} - {pretitle && - renderRichText(pretitle, {color: vars.colors.textPrimary, as: pretitleAs})} - {title && - (small ? ( - {title} - ) : ( - {title} - ))} - {description && - (small ? ( - - {description} - - ) : ( - - {description} - - ))} - + {/** using flex instead of nested Stacks, this way we can rearrange texts so the DOM structure makes more sense for screen reader users */} +
+
+ {headline} +
+ + {isBiggerHeading(titleAs, pretitleAs) ? ( + <> + {titleContent && ( +
+ {titleContent} +
+ )} + {pretitleContent && ( +
{pretitleContent}
+ )} + + ) : ( + <> + {pretitleContent && ( +
+ {pretitleContent} +
+ )} + {titleContent && ( +
{titleContent}
+ )} + + )} + + {description && ( +
+ {small ? ( + + {description} + + ) : ( + + {description} + + )} +
+ )} +
)}
@@ -155,7 +196,7 @@ export const HeaderLayout = ({ return (
- + = T | {mobile: T; tablet?: T; desktop: T}; export type HeadingType = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'span'; + +export const isBiggerHeading = (heading: HeadingType, otherHeading?: HeadingType): boolean => { + if (!otherHeading) { + return true; + } + return heading !== 'span' && (otherHeading === 'span' || heading < otherHeading); +}; From 78628971b47d17988a9bd5b6bfa063a4aa373cd4 Mon Sep 17 00:00:00 2001 From: marcoskolodny Date: Mon, 11 Nov 2024 17:12:13 +0100 Subject: [PATCH 2/9] add unit test --- src/__tests__/header-test.tsx | 42 +++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 src/__tests__/header-test.tsx diff --git a/src/__tests__/header-test.tsx b/src/__tests__/header-test.tsx new file mode 100644 index 0000000000..5926475f3c --- /dev/null +++ b/src/__tests__/header-test.tsx @@ -0,0 +1,42 @@ +import * as React from 'react'; +import {Header} from '../header'; +import {makeTheme} from './test-utils'; +import {render, screen} from '@testing-library/react'; +import ThemeContextProvider from '../theme-context-provider'; + +const pretitleFirst = 'HeadlinePretitleTitleDescription'; +const titleFirst = 'HeadlineTitlePretitleDescription'; + +test.each` + pretitleAs | titleAs | expectedOrder + ${undefined} | ${undefined} | ${titleFirst} + ${undefined} | ${'span'} | ${titleFirst} + ${undefined} | ${'h1'} | ${titleFirst} + ${'span'} | ${undefined} | ${titleFirst} + ${'h3'} | ${undefined} | ${titleFirst} + ${'h3'} | ${'h1'} | ${titleFirst} + ${'span'} | ${'h1'} | ${titleFirst} + ${'h1'} | ${undefined} | ${pretitleFirst} + ${'h1'} | ${'h3'} | ${pretitleFirst} + ${'h1'} | ${'span'} | ${pretitleFirst} +`( + 'Header has correct reading order with pretitleAs={$pretitleAs} and titleAs={$titleAs}', + async ({pretitleAs, titleAs, expectedOrder}) => { + render( + +
+ + ); + + const header = await screen.findByTestId('header'); + expect(header.textContent).toEqual(expectedOrder); + } +); From 2d9f5d79dfe509c7e22d56e286d111d2af4e7f50 Mon Sep 17 00:00:00 2001 From: marcoskolodny Date: Mon, 11 Nov 2024 17:13:26 +0100 Subject: [PATCH 3/9] fix headline wrong padding --- src/header.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/header.tsx b/src/header.tsx index ba57177ff5..e16569c060 100644 --- a/src/header.tsx +++ b/src/header.tsx @@ -85,9 +85,11 @@ export const Header = ({ {/** using flex instead of nested Stacks, this way we can rearrange texts so the DOM structure makes more sense for screen reader users */}
-
- {headline} -
+ {headline && ( +
+ {headline} +
+ )} {isBiggerHeading(titleAs, pretitleAs) ? ( <> From f2c0306f8c873cfa990ad4682127c70a1acd1a10 Mon Sep 17 00:00:00 2001 From: marcoskolodny Date: Mon, 11 Nov 2024 17:27:56 +0100 Subject: [PATCH 4/9] fix bug with theme provider --- src/header.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/header.tsx b/src/header.tsx index e16569c060..90d79c5e03 100644 --- a/src/header.tsx +++ b/src/header.tsx @@ -198,7 +198,7 @@ export const HeaderLayout = ({ return (
- + Date: Mon, 11 Nov 2024 18:41:12 +0100 Subject: [PATCH 5/9] update hero and coverHero --- src/__stories__/cover-hero-story.tsx | 17 ++++++++++ src/__stories__/hero-story.tsx | 17 ++++++++++ src/card.css.ts | 2 +- src/cover-hero.css.ts | 5 +++ src/cover-hero.tsx | 50 ++++++++++++++++++++++------ src/header.css.ts | 2 +- src/hero.css.ts | 5 +++ src/hero.tsx | 47 +++++++++++++++++++++----- 8 files changed, 124 insertions(+), 21 deletions(-) diff --git a/src/__stories__/cover-hero-story.tsx b/src/__stories__/cover-hero-story.tsx index 5a70572d78..4472f9e2c2 100644 --- a/src/__stories__/cover-hero-story.tsx +++ b/src/__stories__/cover-hero-story.tsx @@ -13,6 +13,7 @@ import usingVrImg from './images/using-vr.jpg'; import beachImg from './images/beach.jpg'; import beachVideo from './videos/beach.mp4'; +import type {HeadingType} from '../utils/types'; import type {TagType} from '..'; export default { @@ -30,7 +31,9 @@ type Args = { headlineType: TagType; headline: string; pretitle: string; + pretitleAs: HeadingType; title: string; + titleAs: HeadingType; description: string; extra: boolean; sideExtra: boolean; @@ -56,7 +59,9 @@ export const Default: StoryComponent = ({ headlineType, headline, pretitle, + pretitleAs, title, + titleAs, description, extra, sideExtra, @@ -92,7 +97,9 @@ export const Default: StoryComponent = ({ dataAttributes={{testid: 'cover-hero'}} headline={headline ? {headline} : undefined} pretitle={pretitle} + pretitleAs={pretitleAs} title={title} + titleAs={titleAs} description={description} extra={extra ? : undefined} sideExtra={sideExtra ? : undefined} @@ -118,7 +125,9 @@ Default.args = { headlineType: 'promo', headline: 'Hero', pretitle: 'Pretitle', + pretitleAs: 'span', title: 'Title', + titleAs: 'h1', description: 'This is a long description with a long text to see how this works', extra: false, sideExtra: false, @@ -197,6 +206,14 @@ Default.argTypes = { }, }, }, + pretitleAs: { + options: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'span'], + control: {type: 'select'}, + }, + titleAs: { + options: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'span'], + control: {type: 'select'}, + }, }; export const CoverHeroInSlideshow: StoryComponent = () => ( diff --git a/src/__stories__/hero-story.tsx b/src/__stories__/hero-story.tsx index 05b72ecc14..1cad9490ae 100644 --- a/src/__stories__/hero-story.tsx +++ b/src/__stories__/hero-story.tsx @@ -4,6 +4,7 @@ import usingVrImg from './images/using-vr.jpg'; import beachImg from './images/beach.jpg'; import beachVideo from './videos/beach.mp4'; +import type {HeadingType} from '../utils/types'; import type {TagType} from '..'; import type {AspectRatio} from '../video'; @@ -19,7 +20,9 @@ type HeroArgs = { headlineType: TagType; headline: string; pretitle: string; + pretitleAs: HeadingType; title: string; + titleAs: HeadingType; description: string; extra: boolean; actions: 'button' | 'link' | 'button and link'; @@ -36,7 +39,9 @@ export const Default: StoryComponent = ({ headline, headlineType, pretitle, + pretitleAs, title, + titleAs, description, actions, desktopMediaPosition, @@ -65,7 +70,9 @@ export const Default: StoryComponent = ({ media={mediaComponent} headline={headline ? {headline} : undefined} pretitle={pretitle} + pretitleAs={pretitleAs} title={title} + titleAs={titleAs} description={description} extra={extra ? : undefined} button={button} @@ -85,7 +92,9 @@ Default.args = { headlineType: 'promo', headline: 'Hero', pretitle: 'Pretitle', + pretitleAs: 'span', title: 'Title', + titleAs: 'h1', description: 'This is a long description with a long text to see how this works', extra: false, actions: 'button and link', @@ -128,4 +137,12 @@ Default.argTypes = { }, }, }, + 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/card.css.ts b/src/card.css.ts index 6613731128..97bf5e2ca7 100644 --- a/src/card.css.ts +++ b/src/card.css.ts @@ -415,7 +415,7 @@ export const dataCardTopActionsWithoutIcon = style({ }, }); -export const flexColumn = style({ +export const flexColumn = sprinkles({ display: 'flex', flexDirection: 'column', }); diff --git a/src/cover-hero.css.ts b/src/cover-hero.css.ts index 921f992dcc..224a816150 100644 --- a/src/cover-hero.css.ts +++ b/src/cover-hero.css.ts @@ -126,3 +126,8 @@ export const sixColumns = style({ }); export const sideExtra = sprinkles({position: 'relative'}); + +export const flexColumn = sprinkles({ + display: 'flex', + flexDirection: 'column', +}); diff --git a/src/cover-hero.tsx b/src/cover-hero.tsx index 7a03b97b63..8fb287964d 100644 --- a/src/cover-hero.tsx +++ b/src/cover-hero.tsx @@ -12,6 +12,7 @@ import * as mediaStyles from './image.css'; import GridLayout from './grid-layout'; import {CoverHeroMedia} from './cover-hero-media'; import {getPrefixedDataAttributes} from './utils/dom'; +import {isBiggerHeading} from './utils/types'; import type {DataAttributes, HeadingType} from './utils/types'; import type {ImageProps, VideoProps} from './cover-hero-media'; @@ -26,6 +27,7 @@ type BaseProps = { headline?: RendersNullableElement; pretitle?: string; pretitleLinesMax?: number; + pretitleAs?: HeadingType; title: string; titleLinesMax?: number; titleAs?: HeadingType; @@ -73,6 +75,7 @@ const CoverHero = React.forwardRef( headline, pretitle, pretitleLinesMax, + pretitleAs, title, titleLinesMax, titleAs = 'h1', @@ -108,22 +111,47 @@ const CoverHero = React.forwardRef( const textShadow = hasMedia ? '0 0 15px rgba(0, 0, 0, 0.4)' : undefined; + const pretitleContent = pretitle ? ( + + {pretitle} + + ) : undefined; + + const titleContent = ( + + {title} + + ); + const mainContent = (
{headline && {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) ? ( + <> + {titleContent} + {pretitleContent && ( +
+ {pretitleContent} +
+ )} + + ) : ( + <> + {pretitleContent && ( +
+ {pretitleContent} +
+ )} + {titleContent} + )} - - {title} - - +
{description && (
{ type HeroContentProps = { headline?: RendersNullableElement; pretitle?: string; + pretitleAs?: HeadingType; title?: string; titleAs?: HeadingType; description?: string; @@ -64,6 +71,7 @@ const HeroContent = ({ title, titleAs = 'h1', pretitle, + pretitleAs, description, descriptionLinesMax, extra, @@ -71,19 +79,41 @@ const HeroContent = ({ secondaryButton, buttonLink, }: HeroContentProps) => { + const titleContent = title ? {title} : undefined; + + const pretitleContent = pretitle ? ( + + {pretitle} + + ) : undefined; + return (
{headline && 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) ? ( + <> + {titleContent} + {pretitleContent && ( +
+ {pretitleContent} +
+ )} + + ) : ( + <> + {pretitleContent && ( +
{pretitleContent}
+ )} + {titleContent} + )} - {title && {title}} - +
+ {description && ( | RendersElement; headline?: RendersNullableElement; pretitle?: string; + pretitleAs?: HeadingType; title?: string; titleAs?: HeadingType; description?: string; From 086f315137dada3e957d201d2a4ac12b69ddf2b5 Mon Sep 17 00:00:00 2001 From: marcoskolodny Date: Mon, 11 Nov 2024 19:01:16 +0100 Subject: [PATCH 6/9] add hero/coverHero unit tests --- src/__tests__/cover-hero-test.tsx | 44 +++++++++++++++++++++++++++++++ src/__tests__/hero-test.tsx | 44 +++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 src/__tests__/cover-hero-test.tsx create mode 100644 src/__tests__/hero-test.tsx diff --git a/src/__tests__/cover-hero-test.tsx b/src/__tests__/cover-hero-test.tsx new file mode 100644 index 0000000000..118f53345d --- /dev/null +++ b/src/__tests__/cover-hero-test.tsx @@ -0,0 +1,44 @@ +import * as React from 'react'; +import CoverHero from '../cover-hero'; +import Tag from '../tag'; +import {makeTheme} from './test-utils'; +import {render, screen} from '@testing-library/react'; +import ThemeContextProvider from '../theme-context-provider'; + +const pretitleFirst = 'HeadlinePretitleTitleDescription'; +const titleFirst = 'HeadlineTitlePretitleDescription'; + +test.each` + pretitleAs | titleAs | expectedOrder + ${undefined} | ${undefined} | ${titleFirst} + ${undefined} | ${'span'} | ${titleFirst} + ${undefined} | ${'h1'} | ${titleFirst} + ${'span'} | ${undefined} | ${titleFirst} + ${'h3'} | ${undefined} | ${titleFirst} + ${'h3'} | ${'h1'} | ${titleFirst} + ${'span'} | ${'h1'} | ${titleFirst} + ${'h1'} | ${undefined} | ${pretitleFirst} + ${'h1'} | ${'h3'} | ${pretitleFirst} + ${'h1'} | ${'span'} | ${pretitleFirst} +`( + 'CoverHero has correct reading order with pretitleAs={$pretitleAs} and titleAs={$titleAs}', + async ({pretitleAs, titleAs, expectedOrder}) => { + render( + + Headline} + pretitle="Pretitle" + title="Title" + description="Description" + titleAs={titleAs} + pretitleAs={pretitleAs} + dataAttributes={{testid: 'cover-hero'}} + /> + + ); + + const coverHero = await screen.findByTestId('cover-hero'); + expect(coverHero.textContent).toEqual(expectedOrder); + } +); diff --git a/src/__tests__/hero-test.tsx b/src/__tests__/hero-test.tsx new file mode 100644 index 0000000000..d65920c9ec --- /dev/null +++ b/src/__tests__/hero-test.tsx @@ -0,0 +1,44 @@ +import * as React from 'react'; +import Hero from '../hero'; +import Tag from '../tag'; +import {makeTheme} from './test-utils'; +import {render, screen} from '@testing-library/react'; +import ThemeContextProvider from '../theme-context-provider'; + +const pretitleFirst = 'HeadlinePretitleTitleDescription'; +const titleFirst = 'HeadlineTitlePretitleDescription'; + +test.each` + pretitleAs | titleAs | expectedOrder + ${undefined} | ${undefined} | ${titleFirst} + ${undefined} | ${'span'} | ${titleFirst} + ${undefined} | ${'h1'} | ${titleFirst} + ${'span'} | ${undefined} | ${titleFirst} + ${'h3'} | ${undefined} | ${titleFirst} + ${'h3'} | ${'h1'} | ${titleFirst} + ${'span'} | ${'h1'} | ${titleFirst} + ${'h1'} | ${undefined} | ${pretitleFirst} + ${'h1'} | ${'h3'} | ${pretitleFirst} + ${'h1'} | ${'span'} | ${pretitleFirst} +`( + 'Hero has correct reading order with pretitleAs={$pretitleAs} and titleAs={$titleAs}', + async ({pretitleAs, titleAs, expectedOrder}) => { + render( + + } + headline={Headline} + pretitle="Pretitle" + title="Title" + description="Description" + titleAs={titleAs} + pretitleAs={pretitleAs} + dataAttributes={{testid: 'hero'}} + /> + + ); + + const hero = await screen.findByTestId('hero'); + expect(hero.textContent).toEqual(expectedOrder); + } +); From 08589ce6cdd6a11814343e004c85e88546e8e899 Mon Sep 17 00:00:00 2001 From: marcoskolodny Date: Wed, 13 Nov 2024 14:10:28 +0100 Subject: [PATCH 7/9] update all cards, add unit tests and add titleAs/pretitleAs controls in stories --- src/__stories__/data-card-story.tsx | 17 ++ src/__stories__/display-data-card-story.tsx | 17 ++ src/__stories__/display-media-card-story.tsx | 17 ++ src/__stories__/media-card-story.tsx | 17 ++ src/__stories__/naked-card-story.tsx | 17 ++ src/__stories__/poster-card-story.tsx | 17 ++ src/__tests__/data-card-test.tsx | 153 +++++++----- src/__tests__/display-data-card-test.tsx | 153 +++++++----- src/__tests__/display-media-card-test.tsx | 159 ++++++++----- src/__tests__/media-card-test.tsx | 168 ++++++++------ src/__tests__/naked-card-test.tsx | 168 ++++++++------ src/__tests__/poster-card-test.tsx | 168 ++++++++------ src/card.tsx | 231 ++++++++++++++----- 13 files changed, 848 insertions(+), 454 deletions(-) diff --git a/src/__stories__/data-card-story.tsx b/src/__stories__/data-card-story.tsx index d0602c45ef..aeced7f28c 100644 --- a/src/__stories__/data-card-story.tsx +++ b/src/__stories__/data-card-story.tsx @@ -17,6 +17,7 @@ import { import {Placeholder} from '../placeholder'; import avatarImg from './images/avatar.jpg'; +import type {HeadingType} from '../utils/types'; import type {AspectRatio} from '../card'; import type {TagType} from '..'; @@ -29,7 +30,9 @@ type DataCardArgs = { headlineType: TagType; headline: string; pretitle: string; + pretitleAs: HeadingType; title: string; + titleAs: HeadingType; subtitle: string; description: string; ariaLabel: string; @@ -47,7 +50,9 @@ export const Default: StoryComponent = ({ headline, headlineType, pretitle, + pretitleAs, title, + titleAs, subtitle, description, ariaLabel, @@ -99,7 +104,9 @@ export const Default: StoryComponent = ({ asset={assetElement} headline={headline && {headline}} pretitle={pretitle} + pretitleAs={pretitleAs} title={title} + titleAs={titleAs} subtitle={subtitle} description={description} extra={extra ? : undefined} @@ -142,7 +149,9 @@ Default.args = { headlineType: 'promo', headline: 'Priority', pretitle: 'Pretitle', + pretitleAs: 'span', title: 'Title', + titleAs: 'h3', subtitle: 'Subtitle', description: 'This is a description for the card', extra: false, @@ -177,6 +186,14 @@ Default.argTypes = { }, }, }, + pretitleAs: { + options: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'span'], + control: {type: 'select'}, + }, + titleAs: { + options: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'span'], + control: {type: 'select'}, + }, }; export const Group: StoryComponent = () => { diff --git a/src/__stories__/display-data-card-story.tsx b/src/__stories__/display-data-card-story.tsx index 8cd8a30ab3..bb44a1b516 100644 --- a/src/__stories__/display-data-card-story.tsx +++ b/src/__stories__/display-data-card-story.tsx @@ -20,6 +20,7 @@ import { import {Placeholder} from '../placeholder'; import avatarImg from './images/avatar.jpg'; +import type {HeadingType} from '../utils/types'; import type {AspectRatio} from '../card'; import type {TagType} from '..'; @@ -32,7 +33,9 @@ type DisplayDataCardArgs = { headlineType: TagType; headline: string; pretitle: string; + pretitleAs: HeadingType; title: string; + titleAs: HeadingType; description: string; extra: boolean; closable: boolean; @@ -57,7 +60,9 @@ export const Default: StoryComponent = ({ headline, headlineType, pretitle, + pretitleAs, title, + titleAs, description, extra, actions = 'button', @@ -141,7 +146,9 @@ export const Default: StoryComponent = ({ asset={assetElement} headline={headline ? {headline} : undefined} pretitle={pretitle} + pretitleAs={pretitleAs} title={title} + titleAs={titleAs} description={description} aspectRatio={aspectRatioValue as AspectRatio} extra={extra ? : undefined} @@ -158,7 +165,9 @@ Default.args = { headlineType: 'promo', headline: 'Priority', pretitle: 'Pretitle', + pretitleAs: 'span', title: 'Title', + titleAs: 'h3', description: 'This is a description for the card', extra: false, actions: 'button', @@ -201,6 +210,14 @@ Default.argTypes = { }, }, }, + pretitleAs: { + options: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'span'], + control: {type: 'select'}, + }, + titleAs: { + options: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'span'], + control: {type: 'select'}, + }, }; export const Group: StoryComponent = () => { diff --git a/src/__stories__/display-media-card-story.tsx b/src/__stories__/display-media-card-story.tsx index 42e6ae96f4..554717bf7a 100644 --- a/src/__stories__/display-media-card-story.tsx +++ b/src/__stories__/display-media-card-story.tsx @@ -23,6 +23,7 @@ import avatarImg from './images/avatar.jpg'; import beachVideo from './videos/beach.mp4'; import beachImg from './images/beach.jpg'; +import type {HeadingType} from '../utils/types'; import type {TagType} from '..'; export default { @@ -39,7 +40,9 @@ type DisplayMediaCardArgs = { background: 'image' | 'video'; headline: string; pretitle: string; + pretitleAs: HeadingType; title: string; + titleAs: HeadingType; description: string; extra: boolean; closable: boolean; @@ -65,7 +68,9 @@ export const Default: StoryComponent = ({ headlineType, background, pretitle, + pretitleAs, title, + titleAs, description, extra, actions = 'button', @@ -153,7 +158,9 @@ export const Default: StoryComponent = ({ asset={assetElement} headline={headline ? {headline} : undefined} pretitle={pretitle} + pretitleAs={pretitleAs} title={title} + titleAs={titleAs} description={description} {...interactiveActions} aria-label="Display media card label" @@ -174,7 +181,9 @@ Default.args = { background: 'image', headline: 'Priority', pretitle: 'Pretitle', + pretitleAs: 'span', title: 'Title', + titleAs: 'h3', description: 'This is a description for the card', extra: false, actions: 'button', @@ -215,6 +224,14 @@ Default.argTypes = { options: ['1:1', '16:9', '7:10', '9:10', 'auto'], 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'}, + }, }; Default.parameters = {fullScreen: true}; diff --git a/src/__stories__/media-card-story.tsx b/src/__stories__/media-card-story.tsx index 6fa815fe41..745abc95b7 100644 --- a/src/__stories__/media-card-story.tsx +++ b/src/__stories__/media-card-story.tsx @@ -21,6 +21,7 @@ import tennisImg from './images/tennis.jpg'; import beachVideo from './videos/beach.mp4'; import avatarImg from './images/avatar.jpg'; +import type {HeadingType} from '../utils/types'; import type {TagType} from '..'; export default { @@ -36,7 +37,9 @@ type Args = { headlineType: TagType; headline: string; pretitle: string; + pretitleAs: HeadingType; title: string; + titleAs: HeadingType; subtitle: string; description: string; extra: boolean; @@ -50,7 +53,9 @@ export const Default: StoryComponent = ({ headline, headlineType, pretitle, + pretitleAs, title, + titleAs, subtitle, description, actions = 'button', @@ -99,7 +104,9 @@ export const Default: StoryComponent = ({ dataAttributes={{testid: 'media-card'}} headline={headline && {headline}} pretitle={pretitle} + pretitleAs={pretitleAs} title={title} + titleAs={titleAs} subtitle={subtitle} description={description} asset={assetElement} @@ -153,7 +160,9 @@ Default.args = { headlineType: 'promo', headline: 'Priority', pretitle: 'Pretitle', + pretitleAs: 'span', title: 'Title', + titleAs: 'h3', subtitle: 'Subtitle', description: 'This is a description for the card', extra: false, @@ -179,6 +188,14 @@ Default.argTypes = { options: ['button', 'link', 'button and link', 'onPress', 'href', 'to', 'none'], 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'}, + }, }; export const Group: StoryComponent = () => { diff --git a/src/__stories__/naked-card-story.tsx b/src/__stories__/naked-card-story.tsx index cac8979fd8..e6b6d8c4da 100644 --- a/src/__stories__/naked-card-story.tsx +++ b/src/__stories__/naked-card-story.tsx @@ -23,6 +23,7 @@ import beachVideo from './videos/beach.mp4'; import {SmallNakedCard} from '../card'; import avatarImg from './images/avatar.jpg'; +import type {HeadingType} from '../utils/types'; import type {TagType} from '..'; export default { @@ -41,7 +42,9 @@ type Args = { headlineType: TagType; headline: string; pretitle: string; + pretitleAs: HeadingType; title: string; + titleAs: HeadingType; subtitle: string; description: string; extra: boolean; @@ -55,7 +58,9 @@ export const Default: StoryComponent = ({ headline, headlineType, pretitle, + pretitleAs, title, + titleAs, subtitle, description, actions = 'button', @@ -106,7 +111,9 @@ export const Default: StoryComponent = ({ dataAttributes={{testid: 'naked-card'}} headline={headline && {headline}} pretitle={pretitle} + pretitleAs={pretitleAs} title={title} + titleAs={titleAs} subtitle={subtitle} description={description} media={ @@ -160,7 +167,9 @@ Default.args = { headlineType: 'promo', headline: 'Priority', pretitle: 'Pretitle', + pretitleAs: 'span', title: 'Title', + titleAs: 'h3', subtitle: 'Subtitle', description: 'This is a description for the card', extra: false, @@ -186,6 +195,14 @@ Default.argTypes = { options: ['button', 'link', 'button and link', 'onPress', 'href', 'to', 'none'], 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'}, + }, }; type SmallArgs = { diff --git a/src/__stories__/poster-card-story.tsx b/src/__stories__/poster-card-story.tsx index d134708eff..238a44c0f6 100644 --- a/src/__stories__/poster-card-story.tsx +++ b/src/__stories__/poster-card-story.tsx @@ -20,6 +20,7 @@ import avatarImg from './images/avatar.jpg'; import beachVideo from './videos/beach.mp4'; import beachImg from './images/beach.jpg'; +import type {HeadingType} from '../utils/types'; import type {TagType} from '..'; export default { @@ -39,7 +40,9 @@ type PosterCardArgs = { headlineType: TagType; headline: string; pretitle: string; + pretitleAs: HeadingType; title: string; + titleAs: HeadingType; subtitle: string; description: string; extra: boolean; @@ -63,7 +66,9 @@ export const Default: StoryComponent = ({ headline, headlineType, pretitle, + pretitleAs, title, + titleAs, subtitle, description, extra, @@ -144,7 +149,9 @@ export const Default: StoryComponent = ({ asset={assetElement} headline={headline ? {headline} : undefined} pretitle={pretitle} + pretitleAs={pretitleAs} title={title} + titleAs={titleAs} subtitle={subtitle} description={description} extra={extra ? : undefined} @@ -169,7 +176,9 @@ Default.args = { variant: 'default', headline: 'Priority', pretitle: 'Pretitle', + pretitleAs: 'span', title: 'Title', + titleAs: 'h3', subtitle: 'Subtitle', description: 'This is a description for the card', extra: false, @@ -226,6 +235,14 @@ Default.argTypes = { options: ['onPress', 'href', 'to', 'none'], 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'}, + }, }; Default.parameters = {fullScreen: true}; diff --git a/src/__tests__/data-card-test.tsx b/src/__tests__/data-card-test.tsx index 2056c341b0..4b4dc2cf17 100644 --- a/src/__tests__/data-card-test.tsx +++ b/src/__tests__/data-card-test.tsx @@ -8,71 +8,104 @@ import Stack from '../stack'; import {Text2} from '../text'; import userEvent from '@testing-library/user-event'; -test('DataCard "href" label', async () => { - render( - - Headline} - pretitle="Pretitle" - title="Title" - description="Description" - extra={ - - Extra line 1 - Extra line 2 - - } - /> - - ); +const titleFirst = 'Title Headline Pretitle Description Extra line 1Extra line 2'; +const pretitleFirst = 'Pretitle Headline Title Description Extra line 1Extra line 2'; - await screen.findByRole('link', {name: 'Title Headline Pretitle Description Extra line 1Extra line 2'}); -}); +test.each` + pretitleAs | titleAs | expectedLabel + ${undefined} | ${undefined} | ${titleFirst} + ${'h1'} | ${'h2'} | ${pretitleFirst} + ${'h2'} | ${'h1'} | ${titleFirst} +`( + 'DataCard "href" label with pretitleAs={$pretitleAs} and titleAs={$titleAs}', + async ({pretitleAs, titleAs, expectedLabel}) => { + render( + + Headline} + pretitle="Pretitle" + pretitleAs={pretitleAs} + title="Title" + titleAs={titleAs} + description="Description" + extra={ + + Extra line 1 + Extra line 2 + + } + /> + + ); -test('DataCard "to" label', async () => { - render( - - Headline} - pretitle="Pretitle" - title="Title" - description="Description" - extra={ - - Extra line 1 - Extra line 2 - - } - /> - - ); + await screen.findByRole('link', {name: expectedLabel}); + } +); - await screen.findByRole('link', {name: 'Title Headline Pretitle Description Extra line 1Extra line 2'}); -}); +test.each` + pretitleAs | titleAs | expectedLabel + ${undefined} | ${undefined} | ${titleFirst} + ${'h1'} | ${'h2'} | ${pretitleFirst} + ${'h2'} | ${'h1'} | ${titleFirst} +`( + 'DataCard "to" label with pretitleAs={$pretitleAs} and titleAs={$titleAs}', + async ({pretitleAs, titleAs, expectedLabel}) => { + render( + + Headline} + pretitle="Pretitle" + pretitleAs={pretitleAs} + title="Title" + titleAs={titleAs} + description="Description" + extra={ + + Extra line 1 + Extra line 2 + + } + /> + + ); -test('DataCard "onPress" label', async () => { - render( - - {}} - headline={Headline} - pretitle="Pretitle" - title="Title" - description="Description" - extra={ - - Extra line 1 - Extra line 2 - - } - /> - - ); + await screen.findByRole('link', {name: expectedLabel}); + } +); - await screen.findByRole('button', {name: 'Title Headline Pretitle Description Extra line 1Extra line 2'}); -}); +test.each` + pretitleAs | titleAs | expectedLabel + ${undefined} | ${undefined} | ${titleFirst} + ${'h1'} | ${'h2'} | ${pretitleFirst} + ${'h2'} | ${'h1'} | ${titleFirst} +`( + 'DataCard "onPress" label with pretitleAs={$pretitleAs} and titleAs={$titleAs}', + async ({pretitleAs, titleAs, expectedLabel}) => { + render( + + {}} + headline={Headline} + pretitle="Pretitle" + pretitleAs={pretitleAs} + title="Title" + titleAs={titleAs} + description="Description" + extra={ + + Extra line 1 + Extra line 2 + + } + /> + + ); + + await screen.findByRole('button', {name: expectedLabel}); + } +); test('DataCard onClose custom label', async () => { const closeSpy = jest.fn(); diff --git a/src/__tests__/display-data-card-test.tsx b/src/__tests__/display-data-card-test.tsx index 49e33ffc06..e0420f5df4 100644 --- a/src/__tests__/display-data-card-test.tsx +++ b/src/__tests__/display-data-card-test.tsx @@ -8,71 +8,104 @@ import Stack from '../stack'; import {Text2} from '../text'; import userEvent from '@testing-library/user-event'; -test('DisplayDataCard "href" label', async () => { - render( - - Headline} - pretitle="Pretitle" - title="Title" - description="Description" - extra={ - - Extra line 1 - Extra line 2 - - } - /> - - ); +const titleFirst = 'Title Headline Pretitle Description Extra line 1Extra line 2'; +const pretitleFirst = 'Pretitle Headline Title Description Extra line 1Extra line 2'; - await screen.findByRole('link', {name: 'Title Headline Pretitle Description Extra line 1Extra line 2'}); -}); +test.each` + pretitleAs | titleAs | expectedLabel + ${undefined} | ${undefined} | ${titleFirst} + ${'h1'} | ${'h2'} | ${pretitleFirst} + ${'h2'} | ${'h1'} | ${titleFirst} +`( + 'DisplayDataCard "href" label with pretitleAs={$pretitleAs} and titleAs={$titleAs}', + async ({pretitleAs, titleAs, expectedLabel}) => { + render( + + Headline} + pretitle="Pretitle" + pretitleAs={pretitleAs} + title="Title" + titleAs={titleAs} + description="Description" + extra={ + + Extra line 1 + Extra line 2 + + } + /> + + ); -test('DisplayDataCard "to" label', async () => { - render( - - Headline} - pretitle="Pretitle" - title="Title" - description="Description" - extra={ - - Extra line 1 - Extra line 2 - - } - /> - - ); + await screen.findByRole('link', {name: expectedLabel}); + } +); - await screen.findByRole('link', {name: 'Title Headline Pretitle Description Extra line 1Extra line 2'}); -}); +test.each` + pretitleAs | titleAs | expectedLabel + ${undefined} | ${undefined} | ${titleFirst} + ${'h1'} | ${'h2'} | ${pretitleFirst} + ${'h2'} | ${'h1'} | ${titleFirst} +`( + 'DisplayDataCard "to" label with pretitleAs={$pretitleAs} and titleAs={$titleAs}', + async ({pretitleAs, titleAs, expectedLabel}) => { + render( + + Headline} + pretitle="Pretitle" + pretitleAs={pretitleAs} + title="Title" + titleAs={titleAs} + description="Description" + extra={ + + Extra line 1 + Extra line 2 + + } + /> + + ); -test('DisplayDataCard "onPress" label', async () => { - render( - - {}} - headline={Headline} - pretitle="Pretitle" - title="Title" - description="Description" - extra={ - - Extra line 1 - Extra line 2 - - } - /> - - ); + await screen.findByRole('link', {name: expectedLabel}); + } +); - await screen.findByRole('button', {name: 'Title Headline Pretitle Description Extra line 1Extra line 2'}); -}); +test.each` + pretitleAs | titleAs | expectedLabel + ${undefined} | ${undefined} | ${titleFirst} + ${'h1'} | ${'h2'} | ${pretitleFirst} + ${'h2'} | ${'h1'} | ${titleFirst} +`( + 'DisplayDataCard "onPress" label with pretitleAs={$pretitleAs} and titleAs={$titleAs}', + async ({pretitleAs, titleAs, expectedLabel}) => { + render( + + {}} + headline={Headline} + pretitle="Pretitle" + pretitleAs={pretitleAs} + title="Title" + titleAs={titleAs} + description="Description" + extra={ + + Extra line 1 + Extra line 2 + + } + /> + + ); + + await screen.findByRole('button', {name: expectedLabel}); + } +); test('DisplayDataCard onClose custom label', async () => { const closeSpy = jest.fn(); diff --git a/src/__tests__/display-media-card-test.tsx b/src/__tests__/display-media-card-test.tsx index da8d978d22..24db7b9898 100644 --- a/src/__tests__/display-media-card-test.tsx +++ b/src/__tests__/display-media-card-test.tsx @@ -8,74 +8,107 @@ import Stack from '../stack'; import {Text2} from '../text'; import userEvent from '@testing-library/user-event'; -test('DisplayMediaCard "href" label', async () => { - render( - - Headline} - pretitle="Pretitle" - title="Title" - description="Description" - extra={ - - Extra line 1 - Extra line 2 - - } - /> - - ); +const titleFirst = 'Title Headline Pretitle Description Extra line 1Extra line 2'; +const pretitleFirst = 'Pretitle Headline Title Description Extra line 1Extra line 2'; - await screen.findByRole('link', {name: 'Title Headline Pretitle Description Extra line 1Extra line 2'}); -}); +test.each` + pretitleAs | titleAs | expectedLabel + ${undefined} | ${undefined} | ${titleFirst} + ${'h1'} | ${'h2'} | ${pretitleFirst} + ${'h2'} | ${'h1'} | ${titleFirst} +`( + 'DisplayMediaCard "href" label with pretitleAs={$pretitleAs} and titleAs={$titleAs}', + async ({pretitleAs, titleAs, expectedLabel}) => { + render( + + Headline} + pretitle="Pretitle" + pretitleAs={pretitleAs} + title="Title" + titleAs={titleAs} + description="Description" + extra={ + + Extra line 1 + Extra line 2 + + } + /> + + ); -test('DisplayMediaCard "to" label', async () => { - render( - - Headline} - pretitle="Pretitle" - title="Title" - description="Description" - extra={ - - Extra line 1 - Extra line 2 - - } - /> - - ); + await screen.findByRole('link', {name: expectedLabel}); + } +); - await screen.findByRole('link', {name: 'Title Headline Pretitle Description Extra line 1Extra line 2'}); -}); +test.each` + pretitleAs | titleAs | expectedLabel + ${undefined} | ${undefined} | ${titleFirst} + ${'h1'} | ${'h2'} | ${pretitleFirst} + ${'h2'} | ${'h1'} | ${titleFirst} +`( + 'DisplayMediaCard "to" label with pretitleAs={$pretitleAs} and titleAs={$titleAs}', + async ({pretitleAs, titleAs, expectedLabel}) => { + render( + + Headline} + pretitle="Pretitle" + pretitleAs={pretitleAs} + title="Title" + titleAs={titleAs} + description="Description" + extra={ + + Extra line 1 + Extra line 2 + + } + /> + + ); -test('DisplayMediaCard "onPress" label', async () => { - render( - - {}} - backgroundImage="https://source.unsplash.com/900x900/" - headline={Headline} - pretitle="Pretitle" - title="Title" - description="Description" - extra={ - - Extra line 1 - Extra line 2 - - } - /> - - ); + await screen.findByRole('link', {name: expectedLabel}); + } +); - await screen.findByRole('button', {name: 'Title Headline Pretitle Description Extra line 1Extra line 2'}); -}); +test.each` + pretitleAs | titleAs | expectedLabel + ${undefined} | ${undefined} | ${titleFirst} + ${'h1'} | ${'h2'} | ${pretitleFirst} + ${'h2'} | ${'h1'} | ${titleFirst} +`( + 'DisplayMediaCard "onPress" label with pretitleAs={$pretitleAs} and titleAs={$titleAs}', + async ({pretitleAs, titleAs, expectedLabel}) => { + render( + + {}} + backgroundImage="https://source.unsplash.com/900x900/" + headline={Headline} + pretitle="Pretitle" + pretitleAs={pretitleAs} + title="Title" + titleAs={titleAs} + description="Description" + extra={ + + Extra line 1 + Extra line 2 + + } + /> + + ); + + await screen.findByRole('button', {name: expectedLabel}); + } +); test('DisplayMediaCard onClose custom label', async () => { const closeSpy = jest.fn(); diff --git a/src/__tests__/media-card-test.tsx b/src/__tests__/media-card-test.tsx index 35776af9de..710d23fe10 100644 --- a/src/__tests__/media-card-test.tsx +++ b/src/__tests__/media-card-test.tsx @@ -9,83 +9,107 @@ import Image from '../image'; import {Text2} from '../text'; import userEvent from '@testing-library/user-event'; -test('MediaCard "href" label', async () => { - render( - - } - headline={Headline} - pretitle="Pretitle" - subtitle="Subtitle" - title="Title" - description="Description" - extra={ - - Extra line 1 - Extra line 2 - - } - /> - - ); +const titleFirst = 'Title Headline Pretitle Description Extra line 1Extra line 2'; +const pretitleFirst = 'Pretitle Headline Title Description Extra line 1Extra line 2'; - await screen.findByRole('link', { - name: 'Title Headline Pretitle Subtitle Description Extra line 1Extra line 2', - }); -}); +test.each` + pretitleAs | titleAs | expectedLabel + ${undefined} | ${undefined} | ${titleFirst} + ${'h1'} | ${'h2'} | ${pretitleFirst} + ${'h2'} | ${'h1'} | ${titleFirst} +`( + 'MediaCard "href" label with pretitleAs={$pretitleAs} and titleAs={$titleAs}', + async ({pretitleAs, titleAs, expectedLabel}) => { + render( + + } + headline={Headline} + pretitle="Pretitle" + pretitleAs={pretitleAs} + title="Title" + titleAs={titleAs} + description="Description" + extra={ + + Extra line 1 + Extra line 2 + + } + /> + + ); -test('MediaCard "to" label', async () => { - render( - - } - headline={Headline} - pretitle="Pretitle" - subtitle="Subtitle" - title="Title" - description="Description" - extra={ - - Extra line 1 - Extra line 2 - - } - /> - - ); + await screen.findByRole('link', {name: expectedLabel}); + } +); - await screen.findByRole('link', { - name: 'Title Headline Pretitle Subtitle Description Extra line 1Extra line 2', - }); -}); +test.each` + pretitleAs | titleAs | expectedLabel + ${undefined} | ${undefined} | ${titleFirst} + ${'h1'} | ${'h2'} | ${pretitleFirst} + ${'h2'} | ${'h1'} | ${titleFirst} +`( + 'MediaCard "to" label with pretitleAs={$pretitleAs} and titleAs={$titleAs}', + async ({pretitleAs, titleAs, expectedLabel}) => { + render( + + } + headline={Headline} + pretitle="Pretitle" + pretitleAs={pretitleAs} + title="Title" + titleAs={titleAs} + description="Description" + extra={ + + Extra line 1 + Extra line 2 + + } + /> + + ); -test('MediaCard "onPress" label', async () => { - render( - - {}} - media={} - headline={Headline} - pretitle="Pretitle" - subtitle="Subtitle" - title="Title" - description="Description" - extra={ - - Extra line 1 - Extra line 2 - - } - /> - - ); + await screen.findByRole('link', {name: expectedLabel}); + } +); - await screen.findByRole('button', { - name: 'Title Headline Pretitle Subtitle Description Extra line 1Extra line 2', - }); -}); +test.each` + pretitleAs | titleAs | expectedLabel + ${undefined} | ${undefined} | ${titleFirst} + ${'h1'} | ${'h2'} | ${pretitleFirst} + ${'h2'} | ${'h1'} | ${titleFirst} +`( + 'MediaCard "onPress" label with pretitleAs={$pretitleAs} and titleAs={$titleAs}', + async ({pretitleAs, titleAs, expectedLabel}) => { + render( + + {}} + media={} + headline={Headline} + pretitle="Pretitle" + pretitleAs={pretitleAs} + title="Title" + titleAs={titleAs} + description="Description" + extra={ + + Extra line 1 + Extra line 2 + + } + /> + + ); + + await screen.findByRole('button', {name: expectedLabel}); + } +); test('MediaCard onClose custom label', async () => { const closeSpy = jest.fn(); diff --git a/src/__tests__/naked-card-test.tsx b/src/__tests__/naked-card-test.tsx index 4cdce0b345..d68672fa09 100644 --- a/src/__tests__/naked-card-test.tsx +++ b/src/__tests__/naked-card-test.tsx @@ -9,83 +9,107 @@ import Image from '../image'; import {Text2} from '../text'; import userEvent from '@testing-library/user-event'; -test('NakedCard "href" label', async () => { - render( - - } - headline={Headline} - pretitle="Pretitle" - subtitle="Subtitle" - title="Title" - description="Description" - extra={ - - Extra line 1 - Extra line 2 - - } - /> - - ); +const titleFirst = 'Title Headline Pretitle Description Extra line 1Extra line 2'; +const pretitleFirst = 'Pretitle Headline Title Description Extra line 1Extra line 2'; - await screen.findByRole('link', { - name: 'Title Headline Pretitle Subtitle Description Extra line 1Extra line 2', - }); -}); +test.each` + pretitleAs | titleAs | expectedLabel + ${undefined} | ${undefined} | ${titleFirst} + ${'h1'} | ${'h2'} | ${pretitleFirst} + ${'h2'} | ${'h1'} | ${titleFirst} +`( + 'NakedCard "href" label with pretitleAs={$pretitleAs} and titleAs={$titleAs}', + async ({pretitleAs, titleAs, expectedLabel}) => { + render( + + } + headline={Headline} + pretitle="Pretitle" + pretitleAs={pretitleAs} + title="Title" + titleAs={titleAs} + description="Description" + extra={ + + Extra line 1 + Extra line 2 + + } + /> + + ); -test('NakedCard "to" label', async () => { - render( - - } - headline={Headline} - pretitle="Pretitle" - subtitle="Subtitle" - title="Title" - description="Description" - extra={ - - Extra line 1 - Extra line 2 - - } - /> - - ); + await screen.findByRole('link', {name: expectedLabel}); + } +); - await screen.findByRole('link', { - name: 'Title Headline Pretitle Subtitle Description Extra line 1Extra line 2', - }); -}); +test.each` + pretitleAs | titleAs | expectedLabel + ${undefined} | ${undefined} | ${titleFirst} + ${'h1'} | ${'h2'} | ${pretitleFirst} + ${'h2'} | ${'h1'} | ${titleFirst} +`( + 'NakedCard "to" label with pretitleAs={$pretitleAs} and titleAs={$titleAs}', + async ({pretitleAs, titleAs, expectedLabel}) => { + render( + + } + headline={Headline} + pretitle="Pretitle" + pretitleAs={pretitleAs} + title="Title" + titleAs={titleAs} + description="Description" + extra={ + + Extra line 1 + Extra line 2 + + } + /> + + ); -test('NakedCard "onPress" label', async () => { - render( - - {}} - media={} - headline={Headline} - pretitle="Pretitle" - subtitle="Subtitle" - title="Title" - description="Description" - extra={ - - Extra line 1 - Extra line 2 - - } - /> - - ); + await screen.findByRole('link', {name: expectedLabel}); + } +); - await screen.findByRole('button', { - name: 'Title Headline Pretitle Subtitle Description Extra line 1Extra line 2', - }); -}); +test.each` + pretitleAs | titleAs | expectedLabel + ${undefined} | ${undefined} | ${titleFirst} + ${'h1'} | ${'h2'} | ${pretitleFirst} + ${'h2'} | ${'h1'} | ${titleFirst} +`( + 'NakedCard "onPress" label with pretitleAs={$pretitleAs} and titleAs={$titleAs}', + async ({pretitleAs, titleAs, expectedLabel}) => { + render( + + {}} + media={} + headline={Headline} + pretitle="Pretitle" + pretitleAs={pretitleAs} + title="Title" + titleAs={titleAs} + description="Description" + extra={ + + Extra line 1 + Extra line 2 + + } + /> + + ); + + await screen.findByRole('button', {name: expectedLabel}); + } +); test('NakedCard onClose custom label', async () => { const closeSpy = jest.fn(); diff --git a/src/__tests__/poster-card-test.tsx b/src/__tests__/poster-card-test.tsx index bca1d5ef10..af50daef48 100644 --- a/src/__tests__/poster-card-test.tsx +++ b/src/__tests__/poster-card-test.tsx @@ -7,83 +7,107 @@ import Stack from '../stack'; import {Text2} from '../text'; import userEvent from '@testing-library/user-event'; -test('PosterCard "href" label', async () => { - render( - - - Extra line 1 - Extra line 2 -
- } - /> - - ); +const titleFirst = 'Title Headline Pretitle Description Extra line 1Extra line 2'; +const pretitleFirst = 'Pretitle Headline Title Description Extra line 1Extra line 2'; - await screen.findByRole('link', { - name: 'Title Headline Pretitle Subtitle Description Extra line 1Extra line 2', - }); -}); +test.each` + pretitleAs | titleAs | expectedLabel + ${undefined} | ${undefined} | ${titleFirst} + ${'h1'} | ${'h2'} | ${pretitleFirst} + ${'h2'} | ${'h1'} | ${titleFirst} +`( + 'PosterCard "href" label with pretitleAs={$pretitleAs} and titleAs={$titleAs}', + async ({pretitleAs, titleAs, expectedLabel}) => { + render( + + + Extra line 1 + Extra line 2 +
+ } + /> + + ); -test('PosterCard "to" label', async () => { - render( - - - Extra line 1 - Extra line 2 - - } - /> - - ); + await screen.findByRole('link', {name: expectedLabel}); + } +); - await screen.findByRole('link', { - name: 'Title Headline Pretitle Subtitle Description Extra line 1Extra line 2', - }); -}); +test.each` + pretitleAs | titleAs | expectedLabel + ${undefined} | ${undefined} | ${titleFirst} + ${'h1'} | ${'h2'} | ${pretitleFirst} + ${'h2'} | ${'h1'} | ${titleFirst} +`( + 'PosterCard "to" label with pretitleAs={$pretitleAs} and titleAs={$titleAs}', + async ({pretitleAs, titleAs, expectedLabel}) => { + render( + + + Extra line 1 + Extra line 2 + + } + /> + + ); -test('PosterCard "onPress" label', async () => { - render( - - {}} - isInverse - headline="Headline" - pretitle="Pretitle" - title="Title" - subtitle="Subtitle" - description="Description" - extra={ - - Extra line 1 - Extra line 2 - - } - /> - - ); + await screen.findByRole('link', {name: expectedLabel}); + } +); - await screen.findByRole('button', { - name: 'Title Headline Pretitle Subtitle Description Extra line 1Extra line 2', - }); -}); +test.each` + pretitleAs | titleAs | expectedLabel + ${undefined} | ${undefined} | ${titleFirst} + ${'h1'} | ${'h2'} | ${pretitleFirst} + ${'h2'} | ${'h1'} | ${titleFirst} +`( + 'PosterCard "onPress" label with pretitleAs={$pretitleAs} and titleAs={$titleAs}', + async ({pretitleAs, titleAs, expectedLabel}) => { + render( + + {}} + isInverse + headline="Headline" + pretitle="Pretitle" + pretitleAs={pretitleAs} + title="Title" + titleAs={titleAs} + description="Description" + extra={ + + Extra line 1 + Extra line 2 + + } + /> + + ); + + await screen.findByRole('button', {name: expectedLabel}); + } +); test('PosterCard onClose custom label', async () => { const closeSpy = jest.fn(); diff --git a/src/card.tsx b/src/card.tsx index c0c50a78ea..535797545a 100644 --- a/src/card.tsx +++ b/src/card.tsx @@ -27,20 +27,21 @@ import {getPrefixedDataAttributes} from './utils/dom'; import {isRunningAcceptanceTest} from './utils/platform'; import {applyCssVars} from './utils/css'; import * as tokens from './text-tokens'; +import { + isBiggerHeading, + type DataAttributes, + type HeadingType, + type IconProps, + type RendersElement, + type RendersNullableElement, + type TrackingEvent, +} from './utils/types'; import type {Variant} from './theme-variant-context'; import type {PressHandler} from './touchable'; import type {VideoElement, VideoSource} from './video'; import type {ButtonLink, ButtonPrimary, ButtonSecondary} from './button'; import type {ExclusifyUnion} from './utils/utility-types'; -import type { - DataAttributes, - HeadingType, - IconProps, - RendersElement, - RendersNullableElement, - TrackingEvent, -} from './utils/types'; const useInnerText = () => { const [text, setText] = React.useState(''); @@ -369,6 +370,7 @@ type CardContentProps = { headline?: string | RendersNullableElement; headlineRef?: (instance: HTMLElement | null) => void; pretitle?: string; + pretitleAs?: HeadingType; pretitleLinesMax?: number; title?: string; titleAs?: HeadingType; @@ -387,6 +389,7 @@ const CardContent = ({ headline, headlineRef, pretitle, + pretitleAs, pretitleLinesMax, title, titleAs = 'h3', @@ -405,31 +408,81 @@ const CardContent = ({
{/** using flex instead of nested Stacks, this way we can rearrange texts so the DOM structure makes more sense for screen reader users */}
- {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} - -
+ {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 && (
@@ -502,6 +555,7 @@ interface MediaCardBaseProps { asset?: React.ReactElement; headline?: string | RendersNullableElement; pretitle?: string; + pretitleAs?: HeadingType; pretitleLinesMax?: number; title?: string; titleAs?: HeadingType; @@ -535,6 +589,7 @@ export const MediaCard = React.forwardRef( asset, headline, pretitle, + pretitleAs, pretitleLinesMax, subtitle, subtitleLinesMax, @@ -561,7 +616,12 @@ export const MediaCard = React.forwardRef( const ariaLabel = ariaLabelProp || - [title, headlineText, pretitle, subtitle, description, extraText].filter(Boolean).join(' '); + (isBiggerHeading(titleAs, pretitleAs) + ? [title, headlineText, pretitle, subtitle, description, extraText] + : [pretitle, headlineText, title, subtitle, description, extraText] + ) + .filter(Boolean) + .join(' '); return ( ( headline={headline} headlineRef={headlineRef} pretitle={pretitle} + pretitleAs={pretitleAs} pretitleLinesMax={pretitleLinesMax} title={title} titleAs={titleAs} @@ -633,6 +694,7 @@ export const NakedCard = React.forwardRef( asset, headline, pretitle, + pretitleAs, pretitleLinesMax, subtitle, subtitleLinesMax, @@ -660,7 +722,12 @@ export const NakedCard = React.forwardRef( const ariaLabel = ariaLabelProp || - [title, headlineText, pretitle, subtitle, description, extraText].filter(Boolean).join(' '); + (isBiggerHeading(titleAs, pretitleAs) + ? [title, headlineText, pretitle, subtitle, description, extraText] + : [pretitle, headlineText, title, subtitle, description, extraText] + ) + .filter(Boolean) + .join(' '); return ( ( headline={headline} headlineRef={headlineRef} pretitle={pretitle} + pretitleAs={pretitleAs} pretitleLinesMax={pretitleLinesMax} title={title} titleAs={titleAs} @@ -857,6 +925,7 @@ interface DataCardBaseProps { asset?: React.ReactElement; headline?: string | RendersNullableElement; pretitle?: string; + pretitleAs?: HeadingType; pretitleLinesMax?: number; title?: string; titleAs?: HeadingType; @@ -891,6 +960,7 @@ export const DataCard = React.forwardRef( asset, headline, pretitle, + pretitleAs, pretitleLinesMax, title, titleAs = 'h3', @@ -921,7 +991,12 @@ export const DataCard = React.forwardRef( const ariaLabel = ariaLabelProp || - [title, headlineText, pretitle, description, extraText].filter(Boolean).join(' '); + (isBiggerHeading(titleAs, pretitleAs) + ? [title, headlineText, pretitle, subtitle, description, extraText] + : [pretitle, headlineText, title, subtitle, description, extraText] + ) + .filter(Boolean) + .join(' '); return ( ( headline={headline} headlineRef={headlineRef} pretitle={pretitle} + pretitleAs={pretitleAs} pretitleLinesMax={pretitleLinesMax} title={title} titleAs={titleAs} @@ -1136,8 +1212,10 @@ export const SnapCard = React.forwardRef( interface DisplayCardContentProps { title?: React.ReactNode; + titleAs?: HeadingType; headline?: React.ReactNode; pretitle?: React.ReactNode; + pretitleAs?: HeadingType; subtitle?: React.ReactNode; description?: React.ReactNode; extra?: React.ReactNode; @@ -1147,8 +1225,10 @@ interface DisplayCardContentProps { const DisplayCardContent = ({ title, + titleAs = 'h3', headline, pretitle, + pretitleAs, subtitle, description, extra, @@ -1158,21 +1238,44 @@ const DisplayCardContent = ({ // using flex instead of nested Stacks, this way we can rearrange texts so the DOM structure makes more sense for screen reader users return (
- {title && ( -
- {title} -
- )} - {headline && ( - // assuming that the headline will always be followed by one of: pretitle, title, subtitle, description -
- {headline} -
- )} - {pretitle && ( -
- {pretitle} -
+ {isBiggerHeading(titleAs, pretitleAs) ? ( + <> + {title && ( +
+ {title} +
+ )} + {headline && ( + // assuming that the headline will always be followed by one of: pretitle, title, subtitle, description +
+ {headline} +
+ )} + {pretitle && ( +
+ {pretitle} +
+ )} + + ) : ( + <> + {pretitle && ( +
+ {pretitle} +
+ )} + {headline && ( + // assuming that the headline will always be followed by one of: pretitle, title, subtitle, description +
+ {headline} +
+ )} + {title && ( +
+ {title} +
+ )} + )} {subtitle && ( @@ -1212,6 +1315,7 @@ interface CommonDisplayCardProps { dataAttributes?: DataAttributes; headline?: React.ReactComponentElement; pretitle?: string; + pretitleAs?: HeadingType; pretitleLinesMax?: number; title: string; titleAs?: HeadingType; @@ -1275,6 +1379,7 @@ const DisplayCard = React.forwardRef( asset, headline, pretitle, + pretitleAs, pretitleLinesMax, title, titleAs = 'h3', @@ -1323,7 +1428,12 @@ const DisplayCard = React.forwardRef( const ariaLabel = ariaLabelProp || - [title, headlineText, pretitle, description, extraText].filter(Boolean).join(' '); + (isBiggerHeading(titleAs, pretitleAs) + ? [title, headlineText, pretitle, description, extraText] + : [pretitle, headlineText, title, description, extraText] + ) + .filter(Boolean) + .join(' '); return ( ( ) : undefined } + titleAs={titleAs} headline={headline} pretitle={ pretitle ? ( @@ -1425,6 +1536,7 @@ const DisplayCard = React.forwardRef( ) : undefined } + pretitleAs={pretitleAs} description={ description ? ( ; pretitle?: string; + pretitleAs?: HeadingType; pretitleLinesMax?: number; title?: string; titleAs?: HeadingType; @@ -1568,6 +1681,7 @@ export const PosterCard = React.forwardRef( asset, headline, pretitle, + pretitleAs, pretitleLinesMax, title, titleAs = 'h3', @@ -1633,7 +1747,12 @@ export const PosterCard = React.forwardRef( const ariaLabel = ariaLabelProp || - [title, headlineText, pretitle, subtitle, description, extraText].filter(Boolean).join(' '); + (isBiggerHeading(titleAs, pretitleAs) + ? [title, headlineText, pretitle, subtitle, description, extraText] + : [pretitle, headlineText, title, subtitle, description, extraText] + ) + .filter(Boolean) + .join(' '); return ( ( ) : undefined } + titleAs={titleAs} headline={headline} pretitle={ pretitle ? ( @@ -1738,6 +1858,7 @@ export const PosterCard = React.forwardRef( ) : undefined } + pretitleAs={pretitleAs} subtitle={ subtitle ? ( Date: Thu, 14 Nov 2024 12:26:35 +0100 Subject: [PATCH 8/9] simplify headings helper --- src/card.tsx | 2 +- src/cover-hero.tsx | 2 +- src/header.tsx | 2 +- src/hero.tsx | 2 +- src/utils/headings.tsx | 15 +++++++++++++++ src/utils/types.tsx | 7 ------- 6 files changed, 19 insertions(+), 11 deletions(-) create mode 100644 src/utils/headings.tsx diff --git a/src/card.tsx b/src/card.tsx index 535797545a..4824f7ae24 100644 --- a/src/card.tsx +++ b/src/card.tsx @@ -28,7 +28,6 @@ import {isRunningAcceptanceTest} from './utils/platform'; import {applyCssVars} from './utils/css'; import * as tokens from './text-tokens'; import { - isBiggerHeading, type DataAttributes, type HeadingType, type IconProps, @@ -36,6 +35,7 @@ import { type RendersNullableElement, type TrackingEvent, } from './utils/types'; +import {isBiggerHeading} from './utils/headings'; import type {Variant} from './theme-variant-context'; import type {PressHandler} from './touchable'; diff --git a/src/cover-hero.tsx b/src/cover-hero.tsx index 8fb287964d..659681b2f0 100644 --- a/src/cover-hero.tsx +++ b/src/cover-hero.tsx @@ -12,7 +12,7 @@ import * as mediaStyles from './image.css'; import GridLayout from './grid-layout'; import {CoverHeroMedia} from './cover-hero-media'; import {getPrefixedDataAttributes} from './utils/dom'; -import {isBiggerHeading} from './utils/types'; +import {isBiggerHeading} from './utils/headings'; import type {DataAttributes, HeadingType} from './utils/types'; import type {ImageProps, VideoProps} from './cover-hero-media'; diff --git a/src/header.tsx b/src/header.tsx index 90d79c5e03..a875fa7ea7 100644 --- a/src/header.tsx +++ b/src/header.tsx @@ -11,12 +11,12 @@ import * as styles from './header.css'; import {getPrefixedDataAttributes} from './utils/dom'; import {Title3, Title4} from './title'; import { - isBiggerHeading, type DataAttributes, type HeadingType, type RendersElement, type RendersNullableElement, } from './utils/types'; +import {isBiggerHeading} from './utils/headings'; import type NavigationBreadcrumbs from './navigation-breadcrumbs'; import type {ButtonPrimary, ButtonSecondary} from './button'; diff --git a/src/hero.tsx b/src/hero.tsx index 9ef4f956e5..1e096ebb59 100644 --- a/src/hero.tsx +++ b/src/hero.tsx @@ -16,12 +16,12 @@ import {useIsInverseOrMediaVariant} from './theme-variant-context'; import {applyCssVars} from './utils/css'; import {InternalResponsiveLayout, ResetResponsiveLayout} from './responsive-layout'; import { - isBiggerHeading, type DataAttributes, type HeadingType, type RendersElement, type RendersNullableElement, } from './utils/types'; +import {isBiggerHeading} from './utils/headings'; import type Image from './image'; import type Video from './video'; diff --git a/src/utils/headings.tsx b/src/utils/headings.tsx new file mode 100644 index 0000000000..9fb25bf1aa --- /dev/null +++ b/src/utils/headings.tsx @@ -0,0 +1,15 @@ +import type {HeadingType} from './types'; + +export const isBiggerHeading = (heading: HeadingType, otherHeading?: HeadingType): boolean => { + // In case headings are equal, we consider that the first one has more priority + if (!otherHeading || heading === otherHeading) { + return true; + } + + if (heading === 'span') { + return false; + } + + // Both are header tags + return heading < otherHeading; +}; diff --git a/src/utils/types.tsx b/src/utils/types.tsx index 55bec073f9..cf5a45dd32 100644 --- a/src/utils/types.tsx +++ b/src/utils/types.tsx @@ -14,10 +14,3 @@ export type IconProps = { export type ByBreakpoint = T | {mobile: T; tablet?: T; desktop: T}; export type HeadingType = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'span'; - -export const isBiggerHeading = (heading: HeadingType, otherHeading?: HeadingType): boolean => { - if (!otherHeading) { - return true; - } - return heading !== 'span' && (otherHeading === 'span' || heading < otherHeading); -}; From 49e5adf91b3929dc54aacb3f26d2d8fd5047e582 Mon Sep 17 00:00:00 2001 From: marcoskolodny Date: Thu, 14 Nov 2024 12:39:21 +0100 Subject: [PATCH 9/9] fix tests --- src/__tests__/cover-hero-test.tsx | 2 +- src/__tests__/hero-test.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/__tests__/cover-hero-test.tsx b/src/__tests__/cover-hero-test.tsx index 118f53345d..2d9ea5231e 100644 --- a/src/__tests__/cover-hero-test.tsx +++ b/src/__tests__/cover-hero-test.tsx @@ -17,7 +17,7 @@ test.each` ${'h3'} | ${undefined} | ${titleFirst} ${'h3'} | ${'h1'} | ${titleFirst} ${'span'} | ${'h1'} | ${titleFirst} - ${'h1'} | ${undefined} | ${pretitleFirst} + ${'h1'} | ${undefined} | ${titleFirst} ${'h1'} | ${'h3'} | ${pretitleFirst} ${'h1'} | ${'span'} | ${pretitleFirst} `( diff --git a/src/__tests__/hero-test.tsx b/src/__tests__/hero-test.tsx index d65920c9ec..491b71ac18 100644 --- a/src/__tests__/hero-test.tsx +++ b/src/__tests__/hero-test.tsx @@ -17,7 +17,7 @@ test.each` ${'h3'} | ${undefined} | ${titleFirst} ${'h3'} | ${'h1'} | ${titleFirst} ${'span'} | ${'h1'} | ${titleFirst} - ${'h1'} | ${undefined} | ${pretitleFirst} + ${'h1'} | ${undefined} | ${titleFirst} ${'h1'} | ${'h3'} | ${pretitleFirst} ${'h1'} | ${'span'} | ${pretitleFirst} `(