Skip to content

Commit

Permalink
Video timestamp pills on cards (#13208)
Browse files Browse the repository at this point in the history
* Add media icons to pills

* update audio text colour

* add standalone video pill to bottom of card

* Display video pill in bottom left of card content if video article

* Use pill component for timestamp on youtube video overlay

* Use pill for live indicator

* Switch to new video pill placment for beta containers

* Fix linting

* improve comments for video cards

* Reorder imports for linter
  • Loading branch information
abeddow91 authored Jan 28, 2025
1 parent b101e7f commit 7560ed7
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 68 deletions.
87 changes: 63 additions & 24 deletions dotcom-rendering/src/components/Card/Card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
ArticleSpecial,
} from '../../lib/articleFormat';
import { isMediaCard as isAMediaCard } from '../../lib/cardHelpers';
import { isWithinTwelveHours, secondsToDuration } from '../../lib/formatTime';
import { getZIndex } from '../../lib/getZIndex';
import { DISCUSSION_ID_DATA_ATTRIBUTE } from '../../lib/useCommentCount';
import { palette } from '../../palette';
Expand All @@ -36,7 +37,6 @@ import type { Loading } from '../CardPicture';
import { CardPicture } from '../CardPicture';
import { Island } from '../Island';
import { LatestLinks } from '../LatestLinks.importable';
import { MediaDuration } from '../MediaDuration';
import { MediaMeta } from '../MediaMeta';
import { Pill } from '../Pill';
import { Slideshow } from '../Slideshow';
Expand Down Expand Up @@ -342,13 +342,13 @@ const getHeadlinePosition = ({
return 'inner';
};

export const isWithinTwelveHours = (webPublicationDate: string): boolean => {
const timeDiffMs = Math.abs(
new Date().getTime() - new Date(webPublicationDate).getTime(),
);
const timeDiffHours = timeDiffMs / (1000 * 60 * 60);
return timeDiffHours <= 12;
};
const liveBulletStyles = css`
width: 9px;
height: 9px;
border-radius: 50%;
background-color: ${palette('--pill-bullet')};
margin-right: ${space[1]}px;
`;

export const Card = ({
linkTo,
Expand Down Expand Up @@ -426,6 +426,20 @@ export const Card = ({

const isBetaContainer = BETA_CONTAINERS.includes(containerType ?? '');

/**
* A "video article" refers to standalone video content presented as the main focus of the article.
* It is treated as a media card in the design system.
*/
const isVideoArticle =
mainMedia?.type === 'Video' && format.design === ArticleDesign.Video;

/**
* Articles with a video as the main media but not classified as "video articles"
* are styled differently and are not treated as media cards.
*/
const isVideoMainMedia =
mainMedia?.type === 'Video' && format.design !== ArticleDesign.Video;

const decideAge = () => {
if (!webPublicationDate) return undefined;
const withinTwelveHours = isWithinTwelveHours(webPublicationDate);
Expand Down Expand Up @@ -485,10 +499,29 @@ export const Card = ({
margin-top: auto;
`}
>
{isVideoArticle && (
<>
{mainMedia.duration === 0 ? (
<Pill
content={'Live'}
icon={<div css={liveBulletStyles} />}
iconSize={'small'}
/>
) : (
<Pill
content={secondsToDuration(mainMedia.duration)}
icon={<SvgMediaControlsPlay />}
iconSize={'small'}
/>
)}
</>
)}

{mainMedia?.type === 'Audio' && (
<Pill
content={audioDuration ?? ''}
icon={<SvgMediaControlsPlay />}
iconSize={'small'}
/>
)}
{mainMedia?.type === 'Gallery' && (
Expand All @@ -514,10 +547,7 @@ export const Card = ({
* Check media type to determine if pill, or article metadata & icon shown.
* Currently pills are only shown within beta containers.
*/
const showPill =
isBetaContainer &&
mainMedia &&
(mainMedia.type === 'Audio' || mainMedia.type === 'Gallery');
const showPill = isBetaContainer && !!mainMedia;

const media = getMedia({
imageUrl: image?.src,
Expand Down Expand Up @@ -855,7 +885,11 @@ export const Card = ({
}
index={index}
duration={
media.mainMedia.duration
isBetaContainer &&
isVideoArticle
? undefined
: media.mainMedia
.duration
}
posterImage={
media.mainMedia.images
Expand Down Expand Up @@ -938,18 +972,23 @@ export const Card = ({
roundedCorners={isOnwardContent}
aspectRatio={aspectRatio}
/>
{mainMedia?.type === 'Video' &&
mainMedia.duration > 0 && (
<MediaDuration
mediaDuration={mainMedia.duration}
imagePositionOnDesktop={
imagePositionOnDesktop
}
imagePositionOnMobile={
imagePositionOnMobile
}
{isVideoMainMedia && mainMedia.duration > 0 && (
<div
css={css`
position: absolute;
top: ${space[2]}px;
right: ${space[2]}px;
`}
>
<Pill
content={secondsToDuration(
mainMedia.duration,
)}
icon={<SvgMediaControlsPlay />}
iconSize={'small'}
/>
)}
</div>
)}
</>
)}
{media.type === 'crossword' && (
Expand Down
10 changes: 1 addition & 9 deletions dotcom-rendering/src/components/FeatureCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { css } from '@emotion/react';
import { space } from '@guardian/source/foundations';
import { Link, SvgMediaControlsPlay } from '@guardian/source/react-components';
import { ArticleDesign, type ArticleFormat } from '../lib/articleFormat';
import { secondsToDuration } from '../lib/formatTime';
import { isWithinTwelveHours, secondsToDuration } from '../lib/formatTime';
import { getZIndex } from '../lib/getZIndex';
import { DISCUSSION_ID_DATA_ATTRIBUTE } from '../lib/useCommentCount';
import { palette } from '../palette';
Expand Down Expand Up @@ -218,14 +218,6 @@ const getMedia = ({
return undefined;
};

export const isWithinTwelveHours = (webPublicationDate: string): boolean => {
const timeDiffMs = Math.abs(
new Date().getTime() - new Date(webPublicationDate).getTime(),
);
const timeDiffHours = timeDiffMs / (1000 * 60 * 60);
return timeDiffHours <= 12;
};

const CardAge = ({
showClock,
absoluteServerTimes,
Expand Down
63 changes: 28 additions & 35 deletions dotcom-rendering/src/components/YoutubeAtom/YoutubeAtomOverlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
headlineMedium20,
palette as sourcePalette,
space,
textSansBold12,
} from '@guardian/source/foundations';
import type { ArticleFormat } from '../../lib/articleFormat';
import { secondsToDuration } from '../../lib/formatTime';
Expand All @@ -19,6 +18,8 @@ import type {
import { PlayIcon } from '../Card/components/PlayIcon';
import { FormatBoundary } from '../FormatBoundary';
import { Kicker } from '../Kicker';
import { Pill } from '../Pill';
import { SvgMediaControlsPlay } from '../SvgMediaControlsPlay';
import { YoutubeAtomPicture } from './YoutubeAtomPicture';

type Props = {
Expand Down Expand Up @@ -72,38 +73,14 @@ const pillStyles = css`
position: absolute;
top: ${space[2]}px;
right: ${space[2]}px;
${textSansBold12};
color: ${palette('--pill-text')};
`;

const durationPillStyles = css`
background-color: rgba(0, 0, 0, 0.7);
border-radius: ${space[3]}px;
padding: ${space[1]}px ${space[3]}px;
display: inline-flex;
line-height: ${space[4]}px;
`;

const livePillStyles = css`
border-radius: ${space[10]}px;
padding: ${space[1]}px ${space[2]}px;
gap: ${space[2]}px;
background-color: ${palette('--pill-background')};
display: flex;
align-items: center;
`;

const liveBulletStyles = css`
::before {
content: '';
width: 9px;
height: 9px;
border-radius: 50%;
background-color: ${palette('--pill-bullet')};
display: inline-block;
position: relative;
margin-right: 0.1875rem;
}
width: 9px;
height: 9px;
border-radius: 50%;
background-color: ${palette('--pill-bullet')};
margin-right: ${space[1]}px;
`;

const textOverlayStyles = css`
Expand Down Expand Up @@ -151,7 +128,7 @@ export const YoutubeAtomOverlay = ({
const id = `youtube-overlay-${uniqueId}`;
const hasDuration = !isUndefined(duration) && duration > 0;
//** We infer that a video is a livestream if the duration is set to 0. This is a soft contract with Editorial who manual set the duration of videos */
const isLiveStream = duration === 0;
const isLiveStream = !isUndefined(duration) && duration === 0;
const image = overrideImage ?? posterImage;
const hidePillOnMobile =
imagePositionOnMobile === 'right' || imagePositionOnMobile === 'left';
Expand All @@ -175,8 +152,20 @@ export const YoutubeAtomOverlay = ({
/>
)}
{isLiveStream && (
<div css={[pillStyles, livePillStyles, liveBulletStyles]}>
Live
<div
css={
hidePillOnMobile
? css`
display: none;
`
: pillStyles
}
>
<Pill
content={'Live'}
icon={<div css={[liveBulletStyles]} />}
iconSize={'small'}
/>
</div>
)}
{hasDuration && (
Expand All @@ -186,10 +175,14 @@ export const YoutubeAtomOverlay = ({
? css`
display: none;
`
: [pillStyles, durationPillStyles]
: pillStyles
}
>
{secondsToDuration(duration)}
<Pill
content={secondsToDuration(duration)}
icon={<SvgMediaControlsPlay />}
iconSize={'small'}
/>
</div>
)}
<PlayIcon
Expand Down
8 changes: 8 additions & 0 deletions dotcom-rendering/src/lib/formatTime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,11 @@ export const secondsToDuration = (secs?: number): string => {
}
return duration.join(':');
};

export const isWithinTwelveHours = (webPublicationDate: string): boolean => {
const timeDiffMs = Math.abs(
new Date().getTime() - new Date(webPublicationDate).getTime(),
);
const timeDiffHours = timeDiffMs / (1000 * 60 * 60);
return timeDiffHours <= 12;
};

0 comments on commit 7560ed7

Please sign in to comment.