Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
7081754
wip
Georges-GNM Oct 31, 2025
28a08f6
wip2
Georges-GNM Nov 2, 2025
593e7ad
small tweaks
Georges-GNM Nov 2, 2025
619613a
remove unused import
Georges-GNM Nov 3, 2025
6669694
fetch local data
Georges-GNM Nov 11, 2025
8885f44
some tweaks from feedback and set up data pipeline
Georges-GNM Dec 1, 2025
a0a28ca
tweak opinion/media articles and add date
Georges-GNM Dec 1, 2025
c4552bb
Wip including using content provided by frontend
Georges-GNM Dec 9, 2025
db1804b
lint and gen schema
Georges-GNM Dec 9, 2025
1a87a4f
more lint fix
Georges-GNM Dec 9, 2025
56442fd
reorganise some of the code and delete some of the unnecessary parts
Georges-GNM Dec 9, 2025
15a6dbd
Add story
Georges-GNM Dec 9, 2025
735ec97
Add dates to multimedia and feature cards, and tweak storyline title
Georges-GNM Dec 10, 2025
bc6d9ad
tweak articles published since date
Georges-GNM Dec 10, 2025
b45588d
re-gen schemas
Georges-GNM Dec 10, 2025
ceaed1d
lint
Georges-GNM Dec 10, 2025
9e6b575
gen schema again after rebase
Georges-GNM Dec 10, 2025
24b7aed
tweak insertion check and add age to SC supporting content
Georges-GNM Dec 10, 2025
643e629
comment twweak and restore linkto
Georges-GNM Dec 11, 2025
4d86f78
bump ophan tracker js and make use of new component type
Georges-GNM Dec 11, 2025
d5fb9f0
rename fields
Georges-GNM Dec 14, 2025
e2f5c91
tidy
Georges-GNM Dec 15, 2025
56497b1
tidy
Georges-GNM Dec 17, 2025
3c40535
Merge branch 'main' into gl/sc-tag-page-mockup
Georges-GNM Dec 23, 2025
09dad00
wip tweaking supporting content
Georges-GNM Dec 24, 2025
87bfddf
tweak supporting content in key stories
Georges-GNM Dec 29, 2025
2ce0343
lint
Georges-GNM Dec 29, 2025
fbf8008
tweak mobile
Georges-GNM Dec 29, 2025
cc8f23b
tidy
Georges-GNM Dec 30, 2025
a076e49
more tidy
Georges-GNM Dec 30, 2025
a8c9f50
add ophan id and datalink name
Georges-GNM Dec 30, 2025
4570523
lint
Georges-GNM Dec 30, 2025
cd6c49c
add pillar colours
Georges-GNM Dec 30, 2025
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
873 changes: 873 additions & 0 deletions dotcom-rendering/fixtures/manual/storylines-section.ts

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dotcom-rendering/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"@guardian/identity-auth": "6.0.1",
"@guardian/identity-auth-frontend": "8.1.0",
"@guardian/libs": "26.1.0",
"@guardian/ophan-tracker-js": "2.6.3",
"@guardian/ophan-tracker-js": "2.8.0",
"@guardian/react-crossword": "11.1.0",
"@guardian/shimport": "1.0.2",
"@guardian/source": "11.3.0",
Expand Down
318 changes: 189 additions & 129 deletions dotcom-rendering/src/components/Card/Card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import { SnapCssSandbox } from '../SnapCssSandbox';
import { StarRating } from '../StarRating/StarRating';
import type { Alignment } from '../SupportingContent';
import { SupportingContent } from '../SupportingContent';
import { SupportingKeyStoriesContent } from '../SupportingKeyStoriesContent';
import { SvgMediaControlsPlay } from '../SvgMediaControlsPlay';
import { YoutubeBlockComponent } from '../YoutubeBlockComponent.importable';
import { AvatarContainer } from './components/AvatarContainer';
Expand Down Expand Up @@ -163,6 +164,7 @@ export type Props = {
/** Determines if the headline should be positioned within the content or outside the content */
headlinePosition?: 'inner' | 'outer';
enableHls?: boolean;
storylinesStyle?: boolean;
};

