@@ -9,15 +9,14 @@ import { palette } from '../../palette';
99import type { StarRating as Rating } from '../../types/content' ;
1010import type { DCRFrontImage } from '../../types/front' ;
1111import type { MainMedia } from '../../types/mainMedia' ;
12- import { Avatar } from '../Avatar' ;
1312import { CardLink } from '../Card/components/CardLink' ;
1413import { CardHeadline } from '../CardHeadline' ;
1514import type { Loading } from '../CardPicture' ;
16- import { CardPicture } from '../CardPicture' ;
1715import { FormatBoundary } from '../FormatBoundary' ;
1816import { Pill } from '../Pill' ;
1917import { StarRating } from '../StarRating/StarRating' ;
2018import { SvgMediaControlsPlay } from '../SvgMediaControlsPlay' ;
19+ import { HighlightsCardImage } from './HighlightsCardImage' ;
2120
2221export 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 : 300 px ;
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-
10879const 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
219132export 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 ) ;
0 commit comments