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
4 changes: 1 addition & 3 deletions dotcom-rendering/src/components/CardPicture.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,7 @@ const decideImageWidths = (
return [{ breakpoint: breakpoints.mobile, width: 80, aspectRatio }];

case 'highlights-card':
return [
{ breakpoint: breakpoints.mobile, width: 112, aspectRatio },
];
return [{ breakpoint: breakpoints.mobile, width: 98, aspectRatio }];

case 'carousel':
return [
Expand Down
159 changes: 71 additions & 88 deletions dotcom-rendering/src/components/Masthead/HighlightsCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,28 +35,28 @@ export type HighlightsCardProps = {
};

const container = css`
display: grid;
background-color: ${palette('--highlights-container-background')};
/** Relative positioning is required to absolutely
position the card link overlay */
position: relative;
display: flex;
flex-direction: column;
justify-content: space-between;
column-gap: ${space[2]}px;
grid-template-areas:
'headline headline'
'media-icon media-icon'
'. image';

/* Applied word-break: break-word to prevent text overflow
and ensure long words break onto the next line.
This is important since the highlights card can only take up a set portion
of the screen to allow for the peeping card on mobile and grid layout
on larger breakpoints, and the image has a fixed width on all breakpoints. */
/** Relative positioning is required to absolutely position the card link overlay */
position: relative;
padding: 10px 10px 0 10px;
background-color: ${palette('--highlights-card-background')};

/**
* Applied word-break: break-word to prevent text overflow and ensure long words break
* onto the next line. This is important since the highlights card can only take up a
* set portion of the screen to allow for the peeping card on mobile and layout
* on larger breakpoints, and the image has a fixed width on all breakpoints.
*/
word-break: break-word;

${until.mobileMedium} {
min-height: 174px;
}

${between.mobileMedium.and.desktop} {
${between.mobileMedium.and.tablet} {
min-height: 194px;
height: 100%;
}
Expand All @@ -66,44 +66,16 @@ const container = css`
width: 160px;
}

${from.desktop} {
width: 300px;
grid-template-areas:
'headline image'
'media-icon image';
${from.tablet} {
width: 280px;
flex-direction: row;
}
`;

const headline = css`
grid-area: headline;
margin-bottom: ${space[1]}px;
`;

const mediaIcon = css`
grid-area: media-icon;
display: flex;
align-items: flex-end;
`;

const imageArea = css`
grid-area: image;
height: 112px;
width: 112px;
align-self: end;
position: relative;
${until.desktop} {
margin-top: ${space[2]}px;
}
${from.desktop} {
align-self: start;
width: 300px;
}
`;

/** Avatar alignment is an exception and should align with the bottom of the card *if* there is a gap.*/
const avatarAlignmentStyles = css`
align-self: end;
`;

const hoverStyles = css`
:hover .image-overlay {
position: absolute;
Expand All @@ -123,12 +95,21 @@ const hoverStyles = css`
}
`;

const content = css`
display: flex;
flex-direction: column;
justify-content: space-between;
gap: ${space[1]}px;

${from.tablet} {
padding-bottom: 10px;
}
`;
const starWrapper = css`
background-color: ${palette('--star-rating-background')};
color: ${palette('--star-rating-fill')};
width: fit-content;
grid-area: media-icon;
align-self: flex-end;
margin-top: ${space[1]}px;
color: ${palette('--star-rating-fill')};
background-color: ${palette('--star-rating-background')};
`;

export const HighlightsCard = ({
Expand Down Expand Up @@ -158,12 +139,12 @@ export const HighlightsCard = ({
isExternalLink={isExternalLink}
/>

<div css={headline}>
<div css={content}>
<CardHeadline
headlineText={headlineText}
format={format}
fontSizes={{
desktop: 'xsmall',
desktop: 'xxsmall',
tablet: 'xxsmall',
mobileMedium: 'xxsmall',
mobile: 'tiny',
Expand All @@ -177,49 +158,51 @@ export const HighlightsCard = ({
headlineColour={palette('--highlights-card-headline')}
kickerColour={palette('--highlights-card-kicker-text')}
/>
</div>

{!isUndefined(starRating) && (
<div css={starWrapper}>
<StarRating rating={starRating} size="small" />
</div>
)}

{!!mainMedia && isMediaCard && (
<div css={mediaIcon}>
{mainMedia.type === 'Video' && (
<Pill
content={secondsToDuration(mainMedia.duration)}
prefix="Video"
icon={<SvgMediaControlsPlay width={18} />}
/>
)}
{mainMedia.type === 'Audio' && (
<Pill
content={mainMedia.duration}
prefix="Podcast"
icon={<SvgMediaControlsPlay width={18} />}
/>
)}
{mainMedia.type === 'Gallery' && (
<Pill
content={mainMedia.count}
prefix="Gallery"
icon={<SvgCamera />}
/>
)}
</div>
)}
{!isUndefined(starRating) && (
<div css={starWrapper}>
<StarRating rating={starRating} size="small" />
</div>
)}

{!!mainMedia && isMediaCard && (
<div>
{mainMedia.type === 'Video' && (
<Pill
content={secondsToDuration(
mainMedia.duration,
)}
prefix="Video"
icon={<SvgMediaControlsPlay width={18} />}
/>
)}
{mainMedia.type === 'Audio' && (
<Pill
content={mainMedia.duration}
prefix="Podcast"
icon={<SvgMediaControlsPlay width={18} />}
/>
)}
{mainMedia.type === 'Gallery' && (
<Pill
content={mainMedia.count}
prefix="Gallery"
icon={<SvgCamera />}
/>
)}
</div>
)}
</div>

<div css={[imageArea, avatarUrl && avatarAlignmentStyles]}>
{(!!avatarUrl || !!image) && (
<HighlightsCardImage
imageLoading={imageLoading}
image={image}
avatarUrl={avatarUrl}
byline={byline}
mainMedia={mainMedia}
/>
</div>
)}
</div>
</FormatBoundary>
);
Expand Down
41 changes: 31 additions & 10 deletions dotcom-rendering/src/components/Masthead/HighlightsCardImage.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,28 @@
import { css } from '@emotion/react';
import { space, until } from '@guardian/source/foundations';
import type { DCRFrontImage } from '../../types/front';
import type { MainMedia } from '../../types/mainMedia';
import { Avatar } from '../Avatar';
import type { Loading } from '../CardPicture';
import { CardPicture } from '../CardPicture';

const imageStyles = css`
position: relative;
align-self: flex-end;
flex-shrink: 0;
height: 98px;
width: 98px;

${until.tablet} {
margin-top: ${space[2]}px;
}
`;

/** An avatar should align with the bottom of the card */
const nonAvatarImageStyles = css`
margin-bottom: 10px;
`;

type Props = {
imageLoading: Loading;
image?: DCRFrontImage;
Expand All @@ -21,19 +40,21 @@ export const HighlightsCardImage = ({
}: Props) => {
if (avatarUrl) {
return (
<Avatar
src={avatarUrl}
alt={byline ?? ''}
shape="cutout"
imageSize="large"
/>
<div css={imageStyles}>
<Avatar
src={avatarUrl}
alt={byline ?? ''}
shape="cutout"
imageSize="large"
/>
</div>
);
}

if (image) {
if (mainMedia?.type === 'Audio' && mainMedia.podcastImage?.src) {
return (
<>
<div css={[imageStyles, nonAvatarImageStyles]}>
<CardPicture
imageSize="medium"
mainImage={mainMedia.podcastImage.src}
Expand All @@ -43,12 +64,12 @@ export const HighlightsCardImage = ({
aspectRatio="1:1"
/>
<div className="image-overlay" />
</>
</div>
);
}

return (
<>
<div css={[imageStyles, nonAvatarImageStyles]}>
<CardPicture
imageSize="highlights-card"
mainImage={image.src}
Expand All @@ -59,7 +80,7 @@ export const HighlightsCardImage = ({
/>
{/* This image overlay is styled when the CardLink is hovered */}
<div className="image-overlay circular" />
</>
</div>
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ const carouselStyles = css`
scroll-padding-left: 160px;
padding-left: 160px;
}

${from.wide} {
scroll-padding-left: 240px;
padding-left: 240px;
Expand All @@ -71,7 +70,7 @@ const itemStyles = css`
scroll-snap-align: start;
grid-area: span 1;
position: relative;
margin: ${space[3]}px 10px;
margin: ${space[2]}px 10px ${space[3]}px;
:first-child {
${from.tablet} {
margin-left: 0;
Expand Down Expand Up @@ -162,7 +161,7 @@ const generateCarouselColumnStyles = (totalCards: number) => {
/**
* Typically, Ophan tracking data gets determined in the front layout component.
* As the highlights exists outside of this front layout (in the header), we need to construct these fields here.
* */
*/
const getOphanInfo = (frontId?: string) => {
const ophanComponentName = ophanComponentId('highlights');
const ophanComponentLink = `container-${0} | ${ophanComponentName}`;
Expand Down
Loading