diff --git a/dotcom-rendering/src/components/Card/Card.tsx b/dotcom-rendering/src/components/Card/Card.tsx
index dc6c97cfe9f..1d57306813d 100644
--- a/dotcom-rendering/src/components/Card/Card.tsx
+++ b/dotcom-rendering/src/components/Card/Card.tsx
@@ -1,18 +1,17 @@
import { css } from '@emotion/react';
import { isUndefined } from '@guardian/libs';
import { between, from, space, until } from '@guardian/source/foundations';
-import { Hide, Link, SvgCamera } from '@guardian/source/react-components';
+import { Hide } from '@guardian/source/react-components';
import {
ArticleDesign,
type ArticleFormat,
ArticleSpecial,
} from '../../lib/articleFormat';
import { isMediaCard } from '../../lib/cardHelpers';
-import { isWithinTwelveHours, secondsToDuration } from '../../lib/formatTime';
+import { secondsToDuration } from '../../lib/formatTime';
import { appendLinkNameMedia } from '../../lib/getDataLinkName';
import { getZIndex } from '../../lib/getZIndex';
import { getOphanComponents } from '../../lib/labs';
-import { DISCUSSION_ID_DATA_ATTRIBUTE } from '../../lib/useCommentCount';
import { BETA_CONTAINERS } from '../../model/enhanceCollections';
import { palette } from '../../palette';
import type { Branding } from '../../types/branding';
@@ -31,7 +30,6 @@ import type { MainMedia } from '../../types/mainMedia';
import type { OnwardsSource } from '../../types/onwards';
import { Avatar } from '../Avatar';
import { BrandingLabel } from '../BrandingLabel';
-import { CardCommentCount } from '../CardCommentCount.importable';
import { CardHeadline, type ResponsiveFontSize } from '../CardHeadline';
import type { Loading } from '../CardPicture';
import { CardPicture } from '../CardPicture';
@@ -50,8 +48,7 @@ import { SupportingKeyStoriesContent } from '../SupportingKeyStoriesContent';
import { SvgMediaControlsPlay } from '../SvgMediaControlsPlay';
import { YoutubeBlockComponent } from '../YoutubeBlockComponent.importable';
import { AvatarContainer } from './components/AvatarContainer';
-import { CardAge } from './components/CardAge';
-import { CardFooter } from './components/CardFooter';
+import { CardFooter } from './components/CardFooterNew';
import {
CardLayout,
decideAvatarPosition,
@@ -337,14 +334,6 @@ const decideSublinkPosition = (
return alignment === 'vertical' ? 'inner' : 'outer';
};
-const liveBulletStyles = css`
- width: 9px;
- height: 9px;
- border-radius: 50%;
- background-color: ${palette('--pill-bullet')};
- margin-right: ${space[1]}px;
-`;
-
export const Card = ({
linkTo,
format,
@@ -431,143 +420,6 @@ export const Card = ({
*/
const isVideoArticle = format.design === ArticleDesign.Video;
- const isLabs = format.theme === ArticleSpecial.Labs;
-
- const decideAge = () => {
- if (!webPublicationDate) return undefined;
- const withinTwelveHours = isWithinTwelveHours(webPublicationDate);
-
- const shouldShowAge =
- isStorylines ||
- isTagPage ||
- !!onwardsSource ||
- (showAge && withinTwelveHours);
-
- if (!shouldShowAge) return undefined;
-
- return (
-
- );
- };
-
- const CommentCount = () =>
- !!discussionId && (
-
-
-
-
-
- );
-
- const MediaOrNewsletterPill = () => (
-
- {/* Usually, we either display the pill or the footer,
- but if the card appears in the storylines section on tag pages
- then we do want to display the date on these cards as well as the media pill.
- */}
- {isStorylines && (
-
}
- cardBranding={
- isOnwardContent ?
: undefined
- }
- showLivePlayable={showLivePlayable}
- />
- )}
-
- {mainMedia?.type === 'YoutubeVideo' && isVideoArticle && (
- <>
- {mainMedia.isLive ? (
-
}
- />
- ) : (
-
}
- prefix="Video"
- />
- )}
- >
- )}
- {mainMedia?.type === 'Audio' && (
-
}
- prefix="Podcast"
- />
- )}
- {mainMedia?.type === 'Gallery' && (
-
}
- prefix="Gallery"
- />
- )}
- {mainMedia?.type === 'SelfHostedVideo' &&
- (format.design === ArticleDesign.Video ? (
-
}
- prefix="Video"
- />
- ) : format.design === ArticleDesign.Audio ? (
-
}
- prefix="Podcast"
- />
- ) : format.design === ArticleDesign.Gallery ? (
-
} prefix="Gallery" />
- ) : null)}
- {isNewsletter &&
}
-
- );
-
if (snapData?.embedHtml) {
return (
@@ -873,7 +725,7 @@ export const Card = ({
alignment="end"
ophanComponentLink={dataAttributes?.ophanComponentLink}
ophanComponentName={dataAttributes?.ophanComponentName}
- isLabs={isLabs}
+ isLabs={format.theme === ArticleSpecial.Labs}
dataTestId="card-branding-logo"
/>
@@ -896,7 +748,7 @@ export const Card = ({
alignment="end"
ophanComponentLink={dataAttributes?.ophanComponentLink}
ophanComponentName={dataAttributes?.ophanComponentName}
- isLabs={isLabs}
+ isLabs={format.theme === ArticleSpecial.Labs}
/>
>
@@ -1300,33 +1152,28 @@ export const Card = ({
/>
)}
+ {/** Footer rendered inside the card boundary */}
{!isOpinionCardWithAvatar && (
- <>
- {showPill ? (
- <>
- {!!branding &&
- format.theme ===
- ArticleSpecial.Labs &&
- isOnwardContent && (
-
- )}
-
- >
- ) : (
- }
- cardBranding={
- isOnwardContent ? (
-
- ) : undefined
- }
- showLivePlayable={showLivePlayable}
- />
- )}
- >
+ }
+ mainMedia={mainMedia}
+ isNewsletter={isNewsletter}
+ showPill={showPill}
+ isStorylines={isStorylines}
+ onwardsSource={onwardsSource}
+ webPublicationDate={webPublicationDate}
+ showClock={showClock}
+ serverTime={serverTime}
+ isTagPage={isTagPage}
+ discussionId={discussionId}
+ discussionApiUrl={discussionApiUrl}
+ linkTo={linkTo}
+ />
)}
+
{showLivePlayable &&
liveUpdatesPosition === 'inner' && (
}
showLivePlayable={showLivePlayable}
+ showAge={showAge}
+ cardBranding={}
+ mainMedia={mainMedia}
+ isNewsletter={isNewsletter}
shouldReserveSpace={{
mobile: avatarPosition.mobile === 'bottom',
desktop: avatarPosition.desktop === 'bottom',
}}
+ showPill={showPill}
+ isStorylines={isStorylines}
+ onwardsSource={onwardsSource}
+ webPublicationDate={webPublicationDate}
+ showClock={showClock}
+ serverTime={serverTime}
+ isTagPage={isTagPage}
+ discussionId={discussionId}
+ discussionApiUrl={discussionApiUrl}
+ linkTo={linkTo}
/>
)}
-
- {!isOnwardContent && format.theme === ArticleSpecial.Labs && (
-
- )}
);
};
diff --git a/dotcom-rendering/src/components/Card/components/CardFooterNew.tsx b/dotcom-rendering/src/components/Card/components/CardFooterNew.tsx
new file mode 100644
index 00000000000..327e488249c
--- /dev/null
+++ b/dotcom-rendering/src/components/Card/components/CardFooterNew.tsx
@@ -0,0 +1,291 @@
+import { css } from '@emotion/react';
+import {
+ from,
+ palette as sourcePalette,
+ space,
+ textSansBold12,
+} from '@guardian/source/foundations';
+import { Link, SvgCamera } from '@guardian/source/react-components';
+import {
+ ArticleDesign,
+ type ArticleFormat,
+ ArticleSpecial,
+} from '../../../lib/articleFormat';
+import {
+ isWithinTwelveHours,
+ secondsToDuration,
+} from '../../../lib/formatTime';
+import { getZIndex } from '../../../lib/getZIndex';
+import { DISCUSSION_ID_DATA_ATTRIBUTE } from '../../../lib/useCommentCount';
+import { palette } from '../../../palette';
+import type { MainMedia } from '../../../types/mainMedia';
+import type { OnwardsSource } from '../../../types/onwards';
+import { CardCommentCount } from '../../CardCommentCount.importable';
+import { Island } from '../../Island';
+import { Pill } from '../../Pill';
+import { SvgMediaControlsPlay } from '../../SvgMediaControlsPlay';
+import { CardAge } from './CardAge';
+
+const contentStyles = css`
+ margin-top: auto;
+ padding-top: ${space[1]}px;
+ display: flex;
+ justify-content: 'flex-start';
+ width: fit-content;
+ align-items: center;
+ ${textSansBold12}
+`;
+
+const dividerStyles = css`
+ > {
+ /* The dividing line is applied only to the second child. This ensures that no
+ dividing line is added when there is only one child in the container. */
+ :nth-child(2) {
+ ::before {
+ content: '';
+ display: block;
+ width: 1px;
+ height: 12px;
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ background-color: ${sourcePalette.neutral[60]};
+ margin-right: ${space[1]}px;
+ }
+ margin-left: ${space[1]}px;
+ padding-left: ${space[1]}px;
+ }
+ }
+`;
+
+const reserveSpaceStyles = (mobile: boolean, desktop: boolean) => css`
+ min-height: ${mobile ? '14px' : 0};
+
+ ${from.tablet} {
+ min-height: ${desktop ? '14px' : 0};
+ }
+`;
+
+type Props = {
+ format: ArticleFormat;
+ showLivePlayable: boolean;
+ cardBranding?: JSX.Element;
+ mainMedia?: MainMedia;
+ isNewsletter?: boolean;
+ shouldReserveSpace?: { mobile: boolean; desktop: boolean };
+ showAge?: boolean;
+ showPill?: boolean;
+ isStorylines: boolean;
+ onwardsSource?: OnwardsSource;
+ webPublicationDate?: string;
+ showClock?: boolean;
+ serverTime?: number;
+ isTagPage: boolean;
+ discussionId?: string;
+ discussionApiUrl: string;
+ linkTo: string;
+};
+
+const basePillStyles = css`
+ margin-top: auto;
+ display: flex;
+`;
+const storylinesPillStyles = css`
+ flex-direction: column;
+ gap: ${space[1]}px;
+ align-items: flex-start;
+`;
+const liveBulletStyles = css`
+ width: 9px;
+ height: 9px;
+ border-radius: 50%;
+ background-color: ${palette('--pill-bullet')};
+ margin-right: ${space[1]}px;
+`;
+
+const MediaOrNewsletterPill = ({
+ format,
+ mainMedia,
+ isNewsletter,
+ isStorylines,
+}: Pick) => (
+
+ {mainMedia?.type === 'YoutubeVideo' &&
+ format.design === ArticleDesign.Video && (
+ <>
+ {mainMedia.duration === 0 ? (
+
}
+ />
+ ) : (
+
+ {secondsToDuration(mainMedia.duration)}
+
+ }
+ icon={}
+ prefix="Video"
+ />
+ )}
+ >
+ )}
+
+ {mainMedia?.type === 'Audio' && (
+ {mainMedia.duration}}
+ icon={}
+ prefix="Podcast"
+ />
+ )}
+
+ {mainMedia?.type === 'Gallery' && (
+ }
+ prefix="Gallery"
+ />
+ )}
+
+ {mainMedia?.type === 'SelfHostedVideo' &&
+ (format.design === ArticleDesign.Video ? (
+ }
+ prefix="Video"
+ />
+ ) : format.design === ArticleDesign.Audio ? (
+ }
+ prefix="Podcast"
+ />
+ ) : format.design === ArticleDesign.Gallery ? (
+ } prefix="Gallery" />
+ ) : null)}
+
+ {isNewsletter && }
+
+);
+
+export const CardFooter = ({
+ format,
+ showLivePlayable,
+ showAge,
+ cardBranding,
+ mainMedia,
+ isNewsletter,
+ shouldReserveSpace,
+ showPill,
+ isStorylines,
+ onwardsSource,
+ webPublicationDate,
+ showClock,
+ serverTime,
+ isTagPage,
+ discussionId,
+ discussionApiUrl,
+ linkTo,
+}: Props) => {
+ // We don't show the footer for live playable cards
+ if (showLivePlayable) return undefined;
+
+ const canShowBranding =
+ !!cardBranding && format.theme === ArticleSpecial.Labs;
+
+ const getAge = () => {
+ if (!webPublicationDate) return undefined;
+ const withinTwelveHours = isWithinTwelveHours(webPublicationDate);
+
+ const shouldShowAge =
+ isStorylines ||
+ isTagPage ||
+ (!!onwardsSource && !canShowBranding) ||
+ (showAge && withinTwelveHours);
+
+ if (!shouldShowAge) return undefined;
+
+ return (
+
+ );
+ };
+
+ const getCommentCount = () =>
+ !!discussionId && (
+
+
+
+
+
+ );
+
+ return (
+
+ );
+};