Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion dotcom-rendering/src/components/Card/components/LI.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,8 @@ type Props = {
offsetBottomPaddingOnDivider?: boolean;
/** Overrides the vertical divider colour */
verticalDividerColour?: string;

isVisible?: boolean;
isSlideInCarousel?: boolean;
};

export const LI = ({
Expand All @@ -117,6 +117,7 @@ export const LI = ({
offsetBottomPaddingOnDivider = false,
verticalDividerColour = palette('--section-border'),
isVisible = true,
isSlideInCarousel = false,
}: Props) => {
// Decide sizing
const sizeStyles = decideSize(percentage, stretch);
Expand All @@ -138,6 +139,10 @@ export const LI = ({
snapAlignStart && snapAlignStartStyles,
{ visibility: isVisible ? 'visible' : 'hidden' },
]}
{...(isSlideInCarousel && {
role: 'group',
'aria-roledescription': 'slide',
})}
>
{children}
</li>
Expand Down
8 changes: 6 additions & 2 deletions dotcom-rendering/src/components/Carousel.importable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,7 @@ const CarouselCard = ({
padSidesMobileOverride: space[2],
padSidesOverride: space[2],
})}
isSlideInCarousel={true}
>
<Card
linkTo={linkTo}
Expand Down Expand Up @@ -917,6 +918,9 @@ export const Carousel = ({
]}
data-component={onwardsSource}
data-link={formatAttrString(heading)}
role="region"
aria-roledescription="carousel"
aria-label={heading}
>
<Header
heading={heading}
Expand Down Expand Up @@ -953,8 +957,8 @@ export const Carousel = ({
starRating,
} = trail;

// Don't try to render cards that have no publication date. This property is technically optional
// but we rarely if ever expect it not to exist
// Don't try to render cards that have no publication date. This property
// is technically optional but we rarely if ever expect it not to exist.
if (!webPublicationDate) return null;

const image = trailImage && {
Expand Down
1 change: 1 addition & 0 deletions dotcom-rendering/src/components/FrontSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -758,6 +758,7 @@ export const FrontSection = ({
isLabs={isLabs}
/>
}
sectionId={sectionId}
collectionBranding={collectionBranding}
/>

Expand Down
25 changes: 19 additions & 6 deletions dotcom-rendering/src/components/FrontSectionTitle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { BrandingLabel } from './BrandingLabel';
type Props = {
title: React.ReactNode;
collectionBranding: CollectionBranding | undefined;
sectionId: string;
};

const titleStyle = css`
Expand Down Expand Up @@ -77,7 +78,11 @@ const aboutThisLinkAdvertisingPartnerStyles = css`
color: ${sourcePalette.news[400]};
`;

export const FrontSectionTitle = ({ title, collectionBranding }: Props) => {
export const FrontSectionTitle = ({
title,
collectionBranding,
sectionId,
}: Props) => {
switch (collectionBranding?.kind) {
case 'foundation': {
const {
Expand Down Expand Up @@ -117,7 +122,7 @@ export const FrontSectionTitle = ({ title, collectionBranding }: Props) => {

if (isFrontBranding || isContainerBranding) {
return (
<div css={titleStyle}>
<div id={`${sectionId}-title`} css={titleStyle}>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So that aria-labelledby can target the appropriate element. This is better than prop drilling the title name down to the components that refer to it.

{title}
<div
css={css`
Expand Down Expand Up @@ -148,7 +153,7 @@ export const FrontSectionTitle = ({ title, collectionBranding }: Props) => {
<Hide until="leftCol">
<BrandingLabel branding={collectionBranding.branding} />
</Hide>
<div css={titleStyle}>
<div id={`${sectionId}-title`} css={titleStyle}>
<Hide from="leftCol">
<BrandingLabel
branding={collectionBranding.branding}
Expand All @@ -171,7 +176,7 @@ export const FrontSectionTitle = ({ title, collectionBranding }: Props) => {
logo.label.toLowerCase() === 'exclusive advertising partner';
if (isFrontBranding || isContainerBranding) {
return (
<div css={titleStyle}>
<div id={`${sectionId}-title`} css={titleStyle}>
{title}
{isAdvertisingPartnerOrExclusive ? (
<hr css={advertisingPartnerDottedBorder} />
Expand Down Expand Up @@ -212,10 +217,18 @@ export const FrontSectionTitle = ({ title, collectionBranding }: Props) => {
</div>
);
}
return <div css={titleStyle}>{title}</div>;
return (
<div id={`${sectionId}-title`} css={titleStyle}>
{title}
</div>
);
}
case undefined: {
return <div css={titleStyle}>{title}</div>;
return (
<div id={`${sectionId}-title`} css={titleStyle}>
{title}
</div>
);
}
default: {
assertUnreachable(collectionBranding);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ const CardWrapper = ({ children }: { children: React.ReactNode }) => {
export const Default = {};

export const WithQuotes: Story = {
name: 'With Quotes',
args: {
showQuotedHeadline: true,
},
Expand Down Expand Up @@ -101,7 +100,6 @@ export const WithAvatar: Story = {
avatarUrl:
'https://uploads.guim.co.uk/2017/10/06/George-Monbiot,-L.png',
},
name: 'With Avatar',
};

export const WithMediaIcon: Story = {
Expand All @@ -116,8 +114,6 @@ export const WithMediaIcon: Story = {
duration: '30:65',
},
},

name: 'With Media Icon',
};

export const WithLiveKicker: Story = {
Expand All @@ -128,8 +124,6 @@ export const WithLiveKicker: Story = {
theme: Pillar.News,
},
},

name: 'With Live Kicker',
};

export const WithStarRating: Story = {
Expand All @@ -141,8 +135,6 @@ export const WithStarRating: Story = {
},
starRating: 4,
},

name: 'With Star Rating',
};

export const WithPodcastSeriesImage: Story = {
Expand All @@ -165,6 +157,4 @@ export const WithPodcastSeriesImage: Story = {
duration: '31:16',
},
},

name: 'With Podcast Series Image',
};
18 changes: 16 additions & 2 deletions dotcom-rendering/src/components/ScrollableCarousel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type Props = {
sectionId?: string;
shouldStackCards?: { desktop: boolean; mobile: boolean };
gapSizes?: GapSizes;
isBelowTabletBreakpoint?: boolean;
};

/**
Expand Down Expand Up @@ -234,6 +235,7 @@ export const ScrollableCarousel = ({
sectionId,
shouldStackCards = { desktop: false, mobile: false },
gapSizes = { column: 'large', row: 'large' },
isBelowTabletBreakpoint = false,
}: Props) => {
const carouselRef = useRef<HTMLOListElement | null>(null);
const [previousButtonEnabled, setPreviousButtonEnabled] = useState(false);
Expand Down Expand Up @@ -369,7 +371,7 @@ export const ScrollableCarousel = ({

return (
<div css={containerStyles}>
<ol
<ul

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

preferred to ol

#15389 (comment)

ref={carouselRef}
css={[
carouselStyles,
Expand All @@ -383,9 +385,15 @@ export const ScrollableCarousel = ({
]}
data-heatphan-type="carousel"
onFocus={scrollToCardOnFocus}
{...(isBelowTabletBreakpoint && {
role: 'region',
'aria-roledescription': 'carousel',
'aria-labelledby': `${sectionId}-title`,
'aria-live': 'polite',
})}
>
{children}
</ol>
</ul>

{showNavigation && (
<CarouselNavigationButtons
Expand All @@ -412,10 +420,12 @@ ScrollableCarousel.Item = ({
isStackingCarousel = false,
children,
borderColour = palette('--card-border-top'),
isBelowTabletBreakpoint = false,
}: {
isStackingCarousel?: boolean;
children: React.ReactNode;
borderColour?: string;
isBelowTabletBreakpoint?: boolean;
}) => (
<li
css={[
Expand All @@ -424,6 +434,10 @@ ScrollableCarousel.Item = ({
? stackedRowLeftBorderStyles(borderColour)
: singleRowLeftBorderStyles(borderColour),
]}
{...(isBelowTabletBreakpoint && {
role: 'group',
'aria-roledescription': 'slide',
})}
>
{children}
</li>
Expand Down
12 changes: 11 additions & 1 deletion dotcom-rendering/src/components/ScrollableFeature.importable.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { until } from '@guardian/source/foundations';
import { ArticleDesign } from '../lib/articleFormat';
import { removeMediaRulePrefix, useMatchMedia } from '../lib/useMatchMedia';
import type {
AspectRatio,
DCRContainerPalette,
Expand Down Expand Up @@ -31,14 +33,22 @@ export const ScrollableFeature = ({
aspectRatio,
collectionId,
}: Props) => {
const isBelowTabletBreakpoint = useMatchMedia(

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did consider whether the added accessibility was worth the extra javascript. I've come to the conclusion that if we are going to use carousels in this way then the extra javascript needed to make them more accessible should be built in to that decision.

removeMediaRulePrefix(until.tablet),
);

return (
<ScrollableCarousel
carouselLength={trails.length}
visibleCarouselSlidesOnMobile={1}
visibleCarouselSlidesOnTablet={3}
isBelowTabletBreakpoint={isBelowTabletBreakpoint}
>
{trails.map((card, index) => (
<ScrollableCarousel.Item key={card.url}>
<ScrollableCarousel.Item
key={card.url}
isBelowTabletBreakpoint={isBelowTabletBreakpoint}
>
<FeatureCard
linkTo={card.url}
format={card.format}
Expand Down
12 changes: 11 additions & 1 deletion dotcom-rendering/src/components/ScrollableMedium.importable.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { until } from '@guardian/source/foundations';
import { isMediaCard } from '../lib/cardHelpers';
import { removeMediaRulePrefix, useMatchMedia } from '../lib/useMatchMedia';
import type {
AspectRatio,
DCRContainerPalette,
Expand Down Expand Up @@ -33,20 +35,28 @@ export const ScrollableMedium = ({
aspectRatio,
sectionId,
}: Props) => {
const isBelowTabletBreakpoint = useMatchMedia(
removeMediaRulePrefix(until.tablet),
);

return (
<ScrollableCarousel
carouselLength={trails.length}
visibleCarouselSlidesOnMobile={2}
visibleCarouselSlidesOnTablet={4}
sectionId={sectionId}
isBelowTabletBreakpoint={isBelowTabletBreakpoint}
>
{trails.map((trail) => {
const imagePosition = isMediaCard(trail.format)
? 'top'
: 'bottom';

return (
<ScrollableCarousel.Item key={trail.url}>
<ScrollableCarousel.Item
key={trail.url}
isBelowTabletBreakpoint={isBelowTabletBreakpoint}
>
<FrontCard
trail={trail}
imageLoading={imageLoading}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { until } from '@guardian/source/foundations';
import { removeMediaRulePrefix, useMatchMedia } from '../lib/useMatchMedia';
import type {
AspectRatio,
DCRContainerPalette,
Expand Down Expand Up @@ -61,6 +63,10 @@ export const ScrollableSmall = ({
const mobileBottomCards = [1, 3];
const desktopBottomCards = [2, 3];

const isBelowTabletBreakpoint = useMatchMedia(
removeMediaRulePrefix(until.tablet),
);

return (
<ScrollableCarousel
carouselLength={Math.ceil(trails.length / 2)}
Expand All @@ -69,12 +75,14 @@ export const ScrollableSmall = ({
sectionId={sectionId}
shouldStackCards={{ desktop: trails.length > 2, mobile: true }}
gapSizes={{ column: 'large', row: 'medium' }}
isBelowTabletBreakpoint={isBelowTabletBreakpoint}
>
{trails.map((trail, index) => {
return (
<ScrollableCarousel.Item
key={trail.url}
isStackingCarousel={true}
isBelowTabletBreakpoint={isBelowTabletBreakpoint}
>
<FrontCard
trail={trail}
Expand Down
Loading
Loading