Skip to content

Commit 8f983a7

Browse files
committed
Give scrollable highlights cards colour
1 parent c87086f commit 8f983a7

File tree

4 files changed

+231
-207
lines changed

4 files changed

+231
-207
lines changed

dotcom-rendering/src/components/Masthead/HighlightsCard.tsx

Lines changed: 92 additions & 155 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,14 @@ import { palette } from '../../palette';
99
import type { StarRating as Rating } from '../../types/content';
1010
import type { DCRFrontImage } from '../../types/front';
1111
import type { MainMedia } from '../../types/mainMedia';
12-
import { Avatar } from '../Avatar';
1312
import { CardLink } from '../Card/components/CardLink';
1413
import { CardHeadline } from '../CardHeadline';
1514
import type { Loading } from '../CardPicture';
16-
import { CardPicture } from '../CardPicture';
1715
import { FormatBoundary } from '../FormatBoundary';
1816
import { Pill } from '../Pill';
1917
import { StarRating } from '../StarRating/StarRating';
2018
import { SvgMediaControlsPlay } from '../SvgMediaControlsPlay';
19+
import { HighlightsCardImage } from './HighlightsCardImage';
2120

2221
export type HighlightsCardProps = {
2322
linkTo: string;
@@ -35,29 +34,29 @@ export type HighlightsCardProps = {
3534
starRating?: Rating;
3635
};
3736

38-
const gridContainer = css`
39-
display: grid;
40-
background-color: ${palette('--highlights-container-background')};
41-
/** Relative positioning is required to absolutely
42-
position the card link overlay */
43-
position: relative;
37+
const container = css`
38+
display: flex;
39+
flex-direction: column;
40+
justify-content: space-between;
4441
column-gap: ${space[2]}px;
45-
grid-template-areas:
46-
'headline headline'
47-
'media-icon media-icon'
48-
'. image';
49-
50-
/* Applied word-break: break-word to prevent text overflow
51-
and ensure long words break onto the next line.
52-
This is important since the highlights card can only take up a set portion
53-
of the screen to allow for the peeping card on mobile and grid layout
54-
on larger breakpoints, and the image has a fixed width on all breakpoints. */
42+
/** Relative positioning is required to absolutely position the card link overlay */
43+
position: relative;
44+
padding: 10px 10px 0 10px;
45+
background-color: ${palette('--highlights-card-background')};
46+
47+
/**
48+
* Applied word-break: break-word to prevent text overflow and ensure long words break
49+
* onto the next line. This is important since the highlights card can only take up a
50+
* set portion of the screen to allow for the peeping card on mobile and layout
51+
* on larger breakpoints, and the image has a fixed width on all breakpoints.
52+
*/
5553
word-break: break-word;
54+
5655
${until.mobileMedium} {
5756
min-height: 174px;
5857
}
5958
60-
${between.mobileMedium.and.desktop} {
59+
${between.mobileMedium.and.tablet} {
6160
min-height: 194px;
6261
height: 100%;
6362
}
@@ -67,44 +66,16 @@ const gridContainer = css`
6766
width: 160px;
6867
}
6968
70-
${from.desktop} {
71-
width: 300px;
72-
grid-template-areas:
73-
'headline image'
74-
'media-icon image';
69+
${from.tablet} {
70+
width: 280px;
71+
flex-direction: row;
7572
}
76-
`;
77-
78-
const headline = css`
79-
grid-area: headline;
80-
margin-bottom: ${space[1]}px;
81-
`;
82-
83-
const mediaIcon = css`
84-
grid-area: media-icon;
85-
display: flex;
86-
align-items: flex-end;
87-
`;
8873
89-
const imageArea = css`
90-
grid-area: image;
91-
height: 98px;
92-
width: 98px;
93-
align-self: end;
94-
position: relative;
95-
${until.desktop} {
96-
margin-top: ${space[2]}px;
97-
}
9874
${from.desktop} {
99-
align-self: start;
75+
width: 300px;
10076
}
10177
`;
10278

103-
/** Avatar alignment is an exception and should align with the bottom of the card *if* there is a gap.*/
104-
const avatarAlignmentStyles = css`
105-
align-self: end;
106-
`;
107-
10879
const hoverStyles = css`
10980
:hover .image-overlay {
11081
position: absolute;
@@ -124,97 +95,39 @@ const hoverStyles = css`
12495
}
12596
`;
12697

127-
const starWrapper = css`
128-
background-color: ${palette('--star-rating-background')};
129-
color: ${palette('--star-rating-fill')};
130-
width: fit-content;
131-
grid-area: media-icon;
132-
align-self: flex-end;
133-
`;
134-
135-
const decideImage = (
136-
imageLoading: Loading,
137-
image?: DCRFrontImage,
138-
avatarUrl?: string,
139-
byline?: string,
140-
mainMedia?: MainMedia,
141-
) => {
142-
if (!image && !avatarUrl) {
143-
return null;
144-
}
98+
const content = css`
99+
display: flex;
100+
flex-direction: column;
101+
gap: ${space[1]}px;
145102
146-
if (avatarUrl) {
147-
return (
148-
<Avatar
149-
src={avatarUrl}
150-
alt={byline ?? ''}
151-
shape="cutout"
152-
imageSize="large"
153-
/>
154-
);
103+
${from.tablet} {
104+
padding-bottom: 10px;
155105
}
106+
`;
156107

157-
if (mainMedia?.type === 'Audio' && mainMedia.podcastImage?.src) {
158-
return (
159-
<>
160-
<CardPicture
161-
imageSize="medium"
162-
mainImage={mainMedia.podcastImage.src}
163-
alt={mainMedia.podcastImage.altText}
164-
loading={imageLoading}
165-
isCircular={false}
166-
aspectRatio="1:1"
167-
/>
168-
<div className="image-overlay" />
169-
</>
170-
);
171-
}
108+
const imageStyles = css`
109+
position: relative;
110+
align-self: flex-end;
111+
flex-shrink: 0;
112+
height: 98px;
113+
width: 98px;
172114
173-
if (!image) {
174-
return null;
115+
${until.tablet} {
116+
margin-top: ${space[2]}px;
175117
}
118+
`;
176119

177-
return (
178-
<>
179-
<CardPicture
180-
imageSize="highlights-card"
181-
mainImage={image.src}
182-
alt={image.altText}
183-
loading={imageLoading}
184-
isCircular={true}
185-
aspectRatio="1:1"
186-
/>
187-
{/* This image overlay is styled when the CardLink is hovered */}
188-
<div className="image-overlay circular" />
189-
</>
190-
);
191-
};
120+
/** An avatar should align with the bottom of the card */
121+
const nonAvatarImageStyles = css`
122+
margin-bottom: 10px;
123+
`;
192124

193-
const MediaPill = ({ mainMedia }: { mainMedia: MainMedia }) => (
194-
<div css={mediaIcon}>
195-
{mainMedia.type === 'Video' && (
196-
<Pill
197-
content={secondsToDuration(mainMedia.duration)}
198-
prefix="Video"
199-
icon={<SvgMediaControlsPlay width={18} />}
200-
/>
201-
)}
202-
{mainMedia.type === 'Audio' && (
203-
<Pill
204-
content={mainMedia.duration}
205-
prefix="Podcast"
206-
icon={<SvgMediaControlsPlay width={18} />}
207-
/>
208-
)}
209-
{mainMedia.type === 'Gallery' && (
210-
<Pill
211-
content={mainMedia.count}
212-
prefix="Gallery"
213-
icon={<SvgCamera />}
214-
/>
215-
)}
216-
</div>
217-
);
125+
const starWrapper = css`
126+
width: fit-content;
127+
margin-top: ${space[1]}px;
128+
color: ${palette('--star-rating-fill')};
129+
background-color: ${palette('--star-rating-background')};
130+
`;
218131

219132
export const HighlightsCard = ({
220133
linkTo,
@@ -235,20 +148,20 @@ export const HighlightsCard = ({
235148

236149
return (
237150
<FormatBoundary format={format}>
238-
<div css={[gridContainer, hoverStyles]}>
151+
<div css={[container, hoverStyles]}>
239152
<CardLink
240153
linkTo={linkTo}
241154
headlineText={headlineText}
242155
dataLinkName={dataLinkName}
243156
isExternalLink={isExternalLink}
244157
/>
245158

246-
<div css={headline}>
159+
<div css={content}>
247160
<CardHeadline
248161
headlineText={headlineText}
249162
format={format}
250163
fontSizes={{
251-
desktop: 'xsmall',
164+
desktop: 'xxsmall',
252165
tablet: 'xxsmall',
253166
mobileMedium: 'xxsmall',
254167
mobile: 'tiny',
@@ -262,27 +175,51 @@ export const HighlightsCard = ({
262175
headlineColour={palette('--highlights-card-headline')}
263176
kickerColour={palette('--highlights-card-kicker-text')}
264177
/>
265-
</div>
266178

267-
{!isUndefined(starRating) ? (
268-
<div css={starWrapper}>
269-
<StarRating rating={starRating} size="small" />
270-
</div>
271-
) : null}
272-
273-
{!!mainMedia && isMediaCard && (
274-
<MediaPill mainMedia={mainMedia} />
275-
)}
179+
{!isUndefined(starRating) && (
180+
<div css={starWrapper}>
181+
<StarRating rating={starRating} size="small" />
182+
</div>
183+
)}
276184

277-
<div css={[imageArea, avatarUrl && avatarAlignmentStyles]}>
278-
{decideImage(
279-
imageLoading,
280-
image,
281-
avatarUrl,
282-
byline,
283-
mainMedia,
185+
{!!mainMedia && isMediaCard && (
186+
<div>
187+
{mainMedia.type === 'Video' && (
188+
<Pill
189+
content={secondsToDuration(
190+
mainMedia.duration,
191+
)}
192+
prefix="Video"
193+
icon={<SvgMediaControlsPlay width={18} />}
194+
/>
195+
)}
196+
{mainMedia.type === 'Audio' && (
197+
<Pill
198+
content={mainMedia.duration}
199+
prefix="Podcast"
200+
icon={<SvgMediaControlsPlay width={18} />}
201+
/>
202+
)}
203+
{mainMedia.type === 'Gallery' && (
204+
<Pill
205+
content={mainMedia.count}
206+
prefix="Gallery"
207+
icon={<SvgCamera />}
208+
/>
209+
)}
210+
</div>
284211
)}
285212
</div>
213+
214+
<div css={[imageStyles, !avatarUrl && nonAvatarImageStyles]}>
215+
<HighlightsCardImage
216+
imageLoading={imageLoading}
217+
image={image}
218+
avatarUrl={avatarUrl}
219+
byline={byline}
220+
mainMedia={mainMedia}
221+
/>
222+
</div>
286223
</div>
287224
</FormatBoundary>
288225
);
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import type { DCRFrontImage } from '../../types/front';
2+
import type { MainMedia } from '../../types/mainMedia';
3+
import { Avatar } from '../Avatar';
4+
import type { Loading } from '../CardPicture';
5+
import { CardPicture } from '../CardPicture';
6+
7+
type Props = {
8+
imageLoading: Loading;
9+
image?: DCRFrontImage;
10+
avatarUrl?: string;
11+
byline?: string;
12+
mainMedia?: MainMedia;
13+
};
14+
15+
export const HighlightsCardImage = ({
16+
imageLoading,
17+
image,
18+
avatarUrl,
19+
byline,
20+
mainMedia,
21+
}: Props) => {
22+
if (avatarUrl) {
23+
return (
24+
<Avatar
25+
src={avatarUrl}
26+
alt={byline ?? ''}
27+
shape="cutout"
28+
imageSize="large"
29+
/>
30+
);
31+
}
32+
33+
if (image) {
34+
if (mainMedia?.type === 'Audio' && mainMedia.podcastImage?.src) {
35+
return (
36+
<>
37+
<CardPicture
38+
imageSize="medium"
39+
mainImage={mainMedia.podcastImage.src}
40+
alt={mainMedia.podcastImage.altText}
41+
loading={imageLoading}
42+
isCircular={false}
43+
aspectRatio="1:1"
44+
/>
45+
<div className="image-overlay" />
46+
</>
47+
);
48+
}
49+
50+
return (
51+
<>
52+
<CardPicture
53+
imageSize="highlights-card"
54+
mainImage={image.src}
55+
alt={image.altText}
56+
loading={imageLoading}
57+
isCircular={true}
58+
aspectRatio="1:1"
59+
/>
60+
{/* This image overlay is styled when the CardLink is hovered */}
61+
<div className="image-overlay circular" />
62+
</>
63+
);
64+
}
65+
66+
return null;
67+
};

0 commit comments

Comments
 (0)