Skip to content

Commit

Permalink
Add podcast image and footer pill in audio feature cards
Browse files Browse the repository at this point in the history
  • Loading branch information
domlander committed Jan 22, 2025
1 parent 9105220 commit df1dd83
Show file tree
Hide file tree
Showing 5 changed files with 182 additions and 104 deletions.
14 changes: 14 additions & 0 deletions dotcom-rendering/src/components/Card/components/CardFooter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { css } from '@emotion/react';
import { palette, space, textSansBold12 } from '@guardian/source/foundations';
import { SvgCamera } from '@guardian/source/react-components';
import { Pill } from '../../../components/Pill';
import { SvgMediaControlsPlay } from '../../../components/SvgMediaControlsPlay';
import { type ArticleFormat, ArticleSpecial } from '../../../lib/articleFormat';
import type { MainMedia } from '../../../types/mainMedia';

Expand Down Expand Up @@ -46,6 +47,7 @@ type Props = {
cardBranding?: JSX.Element;
mediaType?: MainMedia['type'];
galleryCount?: number;
audioDuration?: string;
};

export const CardFooter = ({
Expand All @@ -56,13 +58,25 @@ export const CardFooter = ({
showLivePlayable,
mediaType,
galleryCount,
audioDuration,
}: Props) => {
if (showLivePlayable) return null;

if (format.theme === ArticleSpecial.Labs && cardBranding) {
return <footer css={labStyles}>{cardBranding}</footer>;
}

if (mediaType === 'Audio' && audioDuration !== undefined) {
return (
<footer css={contentStyles}>
<Pill
content={<time>{audioDuration}</time>}
icon={<SvgMediaControlsPlay />}
/>
</footer>
);
}

if (mediaType === 'Gallery') {
return (
<footer css={contentStyles}>
Expand Down
13 changes: 13 additions & 0 deletions dotcom-rendering/src/components/FeatureCard.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,19 @@ export const Opinion: Story = {
},
};

export const Podcast: Story = {
args: {
image: {
src: 'https://media.guim.co.uk/ecb7f0bebe473d6ef1375b5cb60b78f9466a5779/0_229_3435_2061/master/3435.jpg',
altText: 'alt text',
},
mainMedia: {
type: 'Audio',
duration: 120,
},
},
};

export const Gallery: Story = {
args: {
image: {
Expand Down
256 changes: 152 additions & 104 deletions dotcom-rendering/src/components/FeatureCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type {
DCRSupportingContent,
} from '../types/front';
import type { MainMedia } from '../types/mainMedia';
import type { PodcastSeriesImage } from '../types/tag';
import { CardAge as AgeStamp } from './Card/components/CardAge';
import { CardFooter } from './Card/components/CardFooter';
import { CardLink } from './Card/components/CardLink';
Expand Down Expand Up @@ -74,6 +75,8 @@ export type Props = {
aspectRatio?: AspectRatio;
showQuotes?: boolean;
galleryCount?: number;
podcastImage?: PodcastSeriesImage;
audioDuration?: string;
};

const baseCardStyles = css`
Expand Down Expand Up @@ -117,6 +120,13 @@ const hoverStyles = css`
}
`;

const contentStyles = css`
position: absolute;
bottom: 0;
left: 0;
width: 100%;
`;

/**
* Image mask gradient has additional colour stops to emulate a non-linear
* ease in / ease out curve to make the transition smoother. Values were
Expand All @@ -126,14 +136,9 @@ const hoverStyles = css`
* https://css-tricks.com/easing-linear-gradients/
*/
const overlayStyles = css`
position: absolute;
bottom: 0;
left: 0;
right: 0;
display: flex;
flex-direction: column;
justify-content: flex-start;
flex-grow: 1;
gap: ${space[1]}px;
padding: 64px ${space[2]}px ${space[2]}px;
mask-image: linear-gradient(
Expand All @@ -151,6 +156,24 @@ const overlayStyles = css`
backdrop-filter: blur(12px) brightness(0.5);
`;

const podcastImageContainerStyles = css`
position: relative;
/* Needs to display above of the image mask overlay */
z-index: ${getZIndex('card-podcast-image')};
`;

const podcastImageStyles = css`
height: 80px;
width: 80px;
position: absolute;
/**
* Displays 8px above the text.
* desired space above text (8px) - padding-top of text container (64px) = -56px
*/
bottom: -${space[14]}px;
left: ${space[2]}px;
`;

const starRatingWrapper = css`
background-color: ${palette('--star-rating-background')};
color: ${palette('--star-rating-fill')};
Expand Down Expand Up @@ -296,6 +319,8 @@ export const FeatureCard = ({
starRating,
showQuotes,
galleryCount,
podcastImage,
audioDuration,
}: Props) => {
const hasSublinks = supportingContent && supportingContent.length > 0;

Expand Down Expand Up @@ -336,10 +361,6 @@ export const FeatureCard = ({
background-color: ${palette(
'--feature-card-background',
)};
img {
width: 100%;
display: block;
}
`}
>
{media.type === 'video' && (
Expand Down Expand Up @@ -395,106 +416,133 @@ export const FeatureCard = ({
{/* This image overlay is styled when the CardLink is hovered */}
<div className="image-overlay" />

<div css={overlayStyles}>
{/**
* Without the wrapping div the headline and
* byline would have space inserted between
* them due to being direct children of the
* flex container
*/}
<div>
<CardHeadline
headlineText={headlineText}
<div css={contentStyles}>
{mainMedia?.type === 'Audio' &&
!!podcastImage?.src && (
<div
css={
podcastImageContainerStyles
}
>
<div css={podcastImageStyles}>
<CardPicture
mainImage={
podcastImage.src
}
imageSize="podcast"
alt={
podcastImage.altText ??
''
}
loading="lazy"
roundedCorners={true}
aspectRatio="1:1"
/>
</div>
</div>
)}
<div css={overlayStyles}>
{/**
* Without the wrapping div the headline and
* byline would have space inserted between
* them due to being direct children of the
* flex container
*/}
<div>
<CardHeadline
headlineText={headlineText}
format={format}
fontSizes={headlineSizes}
showQuotes={showQuotes}
kickerText={
format.design ===
ArticleDesign.LiveBlog &&
!kickerText
? 'Live'
: kickerText
}
showPulsingDot={
format.design ===
ArticleDesign.LiveBlog ||
showPulsingDot
}
byline={byline}
showByline={showByline}
isExternalLink={isExternalLink}
headlineColour={palette(
'--feature-card-headline',
)}
kickerColour={palette(
'--feature-card-kicker-text',
)}
isBetaContainer={true}
/>
</div>

{starRating !== undefined ? (
<div css={starRatingWrapper}>
<StarRating
rating={starRating}
size="small"
/>
</div>
) : null}

{!!trailText && (
<div css={trailTextWrapper}>
<TrailText
trailText={trailText}
trailTextColour={palette(
'--feature-card-trail-text',
)}
trailTextSize={'regular'}
padBottom={false}
/>
</div>
)}
<CardFooter
format={format}
fontSizes={headlineSizes}
showQuotes={showQuotes}
kickerText={
format.design ===
ArticleDesign.LiveBlog &&
!kickerText
? 'Live'
: kickerText
age={
<CardAge
webPublicationDate={
webPublicationDate
}
showClock={!!showClock}
absoluteServerTimes={
absoluteServerTimes
}
/>
}
showPulsingDot={
format.design ===
ArticleDesign.LiveBlog ||
showPulsingDot
commentCount={
<CommentCount
linkTo={linkTo}
discussionId={discussionId}
discussionApiUrl={
discussionApiUrl
}
/>
}
byline={byline}
showByline={showByline}
isExternalLink={isExternalLink}
headlineColour={palette(
'--feature-card-headline',
)}
kickerColour={palette(
'--feature-card-kicker-text',
)}
isBetaContainer={true}
/**TODO: Determine if this is needed */
// cardBranding={
// branding ? (
// <CardBranding
// branding={branding}
// format={format}
// onwardsSource={
// onwardsSource
// }
// containerPalette={
// containerPalette
// }
// />
// ) : undefined
// }
showLivePlayable={false}
mediaType={mainMedia?.type}
galleryCount={galleryCount}
audioDuration={audioDuration}
/>
</div>

{starRating !== undefined ? (
<div css={starRatingWrapper}>
<StarRating
rating={starRating}
size="small"
/>
</div>
) : null}

{!!trailText && (
<div css={trailTextWrapper}>
<TrailText
trailText={trailText}
trailTextColour={palette(
'--feature-card-trail-text',
)}
trailTextSize={'regular'}
padBottom={false}
/>
</div>
)}
<CardFooter
format={format}
age={
<CardAge
webPublicationDate={
webPublicationDate
}
showClock={!!showClock}
absoluteServerTimes={
absoluteServerTimes
}
/>
}
commentCount={
<CommentCount
linkTo={linkTo}
discussionId={discussionId}
discussionApiUrl={
discussionApiUrl
}
/>
}
/**TODO: Determine if this is needed */
// cardBranding={
// branding ? (
// <CardBranding
// branding={branding}
// format={format}
// onwardsSource={
// onwardsSource
// }
// containerPalette={
// containerPalette
// }
// />
// ) : undefined
// }
showLivePlayable={false}
mediaType={mainMedia?.type}
galleryCount={galleryCount}
/>
</div>
</div>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ export const ScrollableFeature = ({
mobile: 'xsmall',
}}
galleryCount={card.galleryCount}
podcastImage={card.podcastImage}
audioDuration={card.audioDuration}
/>
</ScrollableCarousel.Item>
);
Expand Down
1 change: 1 addition & 0 deletions dotcom-rendering/src/lib/getZIndex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ const indices = [
// See: https://www.sarasoueidan.com/blog/nested-links/
'card-nested-link',
'card-link',
'card-podcast-image',
] as const;

// Implementation code - you don't need to change this to get a new index
Expand Down

0 comments on commit df1dd83

Please sign in to comment.