const starWrapper = (cardHasImage: boolean) => css`
Expand Down Expand Up @@ -421,6 +423,7 @@ export const Card = ({
headlinePosition = 'inner',
subtitleSize = 'small',
enableHls = false,
storylinesStyle = false,
}: Props) => {
const hasSublinks = supportingContent && supportingContent.length > 0;
const sublinkPosition = decideSublinkPosition(
Expand Down Expand Up @@ -452,7 +455,10 @@ export const Card = ({
const withinTwelveHours = isWithinTwelveHours(webPublicationDate);

const shouldShowAge =
isTagPage || !!onwardsSource || (showAge && withinTwelveHours);
storylinesStyle ||
isTagPage ||
!!onwardsSource ||
(showAge && withinTwelveHours);

if (!shouldShowAge) return undefined;

Expand Down Expand Up @@ -505,8 +511,30 @@ export const Card = ({
css={css`
margin-top: auto;
display: flex;
${storylinesStyle &&
`
flex-direction: column;
gap: ${space[1]}px;
align-items: flex-start;
`}
`}
>
{/* 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.
*/}
{storylinesStyle && (
<CardFooter
format={format}
age={decideAge()}
commentCount={<CommentCount />}
cardBranding={
isOnwardContent ? <LabsBranding /> : undefined
}
showLivePlayable={showLivePlayable}
/>
)}

{mainMedia?.type === 'YoutubeVideo' && isVideoArticle && (
<>
{mainMedia.duration === 0 ? (
Expand Down Expand Up @@ -733,25 +761,39 @@ export const Card = ({
if (!hasSublinks) return null;
if (sublinkPosition === 'none') return null;

const Sublinks = () => (
<SupportingContent
supportingContent={supportingContent}
containerPalette={containerPalette}
alignment={supportingContentAlignment}
isDynamo={isDynamo}
isMedia={isMediaCard(format)}
fillBackgroundOnMobile={
!!isFlexSplash ||
(isBetaContainer &&
!!image &&
(mediaPositionOnMobile === 'bottom' ||
isMediaCard(format)))
}
fillBackgroundOnDesktop={
isBetaContainer && isMediaCardOrNewsletter
}
/>
);
const Sublinks = () => {
return storylinesStyle ? (
<SupportingKeyStoriesContent
supportingContent={supportingContent}
containerPalette={containerPalette}
alignment="vertical"
isMedia={isMediaCard(format)}
fillBackgroundOnMobile={false}
fillBackgroundOnDesktop={
isBetaContainer && isMediaCardOrNewsletter
}
storylinesStyle={storylinesStyle}
/>
) : (
<SupportingContent
supportingContent={supportingContent}
containerPalette={containerPalette}
alignment={supportingContentAlignment}
isDynamo={isDynamo}
isMedia={isMediaCard(format)}
fillBackgroundOnMobile={
!!isFlexSplash ||
(isBetaContainer &&
!!image &&
(mediaPositionOnMobile === 'bottom' ||
isMediaCard(format)))
}
fillBackgroundOnDesktop={
isBetaContainer && isMediaCardOrNewsletter
}
/>
);
};

if (sublinkPosition === 'outer') {
return <Sublinks />;
Expand All @@ -770,14 +812,25 @@ export const Card = ({

return (
<Hide until={isFlexSplash ? 'desktop' : 'tablet'}>
<SupportingContent
supportingContent={supportingContent}
/* inner links are always vertically stacked */
alignment="vertical"
containerPalette={containerPalette}
isDynamo={isDynamo}
fillBackgroundOnMobile={isFlexSplash}
/>
{storylinesStyle ? (
<SupportingKeyStoriesContent
supportingContent={supportingContent}
/* inner links are always vertically stacked */
alignment="vertical"
containerPalette={containerPalette}
fillBackgroundOnMobile={isFlexSplash}
storylinesStyle={storylinesStyle}
/>
) : (
<SupportingContent
supportingContent={supportingContent}
/* inner links are always vertically stacked */
alignment="vertical"
containerPalette={containerPalette}
isDynamo={isDynamo}
fillBackgroundOnMobile={isFlexSplash}
/>
)}
</Hide>
);
};
Expand Down Expand Up @@ -1198,113 +1251,120 @@ export const Card = ({
isOnwardContent,
)}
>
{/* This div is needed to keep the headline and trail text justified at the start */}
<div
css={css`
position: relative;
display: flex;
flex-direction: column;
justify-content: flex-start;
flex-grow: 1;
`}
>
{headlinePosition === 'inner' && (
<HeadlineWrapper>
<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}
kickerImage={
showKickerImage &&
media?.type === 'podcast'
? media.podcastImage
: undefined
}
/>
{!isUndefined(starRating) ? (
<StarRatingComponent
rating={starRating}
cardHasImage={!!image}
/>
) : null}
</HeadlineWrapper>
)}

{!!trailText && shouldShowTrailText && (
<TrailText
trailText={trailText}
trailTextSize={trailTextSize}
padTop={headlinePosition === 'inner'}
hideUntil={hideTrailTextUntil()}
/>
)}

{!isOpinionCardWithAvatar && (
<>
{showPill ? (
<>
{!!branding && isOnwardContent && (
<LabsBranding />
)}
<MediaOrNewsletterPill />
</>
) : (
<CardFooter
{/* In the storylines section on tag pages, the flex splash is used to display key stories.
We don't display an article headline in the conventional sense, the key stories are instead displayed as "supporting content".
However, simply passing an empty string as the article headline still reserves space.
The storylines check enables us to avoid rendering that space at all.
*/}
{/* the div is needed to keep the headline and trail text justified at the start */}
{!(storylinesStyle && isFlexSplash) && (
<div
css={css`
position: relative;
display: flex;
flex-direction: column;
justify-content: flex-start;
flex-grow: 1;
`}
>
{headlinePosition === 'inner' && (
<HeadlineWrapper>
<CardHeadline
headlineText={headlineText}
format={format}
age={decideAge()}
commentCount={<CommentCount />}
cardBranding={
isOnwardContent ? (
<LabsBranding />
) : undefined
fontSizes={headlineSizes}
showQuotes={showQuotes}
kickerText={
format.design ===
ArticleDesign.LiveBlog &&
!kickerText
? 'Live'
: kickerText
}
showLivePlayable={showLivePlayable}
/>
)}
</>
)}
{showLivePlayable &&
liveUpdatesPosition === 'inner' && (
<Island
priority="feature"
defer={{ until: 'visible' }}
>
<LatestLinks
id={linkTo}
isDynamo={isDynamo}
direction={
isFlexibleContainer
? liveUpdatesAlignment
: supportingContentAlignment
showPulsingDot={
format.design ===
ArticleDesign.LiveBlog ||
showPulsingDot
}
containerPalette={containerPalette}
serverTime={serverTime}
displayHeader={isFlexibleContainer}
directionOnMobile={
isFlexibleContainer
? 'horizontal'
byline={byline}
showByline={showByline}
isExternalLink={isExternalLink}
kickerImage={
showKickerImage &&
media?.type === 'podcast'
? media.podcastImage
: undefined
}
></LatestLinks>
</Island>
/>
{!isUndefined(starRating) ? (
<StarRatingComponent
rating={starRating}
cardHasImage={!!image}
/>
) : null}
</HeadlineWrapper>
)}
</div>

{!!trailText && shouldShowTrailText && (
<TrailText
trailText={trailText}
trailTextSize={trailTextSize}
padTop={headlinePosition === 'inner'}
hideUntil={hideTrailTextUntil()}
/>
)}

{!isOpinionCardWithAvatar && (
<>
{showPill ? (
<>
{!!branding && isOnwardContent && (
<LabsBranding />
)}
<MediaOrNewsletterPill />
</>
) : (
<CardFooter
format={format}
age={decideAge()}
commentCount={<CommentCount />}
cardBranding={
isOnwardContent ? (
<LabsBranding />
) : undefined
}
showLivePlayable={showLivePlayable}
/>
)}
</>
)}
{showLivePlayable &&
liveUpdatesPosition === 'inner' && (
<Island
priority="feature"
defer={{ until: 'visible' }}
>
<LatestLinks
id={linkTo}
isDynamo={isDynamo}
direction={
isFlexibleContainer
? liveUpdatesAlignment
: supportingContentAlignment
}
containerPalette={containerPalette}
serverTime={serverTime}
displayHeader={isFlexibleContainer}
directionOnMobile={
isFlexibleContainer
? 'horizontal'
: undefined
}
></LatestLinks>
</Island>
)}
</div>
)}

{/* This div is needed to push this content to the bottom of the card */}
<div style={isOnwardContent ? { marginTop: 'auto' } : {}}>
Expand Down
Loading
Loading