Skip to content

Commit 90ba964

Browse files
authored
Card Pill (#15285)
* Add a CardPill component * Use CardPill on highlights card * Use CardPill in Card and only display age when its a storylines card * Show a pill if it is a youtube atom feature card thats also a video page * Use CardPill in CardFooter * Move card pill decision into card footer * Move card pill decision into card footer * Do not show pill if the card is in a second onwards container in a gallery article * Only pad top if its meta data * Update stories * Use isLive property from main media to determine if the live video pill should be shown * Remove default null from card pill so that the switch case is exhaustive. This will protect developers from missing adding a new pill to CardPill if main media is extending to include a new type.
1 parent 837be6e commit 90ba964

7 files changed

Lines changed: 190 additions & 230 deletions

File tree

dotcom-rendering/src/components/Card/Card.tsx

Lines changed: 18 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { css } from '@emotion/react';
22
import { isUndefined } from '@guardian/libs';
33
import { between, from, space, until } from '@guardian/source/foundations';
4-
import { Hide, Link, SvgCamera } from '@guardian/source/react-components';
4+
import { Hide, Link } from '@guardian/source/react-components';
55
import {
66
ArticleDesign,
77
type ArticleFormat,
@@ -337,14 +337,6 @@ const decideSublinkPosition = (
337337
return alignment === 'vertical' ? 'inner' : 'outer';
338338
};
339339

340-
const liveBulletStyles = css`
341-
width: 9px;
342-
height: 9px;
343-
border-radius: 50%;
344-
background-color: ${palette('--pill-bullet')};
345-
margin-right: ${space[1]}px;
346-
`;
347-
348340
export const Card = ({
349341
linkTo,
350342
format,
@@ -489,85 +481,6 @@ export const Card = ({
489481
</Link>
490482
);
491483

492-
const MediaOrNewsletterPill = () => (
493-
<div
494-
css={css`
495-
margin-top: auto;
496-
display: flex;
497-
${isStorylines &&
498-
`
499-
flex-direction: column;
500-
gap: ${space[1]}px;
501-
align-items: flex-start;
502-
`}
503-
`}
504-
>
505-
{/* Usually, we either display the pill or the footer,
506-
but if the card appears in the storylines section on tag pages
507-
then we do want to display the date on these cards as well as the media pill.
508-
*/}
509-
{isStorylines && (
510-
<CardFooter
511-
format={format}
512-
age={decideAge()}
513-
commentCount={<CommentCount />}
514-
cardBranding={
515-
isOnwardContent ? <LabsBranding /> : undefined
516-
}
517-
showLivePlayable={showLivePlayable}
518-
/>
519-
)}
520-
521-
{mainMedia?.type === 'YoutubeVideo' && isVideoArticle && (
522-
<>
523-
{mainMedia.isLive ? (
524-
<Pill
525-
content="Live"
526-
icon={<div css={liveBulletStyles} />}
527-
/>
528-
) : (
529-
<Pill
530-
content={secondsToDuration(mainMedia.duration)}
531-
icon={<SvgMediaControlsPlay width={18} />}
532-
prefix="Video"
533-
/>
534-
)}
535-
</>
536-
)}
537-
{mainMedia?.type === 'Audio' && (
538-
<Pill
539-
content={mainMedia.duration}
540-
icon={<SvgMediaControlsPlay width={18} />}
541-
prefix="Podcast"
542-
/>
543-
)}
544-
{mainMedia?.type === 'Gallery' && (
545-
<Pill
546-
content={mainMedia.count}
547-
icon={<SvgCamera />}
548-
prefix="Gallery"
549-
/>
550-
)}
551-
{mainMedia?.type === 'SelfHostedVideo' &&
552-
(format.design === ArticleDesign.Video ? (
553-
<Pill
554-
content=""
555-
icon={<SvgMediaControlsPlay width={18} />}
556-
prefix="Video"
557-
/>
558-
) : format.design === ArticleDesign.Audio ? (
559-
<Pill
560-
content=""
561-
icon={<SvgMediaControlsPlay width={18} />}
562-
prefix="Podcast"
563-
/>
564-
) : format.design === ArticleDesign.Gallery ? (
565-
<Pill content="" icon={<SvgCamera />} prefix="Gallery" />
566-
) : null)}
567-
{isNewsletter && <Pill content="Newsletter" />}
568-
</div>
569-
);
570-
571484
if (snapData?.embedHtml) {
572485
return (
573486
<SnapCssSandbox snapData={snapData}>
@@ -594,8 +507,6 @@ export const Card = ({
594507
- */
595508
const isMediaCardOrNewsletter = isMediaCard(format) || isNewsletter;
596509

597-
const showPill = isMediaCardOrNewsletter && !isGallerySecondaryOnward;
598-
599510
const media = getMedia({
600511
imageUrl: image?.src,
601512
imageAltText: image?.altText,
@@ -1300,32 +1211,22 @@ export const Card = ({
13001211
/>
13011212
)}
13021213

1303-
{!isOpinionCardWithAvatar && (
1304-
<>
1305-
{showPill ? (
1306-
<>
1307-
{!!branding &&
1308-
format.theme ===
1309-
ArticleSpecial.Labs &&
1310-
isOnwardContent && (
1311-
<LabsBranding />
1312-
)}
1313-
<MediaOrNewsletterPill />
1314-
</>
1315-
) : (
1316-
<CardFooter
1317-
format={format}
1318-
age={decideAge()}
1319-
commentCount={<CommentCount />}
1320-
cardBranding={
1321-
isOnwardContent ? (
1322-
<LabsBranding />
1323-
) : undefined
1324-
}
1325-
showLivePlayable={showLivePlayable}
1326-
/>
1327-
)}
1328-
</>
1214+
{!isOpinionCardWithAvatar && !showLivePlayable && (
1215+
<CardFooter
1216+
format={format}
1217+
age={decideAge()}
1218+
commentCount={<CommentCount />}
1219+
cardBranding={
1220+
isOnwardContent ? (
1221+
<LabsBranding />
1222+
) : undefined
1223+
}
1224+
mainMedia={
1225+
!isGallerySecondaryOnward
1226+
? mainMedia
1227+
: undefined
1228+
}
1229+
/>
13291230
)}
13301231
{showLivePlayable &&
13311232
liveUpdatesPosition === 'inner' && (
@@ -1410,12 +1311,11 @@ export const Card = ({
14101311

14111312
{decideOuterSublinks()}
14121313

1413-
{isOpinionCardWithAvatar && (
1314+
{isOpinionCardWithAvatar && !showLivePlayable && (
14141315
<CardFooter
14151316
format={format}
14161317
age={decideAge()}
14171318
commentCount={<CommentCount />}
1418-
showLivePlayable={showLivePlayable}
14191319
shouldReserveSpace={{
14201320
mobile: avatarPosition.mobile === 'bottom',
14211321
desktop: avatarPosition.desktop === 'bottom',

dotcom-rendering/src/components/Card/components/CardFooter.stories.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ export const WithAge = {
2323
design: ArticleDesign.Comment,
2424
theme: Pillar.Opinion,
2525
},
26-
showLivePlayable: false,
2726
age: <p>19h ago</p>,
2827
},
2928
} satisfies Story;
@@ -56,7 +55,15 @@ export const WithVideo = {
5655
...WithAge.args,
5756
mainMedia: {
5857
type: 'YoutubeVideo',
58+
id: 'abcdef',
59+
videoId: 'abcd',
60+
title: 'some title',
5961
duration: 972,
62+
width: 480,
63+
height: 288,
64+
origin: 'The Guardian',
65+
expired: false,
66+
image: 'https://i.guim.co.uk/img/media/e060e9b7c92433b3dfeccc98b9206778cda8b8e8/0_180_6680_4009/master/6680.jpg?width=600&quality=45&dpr=2&s=none',
6067
},
6168
},
6269
} satisfies Story;

dotcom-rendering/src/components/Card/components/CardFooter.tsx

Lines changed: 31 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,14 @@ import {
55
space,
66
textSansBold12,
77
} from '@guardian/source/foundations';
8-
import { SvgCamera } from '@guardian/source/react-components';
9-
import { Pill } from '../../../components/Pill';
10-
import { SvgMediaControlsPlay } from '../../../components/SvgMediaControlsPlay';
118
import { type ArticleFormat, ArticleSpecial } from '../../../lib/articleFormat';
12-
import { secondsToDuration } from '../../../lib/formatTime';
9+
import type { MainMedia } from '../../../types/mainMedia';
10+
import { CardPill } from '../../CardPill';
1311

1412
const contentStyles = css`
1513
margin-top: auto;
16-
padding-top: ${space[1]}px;
1714
display: flex;
18-
justify-content: 'flex-start';
15+
justify-content: flex-start;
1916
width: fit-content;
2017
align-items: center;
2118
${textSansBold12}
@@ -40,6 +37,10 @@ const contentStyles = css`
4037
}
4138
`;
4239

40+
const contentTopPaddingStyles = css`
41+
padding-top: ${space[1]}px;
42+
`;
43+
4344
const reserveSpaceStyles = (mobile: boolean, desktop: boolean) => css`
4445
min-height: ${mobile ? '14px' : 0};
4546
@@ -52,89 +53,66 @@ const labStyles = css`
5253
margin-top: ${space[1]}px;
5354
`;
5455

55-
type MainMedia =
56-
| { type: 'YoutubeVideo'; duration: number }
57-
| { type: 'SelfHostedVideo'; duration: number }
58-
| { type: 'Audio'; duration: string }
59-
| { type: 'Gallery'; count: string };
60-
6156
type Props = {
6257
format: ArticleFormat;
63-
showLivePlayable: boolean;
6458
age?: JSX.Element;
6559
commentCount?: JSX.Element;
6660
cardBranding?: JSX.Element;
6761
mainMedia?: MainMedia;
6862
isNewsletter?: boolean;
6963
shouldReserveSpace?: { mobile: boolean; desktop: boolean };
64+
isStorylines?: boolean;
7065
};
7166

7267
export const CardFooter = ({
7368
format,
74-
showLivePlayable,
7569
age,
7670
commentCount,
7771
cardBranding,
7872
mainMedia,
7973
isNewsletter,
8074
shouldReserveSpace,
75+
isStorylines,
8176
}: Props) => {
82-
if (showLivePlayable) return null;
77+
const shouldShowBranding =
78+
format.theme === ArticleSpecial.Labs && !!cardBranding;
8379

84-
if (format.theme === ArticleSpecial.Labs && cardBranding) {
85-
return <footer css={labStyles}>{cardBranding}</footer>;
86-
}
80+
const shouldShowPill =
81+
mainMedia?.type === 'YoutubeVideo' ||
82+
mainMedia?.type === 'Audio' ||
83+
mainMedia?.type === 'Gallery' ||
84+
isNewsletter;
8785

88-
if (mainMedia?.type === 'YoutubeVideo') {
86+
if (shouldShowPill) {
8987
return (
9088
<footer css={contentStyles}>
91-
<Pill
92-
content={
93-
<time>{secondsToDuration(mainMedia.duration)}</time>
94-
}
95-
prefix="Video"
96-
icon={<SvgMediaControlsPlay width={18} />}
97-
/>
98-
</footer>
99-
);
100-
}
89+
{shouldShowBranding && cardBranding}
10190

102-
if (mainMedia?.type === 'Audio') {
103-
return (
104-
<footer css={contentStyles}>
105-
<Pill
106-
content={<time>{mainMedia.duration}</time>}
107-
prefix="Podcast"
108-
icon={<SvgMediaControlsPlay width={18} />}
109-
/>
110-
</footer>
111-
);
112-
}
91+
{/**
92+
* Usually, we either display the pill or the footer,
93+
* but if the card appears in the storylines section on tag pages
94+
* then we do want to display the date on these cards as well as the media pill.
95+
* */}
96+
{isStorylines && age}
11397

114-
if (mainMedia?.type === 'Gallery') {
115-
return (
116-
<footer css={contentStyles}>
117-
<Pill
118-
content={mainMedia.count}
119-
prefix="Gallery"
120-
icon={<SvgCamera />}
98+
<CardPill
99+
mainMedia={mainMedia}
100+
isNewsletter={isNewsletter}
101+
format={format}
121102
/>
122103
</footer>
123104
);
124105
}
125106

126-
if (isNewsletter) {
127-
return (
128-
<footer css={contentStyles}>
129-
<Pill content="Newsletter" />
130-
</footer>
131-
);
107+
if (shouldShowBranding) {
108+
return <footer css={labStyles}>{cardBranding}</footer>;
132109
}
133110

134111
return (
135112
<footer
136113
css={[
137114
contentStyles,
115+
contentTopPaddingStyles,
138116
shouldReserveSpace &&
139117
reserveSpaceStyles(
140118
shouldReserveSpace.mobile,

0 commit comments

Comments
 (0)