From 367a6035fab49321a08ede8794fab708a23be93f Mon Sep 17 00:00:00 2001 From: Marjan Kalanaki Date: Mon, 22 Sep 2025 11:09:40 +0100 Subject: [PATCH 1/7] Add MoreGalleries component with a story Co-authored-by: Jamie B <53781962+JamieB-gu@users.noreply.github.com> --- dotcom-rendering/src/components/Card/Card.tsx | 14 +- .../components/Card/components/TrailText.tsx | 21 +- .../src/components/MoreGalleries.stories.tsx | 209 ++++++++++++++ .../src/components/MoreGalleries.tsx | 270 ++++++++++++++++++ dotcom-rendering/src/paletteDeclarations.ts | 12 + dotcom-rendering/src/types/onwards.ts | 2 + 6 files changed, 514 insertions(+), 14 deletions(-) create mode 100644 dotcom-rendering/src/components/MoreGalleries.stories.tsx create mode 100644 dotcom-rendering/src/components/MoreGalleries.tsx diff --git a/dotcom-rendering/src/components/Card/Card.tsx b/dotcom-rendering/src/components/Card/Card.tsx index c79a83a5281..554c503f20b 100644 --- a/dotcom-rendering/src/components/Card/Card.tsx +++ b/dotcom-rendering/src/components/Card/Card.tsx @@ -30,7 +30,7 @@ import type { DCRSupportingContent, } from '../../types/front'; import type { MainMedia } from '../../types/mainMedia'; -import type { OnwardsSource } from '../../types/onwards'; +import type { OnwardContainerType, OnwardsSource } from '../../types/onwards'; import { Avatar } from '../Avatar'; import { CardCommentCount } from '../CardCommentCount.importable'; import { CardHeadline, type ResponsiveFontSize } from '../CardHeadline'; @@ -120,7 +120,7 @@ export type Props = { supportingContentPosition?: Position; snapData?: DCRSnapType; containerPalette?: DCRContainerPalette; - containerType?: DCRContainerType; + containerType?: DCRContainerType | OnwardContainerType; showAge?: boolean; discussionApiUrl: string; discussionId?: string; @@ -576,6 +576,8 @@ export const Card = ({ containerType === 'flexible/special' || containerType === 'flexible/general'; + const isOnwardContainer = containerType === 'more-galleries'; + const isSmallCard = containerType === 'scrollable/small'; const mediaFixedSizeOptions = (): MediaFixedSizeOptions => { @@ -593,6 +595,8 @@ export const Card = ({ const hideTrailTextUntil = () => { if (isFlexibleContainer) { return undefined; + } else if (isOnwardContainer && isFlexSplash) { + return 'never'; } else if ( mediaSize === 'large' && mediaPositionOnDesktop === 'right' && @@ -604,6 +608,10 @@ export const Card = ({ } }; + const shouldShowTrailText = isOnwardContainer + ? media?.type !== 'podcast' && isFlexSplash + : media?.type !== 'podcast'; + /** * Determines the gap of between card components based on card properties * Order matters here as the logic is based on the card properties @@ -1093,7 +1101,7 @@ export const Card = ({ )} {!!trailText && - media?.type !== 'podcast' && + shouldShowTrailText && !isInHideTrailsAbTest && ( { + return css` + display: flex; + flex-direction: column; + ${hideUntil !== 'never' ? `${until.tablet} { display: none; }` : ''} + `; +}; const bottomPadding = css` padding-bottom: ${space[2]}px; @@ -44,7 +43,7 @@ type Props = { /** Optionally overrides the trail text colour */ trailTextColour?: string; /** Controls visibility of trail text on various breakpoints */ - hideUntil?: 'tablet' | 'desktop'; + hideUntil?: 'tablet' | 'desktop' | 'never'; /** Defaults to `true`. Adds padding to the bottom of the trail text */ padBottom?: boolean; /** Adds padding to the top of the trail text */ @@ -62,7 +61,7 @@ export const TrailText = ({ const trailText = (
); - return hideUntil ? ( + return hideUntil && hideUntil !== 'never' ? ( {trailText} ) : ( <>{trailText} diff --git a/dotcom-rendering/src/components/MoreGalleries.stories.tsx b/dotcom-rendering/src/components/MoreGalleries.stories.tsx new file mode 100644 index 00000000000..4ca6afa0232 --- /dev/null +++ b/dotcom-rendering/src/components/MoreGalleries.stories.tsx @@ -0,0 +1,209 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { ArticleDesign, ArticleDisplay, Pillar } from '../lib/articleFormat'; +import { getDataLinkNameCard } from '../lib/getDataLinkName'; +import { MoreGalleries as MoreGalleriesComponent } from './MoreGalleries'; + +const meta = { + title: 'Components/MoreGalleries', + component: MoreGalleriesComponent, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const MoreGalleries = { + args: { + absoluteServerTimes: false, + discussionApiUrl: + 'https://discussion.code.dev-theguardian.com/discussion-api', + heading: 'More galleries', + url: 'http://localhost:9000/more-galleries', + onwardsSource: 'more-galleries', + trails: [ + { + url: 'http://localhost:9000/environment/gallery/2025/aug/22/week-in-wildlife-a-clumsy-fox-swinging-orangutang-and-rescued-jaguarundi-cub', + linkText: + 'Week in wildlife: a clumsy fox, a swinging orangutan and a rescued jaguarundi cub', + showByline: false, + byline: 'Pejman Faratin', + image: { + src: 'https://media.guim.co.uk/a81e974ffee6c8c88fa280c2d02eaf5dc2af863e/151_292_1020_816/master/1020.jpg', + altText: '', + }, + format: { + theme: Pillar.News, + design: ArticleDesign.Gallery, + display: ArticleDisplay.Standard, + }, + webPublicationDate: '2025-08-22T06:00:25.000Z', + headline: + 'Week in wildlife: a clumsy fox, a swinging orangutan and a rescued jaguarundi cub', + shortUrl: 'https://www.theguardian.com/p/x32n89', + discussion: { + isCommentable: false, + isClosedForComments: true, + discussionId: '/p/x32n89', + }, + dataLinkName: getDataLinkNameCard( + { + theme: Pillar.News, + design: ArticleDesign.Gallery, + display: ArticleDisplay.Standard, + }, + '0', + 0, + ), + trailText: + 'Guinness World Records is looking back at the extraordinary feats achieved since its inception - as well as unveiling 70 whacky and unclaimed records ', + kickerText: 'Politics', // Get data for this + mainMedia: { type: 'Gallery', count: '6' }, // TODO: get data for this + }, + { + url: 'http://localhost:9000/money/gallery/2025/aug/22/characterful-cottages-for-sale-in-england-in-pictures', + linkText: + 'Characterful cottages for sale in England – in pictures', + showByline: false, + byline: 'Anna White', + image: { + src: 'https://media.guim.co.uk/58cd9356e6d68e8efa6028162bb959f9798307d5/515_0_5000_4000/master/5000.jpg', + altText: '', + }, + format: { + design: ArticleDesign.Gallery, + theme: Pillar.Lifestyle, + display: ArticleDisplay.Standard, + }, + webPublicationDate: '2025-08-22T06:00:24.000Z', + headline: + 'Characterful cottages for sale in England – in pictures', + shortUrl: 'https://www.theguardian.com/p/x32gqj', + discussion: { + isCommentable: false, + isClosedForComments: true, + discussionId: '/p/x32gqj', + }, + dataLinkName: getDataLinkNameCard( + { + design: ArticleDesign.Gallery, + theme: Pillar.Lifestyle, + display: ArticleDisplay.Standard, + }, + '0', + 1, + ), + trailText: + 'Picked from a record 60,636 entries, the first images from the Natural History Museum’s wildlife photographer of the year competition have been released. The photographs, which range from a lion facing down a cobra to magnified mould spores, show the diversity, beauty and complexity of the natural world and humanity’s relationship with it', + mainMedia: { type: 'Gallery', count: '6' }, // TODO: get data for this + }, + { + url: 'http://localhost:9000/news/gallery/2025/aug/22/sunsets-aid-parachutes-and-giant-pandas-photos-of-the-day-friday', + linkText: + 'Sunsets, aid parachutes and giant pandas: photos of the day – Friday ', + showByline: false, + byline: 'Eithne Staunton', + image: { + src: 'https://media.guim.co.uk/4ce0b080206fe9b65b976c1acf219d81072cc814/0_0_2113_1690/master/2113.png', + altText: '', + }, + format: { + design: ArticleDesign.Gallery, + theme: Pillar.News, + display: ArticleDisplay.Standard, + }, + webPublicationDate: '2025-08-22T12:49:42.000Z', + headline: + 'Sunsets, aid parachutes and giant pandas: photos of the day – Friday ', + shortUrl: 'https://www.theguardian.com/p/x3359z', + discussion: { + isCommentable: false, + isClosedForComments: true, + discussionId: '/p/x3359z', + }, + dataLinkName: getDataLinkNameCard( + { + design: ArticleDesign.Gallery, + theme: Pillar.News, + display: ArticleDisplay.Standard, + }, + '0', + 2, + ), + trailText: + 'From the mock-Tudor fad of the 1920s to drivers refuelling on a roundabout, each era produces its own distinctive petrol stations – as photographer Philip Butler discovered', + mainMedia: { type: 'Gallery', count: '6' }, // TODO: get data for this + }, + { + url: 'http://localhost:9000/fashion/gallery/2025/aug/22/what-to-wear-to-notting-hill-carnival', + linkText: 'On parade: what to wear to Notting Hill carnival', + showByline: false, + byline: 'Melanie Wilkinson', + image: { + src: 'https://media.guim.co.uk/49a9656cd10c4f64f8bdd54380afb915c7a3648b/207_0_1500_1200/master/1500.jpg', + altText: '', + }, + format: { + design: ArticleDesign.Gallery, + theme: Pillar.Lifestyle, + display: ArticleDisplay.Standard, + }, + webPublicationDate: '2025-08-22T05:00:23.000Z', + headline: 'On parade: what to wear to Notting Hill carnival', + shortUrl: 'https://www.theguardian.com/p/x32mte', + discussion: { + isCommentable: false, + isClosedForComments: true, + discussionId: '/p/x32mte', + }, + dataLinkName: getDataLinkNameCard( + { + design: ArticleDesign.Gallery, + theme: Pillar.Lifestyle, + display: ArticleDisplay.Standard, + }, + '0', + 1, + ), + trailText: + 'The Guardian’s picture editors select photographs from around the world', + mainMedia: { type: 'Gallery', count: '6' }, // TODO: get data for thismainMedia: { type: 'Gallery', count: '6' }, // TODO: get data for this + }, + { + url: 'http://localhost:9000/artanddesign/gallery/2025/aug/21/psychedelic-rock-glass-mountain-michael-lundgren', + linkText: + 'Psychedelic rock! Formations that mess with your mind – in pictures ', + showByline: false, + image: { + src: 'https://media.guim.co.uk/2810af61b2d2d2d5f71ec01e56e6555e0a6d4635/55_0_2813_2250/master/2813.jpg', + altText: '', + }, + format: { + design: ArticleDesign.Gallery, + theme: Pillar.Culture, + display: ArticleDisplay.Standard, + }, + webPublicationDate: '2025-08-21T06:01:01.000Z', + headline: + 'Psychedelic rock! Formations that mess with your mind – in pictures ', + shortUrl: 'https://www.theguardian.com/p/x2p663', + discussion: { + isCommentable: false, + isClosedForComments: true, + discussionId: '/p/x2p663', + }, + dataLinkName: getDataLinkNameCard( + { + design: ArticleDesign.Gallery, + theme: Pillar.Culture, + display: ArticleDisplay.Standard, + }, + '0', + 1, + ), + trailText: + 'Politicians and their partners put on their best show at this year’s Midwinter Ball, an annual dinner hosted by the Federal Parliamentary Press Gallery in Canberra', + mainMedia: { type: 'Gallery', count: '6' }, // TODO: get data for this + }, + ], + }, +} satisfies Story; diff --git a/dotcom-rendering/src/components/MoreGalleries.tsx b/dotcom-rendering/src/components/MoreGalleries.tsx new file mode 100644 index 00000000000..47d5f0eae9b --- /dev/null +++ b/dotcom-rendering/src/components/MoreGalleries.tsx @@ -0,0 +1,270 @@ +import { css } from '@emotion/react'; +import { + from, + headlineBold24, + headlineBold28, + space, + until, +} from '@guardian/source/foundations'; +import { StraightLines } from '@guardian/source-development-kitchen/react-components'; +import { formatAttrString } from '../lib/formatAttrString'; +import { palette as themePalette } from '../palette'; +import { type OnwardsSource } from '../types/onwards'; +import { type TrailType } from '../types/trails'; +import { Card } from './Card/Card'; +import type { Props as CardProps } from './Card/Card'; +import { Hide } from './Hide'; +import { LeftColumn } from './LeftColumn'; +import { Section } from './Section'; + +type Props = { + absoluteServerTimes: boolean; + trails: TrailType[]; + discussionApiUrl: string; + heading: string; + onwardsSource: OnwardsSource; + url?: string; +}; + +const wrapperStyle = css` + display: flex; + justify-content: space-between; + overflow: hidden; + ${from.desktop} { + padding-right: 40px; + } +`; + +const containerStyles = css` + display: flex; + flex-direction: column; + position: relative; + overflow: hidden; /* Needed for scrolling to work */ + + margin-top: ${space[2]}px; + padding-bottom: ${space[6]}px; + + margin-left: 0px; + margin-right: 0px; + + border-bottom: 1px solid ${themePalette('--onward-content-border')}; + + ${from.leftCol} { + margin-left: 10px; + margin-right: 100px; + } +`; + +const standardCardStyles = css` + flex: 1; + + position: relative; + display: flex; + padding: ${space[2]}px; + background-color: ${themePalette('--onward-card-background')}; + + :not(:first-child)::before { + content: ''; + position: absolute; + top: 0; + bottom: 0; + left: -10px; /* shift into the gap */ + width: 1px; + background: ${themePalette('--onward-content-border')}; + } +`; + +const standardCardsListStyles = css` + width: 100%; + display: flex; + flex-direction: row; + gap: 20px; + + ${from.tablet} { + padding-top: ${space[2]}px; + } + + ${until.tablet} { + flex-direction: column; + width: 100%; + } +`; + +const headerStyles = css` + color: ${themePalette('--carousel-text')}; + ${headlineBold24}; + padding-bottom: ${space[3]}px; + padding-top: ${space[1]}px; + margin-left: 0; + + ${from.tablet} { + ${headlineBold28}; + } +`; + +const headerStylesWithUrl = css` + :hover { + text-decoration: underline; + } +`; + +const titleStyle = css` + color: ${themePalette('--onward-text')}; + display: inline-block; + &::first-letter { + text-transform: capitalize; + } +`; + +const getDefaultCardProps = ( + trail: TrailType, + absoluteServerTimes: boolean, + discussionApiUrl: string, +) => { + const defaultProps: CardProps = { + linkTo: trail.url, + format: trail.format, + headlineText: trail.headline, + byline: trail.byline, + showByline: trail.showByline, + showQuotedHeadline: trail.showQuotedHeadline, + webPublicationDate: trail.webPublicationDate, + kickerText: trail.kickerText, + showPulsingDot: false, + showClock: false, + image: trail.image, + isCrossword: trail.isCrossword, + starRating: trail.starRating, + dataLinkName: trail.dataLinkName, + snapData: trail.snapData, + discussionApiUrl, + discussionId: trail.discussionId, + avatarUrl: trail.avatarUrl, + mainMedia: trail.mainMedia, + isExternalLink: false, + branding: trail.branding, + absoluteServerTimes, + imageLoading: 'lazy', + trailText: trail.trailText, + showAge: false, // TODO + containerType: 'more-galleries', + showTopBarDesktop: false, + showTopBarMobile: false, + aspectRatio: '5:4', + }; + return defaultProps; +}; + +export const MoreGalleries = (props: Props) => { + const [firstTrail, ...standardCards] = props.trails; + if (!firstTrail) return null; + + const defaultProps = getDefaultCardProps( + firstTrail, + props.absoluteServerTimes, + props.discussionApiUrl, + ); + + return ( +
+
+ + + </LeftColumn> + + <div + css={containerStyles} + data-component={props.onwardsSource} + data-link={formatAttrString(props.heading)} + > + <Hide when="above" breakpoint="leftCol"> + <Title title={props.heading} url={props.url} /> + </Hide> + + <MoreGalleriesSplashCard defaultProps={defaultProps} /> + <Hide when="below" breakpoint="tablet"> + <StraightLines + count={1} + color={themePalette('--onward-content-border')} + /> + </Hide> + + <ul css={standardCardsListStyles}> + {standardCards.map((trail) => ( + <li key={trail.url} css={standardCardStyles}> + {Card({ + ...getDefaultCardProps( + trail, + props.absoluteServerTimes, + props.discussionApiUrl, + ), + mediaSize: 'medium', + })} + </li> + ))} + </ul> + </div> + </div> + </Section> + ); +}; + +const MoreGalleriesSplashCard = ({ + defaultProps, +}: { + defaultProps: CardProps; +}) => { + const cardProps: Partial<CardProps> = { + headlineSizes: { + desktop: 'medium', + tablet: 'medium', + mobile: 'medium', + }, + mediaPositionOnDesktop: 'right', + mediaPositionOnMobile: 'top', + mediaSize: 'medium', + isFlexSplash: true, + }; + return ( + <div + css={css` + margin-bottom: ${space[6]}px; + background-color: ${themePalette('--onward-card-background')}; + padding: ${space[2]}px; + `} + > + {Card({ ...defaultProps, ...cardProps })} + </div> + ); +}; + +const Title = ({ title, url }: { title: string; url?: string }) => + url ? ( + <a + css={css` + text-decoration: none; + `} + href={url} + data-link-name="section heading" // TODO + > + <h2 css={headerStyles}> + <span css={[headerStylesWithUrl, titleStyle]}>{title}</span> + </h2> + </a> + ) : ( + <h2 css={headerStyles}> + <span css={titleStyle}>{title}</span> + </h2> + ); diff --git a/dotcom-rendering/src/paletteDeclarations.ts b/dotcom-rendering/src/paletteDeclarations.ts index d49cdf6b496..c30af267341 100644 --- a/dotcom-rendering/src/paletteDeclarations.ts +++ b/dotcom-rendering/src/paletteDeclarations.ts @@ -7377,10 +7377,22 @@ const paletteColours = { light: numberedListTitleLight, dark: numberedListTitleDark, }, + '--onward-background': { + light: () => sourcePalette.neutral[100], + dark: () => sourcePalette.neutral[0], + }, + '--onward-card-background': { + light: () => sourcePalette.neutral[97], + dark: () => sourcePalette.neutral[20], + }, '--onward-content-border': { light: onwardContentBorderLight, dark: () => sourcePalette.neutral[20], }, + '--onward-text': { + light: () => sourcePalette.neutral[7], + dark: () => sourcePalette.neutral[86], + }, '--pagination-text': { light: paginationTextLight, dark: paginationTextDark, diff --git a/dotcom-rendering/src/types/onwards.ts b/dotcom-rendering/src/types/onwards.ts index 948ca173272..5d0ee426cca 100644 --- a/dotcom-rendering/src/types/onwards.ts +++ b/dotcom-rendering/src/types/onwards.ts @@ -24,3 +24,5 @@ export type OnwardsSource = | 'curated-content' | 'newsletters-page' | 'unknown-source'; // We should never see this in the analytics data! + +export type OnwardContainerType = 'more-galleries'; From 7d92d1bfa421fa1d37f7e9cc9ffaf6fb941f80d1 Mon Sep 17 00:00:00 2001 From: Marjan Kalanaki <marjan.kalanaki@guardian.co.uk> Date: Fri, 26 Sep 2025 10:22:39 +0100 Subject: [PATCH 2/7] add new prop to TrailText to ensure it is not hidden on smaller breakpoints --- dotcom-rendering/src/components/Card/Card.tsx | 9 +++++---- .../src/components/Card/components/TrailText.tsx | 13 ++++++++----- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/dotcom-rendering/src/components/Card/Card.tsx b/dotcom-rendering/src/components/Card/Card.tsx index 8117840ee77..af9a6419f42 100644 --- a/dotcom-rendering/src/components/Card/Card.tsx +++ b/dotcom-rendering/src/components/Card/Card.tsx @@ -144,7 +144,7 @@ export type Props = { * For example, the first splash card in the second collection would be: "collection-1-splash-0" */ uniqueId?: string; - /** The Splash card in a flexible container gets a different visual treatment to other cards */ + /** The Splash card in a flexible or onward container gets a different visual treatment to other cards */ isFlexSplash?: boolean; showTopBarDesktop?: boolean; showTopBarMobile?: boolean; @@ -593,10 +593,8 @@ export const Card = ({ }; const hideTrailTextUntil = () => { - if (isFlexibleContainer) { + if (isFlexibleContainer || (isOnwardContainer && !!isFlexSplash)) { return undefined; - } else if (isOnwardContainer && isFlexSplash) { - return 'never'; } else if ( mediaSize === 'large' && mediaPositionOnDesktop === 'right' && @@ -1107,6 +1105,9 @@ export const Card = ({ trailText={trailText} trailTextSize={trailTextSize} padTop={headlinePosition === 'inner'} + hideOnSmallBreakpoints={ + !(isOnwardContainer && isFlexSplash) + } hideUntil={hideTrailTextUntil()} /> )} diff --git a/dotcom-rendering/src/components/Card/components/TrailText.tsx b/dotcom-rendering/src/components/Card/components/TrailText.tsx index 6c99a883acd..4fd64e386b9 100644 --- a/dotcom-rendering/src/components/Card/components/TrailText.tsx +++ b/dotcom-rendering/src/components/Card/components/TrailText.tsx @@ -11,11 +11,11 @@ import { palette } from '../../../palette'; export type TrailTextSize = 'regular' | 'large'; -const trailTextStyles = (hideUntil?: 'tablet' | 'desktop' | 'never') => { +const trailTextStyles = (hide?: boolean) => { return css` display: flex; flex-direction: column; - ${hideUntil !== 'never' ? `${until.tablet} { display: none; }` : ''} + ${hide ? `${until.tablet} { display: none; }` : ''} `; }; @@ -43,11 +43,13 @@ type Props = { /** Optionally overrides the trail text colour */ trailTextColour?: string; /** Controls visibility of trail text on various breakpoints */ - hideUntil?: 'tablet' | 'desktop' | 'never'; + hideUntil?: 'tablet' | 'desktop'; /** Defaults to `true`. Adds padding to the bottom of the trail text */ padBottom?: boolean; /** Adds padding to the top of the trail text */ padTop?: boolean; + /** hide trail text for specific breakpoints */ + hideOnSmallBreakpoints?: boolean; }; export const TrailText = ({ @@ -57,11 +59,12 @@ export const TrailText = ({ hideUntil, padBottom = true, padTop = false, + hideOnSmallBreakpoints = true, }: Props) => { const trailText = ( <div css={[ - trailTextStyles(hideUntil), + trailTextStyles(hideOnSmallBreakpoints), css` color: ${trailTextColour}; `, @@ -78,7 +81,7 @@ export const TrailText = ({ </div> ); - return hideUntil && hideUntil !== 'never' ? ( + return hideUntil ? ( <Hide until={hideUntil}>{trailText}</Hide> ) : ( <>{trailText}</> From 044a7576c2e89c64ccff484f0e35253817a8c3cb Mon Sep 17 00:00:00 2001 From: Marjan Kalanaki <marjan.kalanaki@guardian.co.uk> Date: Mon, 29 Sep 2025 08:44:47 +0100 Subject: [PATCH 3/7] replace px with space --- dotcom-rendering/src/components/MoreGalleries.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotcom-rendering/src/components/MoreGalleries.tsx b/dotcom-rendering/src/components/MoreGalleries.tsx index 47d5f0eae9b..99cf5040ecc 100644 --- a/dotcom-rendering/src/components/MoreGalleries.tsx +++ b/dotcom-rendering/src/components/MoreGalleries.tsx @@ -31,7 +31,7 @@ const wrapperStyle = css` justify-content: space-between; overflow: hidden; ${from.desktop} { - padding-right: 40px; + padding-right: ${space[10]}px; } `; From 356ebb60b80182929b8c5677792a64f311aa740c Mon Sep 17 00:00:00 2001 From: Marjan Kalanaki <marjan.kalanaki@guardian.co.uk> Date: Tue, 30 Sep 2025 08:27:52 +0100 Subject: [PATCH 4/7] some cleaning up --- .../components/Card/components/TrailText.tsx | 2 +- .../src/components/MoreGalleries.tsx | 29 +++++++++---------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/dotcom-rendering/src/components/Card/components/TrailText.tsx b/dotcom-rendering/src/components/Card/components/TrailText.tsx index 4fd64e386b9..6a2af963dd1 100644 --- a/dotcom-rendering/src/components/Card/components/TrailText.tsx +++ b/dotcom-rendering/src/components/Card/components/TrailText.tsx @@ -48,7 +48,7 @@ type Props = { padBottom?: boolean; /** Adds padding to the top of the trail text */ padTop?: boolean; - /** hide trail text for specific breakpoints */ + /** hide trail text on small breakpoints */ hideOnSmallBreakpoints?: boolean; }; diff --git a/dotcom-rendering/src/components/MoreGalleries.tsx b/dotcom-rendering/src/components/MoreGalleries.tsx index 99cf5040ecc..5bbe4db44e6 100644 --- a/dotcom-rendering/src/components/MoreGalleries.tsx +++ b/dotcom-rendering/src/components/MoreGalleries.tsx @@ -8,7 +8,7 @@ import { } from '@guardian/source/foundations'; import { StraightLines } from '@guardian/source-development-kitchen/react-components'; import { formatAttrString } from '../lib/formatAttrString'; -import { palette as themePalette } from '../palette'; +import { palette } from '../palette'; import { type OnwardsSource } from '../types/onwards'; import { type TrailType } from '../types/trails'; import { Card } from './Card/Card'; @@ -39,7 +39,6 @@ const containerStyles = css` display: flex; flex-direction: column; position: relative; - overflow: hidden; /* Needed for scrolling to work */ margin-top: ${space[2]}px; padding-bottom: ${space[6]}px; @@ -47,7 +46,7 @@ const containerStyles = css` margin-left: 0px; margin-right: 0px; - border-bottom: 1px solid ${themePalette('--onward-content-border')}; + border-bottom: 1px solid ${palette('--onward-content-border')}; ${from.leftCol} { margin-left: 10px; @@ -61,7 +60,7 @@ const standardCardStyles = css` position: relative; display: flex; padding: ${space[2]}px; - background-color: ${themePalette('--onward-card-background')}; + background-color: ${palette('--onward-card-background')}; :not(:first-child)::before { content: ''; @@ -70,7 +69,7 @@ const standardCardStyles = css` bottom: 0; left: -10px; /* shift into the gap */ width: 1px; - background: ${themePalette('--onward-content-border')}; + background: ${palette('--onward-content-border')}; } `; @@ -91,7 +90,7 @@ const standardCardsListStyles = css` `; const headerStyles = css` - color: ${themePalette('--carousel-text')}; + color: ${palette('--carousel-text')}; ${headlineBold24}; padding-bottom: ${space[3]}px; padding-top: ${space[1]}px; @@ -109,7 +108,7 @@ const headerStylesWithUrl = css` `; const titleStyle = css` - color: ${themePalette('--onward-text')}; + color: ${palette('--onward-text')}; display: inline-block; &::first-letter { text-transform: capitalize; @@ -146,7 +145,7 @@ const getDefaultCardProps = ( absoluteServerTimes, imageLoading: 'lazy', trailText: trail.trailText, - showAge: false, // TODO + showAge: false, containerType: 'more-galleries', showTopBarDesktop: false, showTopBarMobile: false, @@ -168,8 +167,8 @@ export const MoreGalleries = (props: Props) => { return ( <Section fullWidth={true} - borderColour={themePalette('--onward-content-border')} - backgroundColour={themePalette('--onward-background')} + borderColour={palette('--onward-content-border')} + backgroundColour={palette('--onward-background')} showTopBorder={false} > <div @@ -178,8 +177,8 @@ export const MoreGalleries = (props: Props) => { > <LeftColumn size={'compact'} - borderColour={themePalette('--onward-content-border')} - hasPageSkin={false} // TODO + borderColour={palette('--onward-content-border')} + hasPageSkin={false} > <Title title={props.heading} url={props.url} /> </LeftColumn> @@ -197,7 +196,7 @@ export const MoreGalleries = (props: Props) => { <Hide when="below" breakpoint="tablet"> <StraightLines count={1} - color={themePalette('--onward-content-border')} + color={palette('--onward-content-border')} /> </Hide> @@ -241,7 +240,7 @@ const MoreGalleriesSplashCard = ({ <div css={css` margin-bottom: ${space[6]}px; - background-color: ${themePalette('--onward-card-background')}; + background-color: ${palette('--onward-card-background')}; padding: ${space[2]}px; `} > @@ -257,7 +256,7 @@ const Title = ({ title, url }: { title: string; url?: string }) => text-decoration: none; `} href={url} - data-link-name="section heading" // TODO + data-link-name="section heading" > <h2 css={headerStyles}> <span css={[headerStylesWithUrl, titleStyle]}>{title}</span> From ac6dc74d70ab14a4e64a513bed6751c722c09c27 Mon Sep 17 00:00:00 2001 From: Marjan Kalanaki <marjan.kalanaki@guardian.co.uk> Date: Tue, 30 Sep 2025 16:43:12 +0100 Subject: [PATCH 5/7] Refactor TrailText comp to remove un-necessary display none until tablet Instead always pass hideUntil when hiding is required and pass undefined when hiding is not required (trail text needs to be visible for all breakpoints) Co-authored-by: Ravi <7014230+arelra@users.noreply.github.com> --- dotcom-rendering/src/components/Card/Card.tsx | 15 ++++++++------- .../components/Card/components/TrailText.tsx | 17 +++++------------ dotcom-rendering/src/components/FeatureCard.tsx | 1 + .../YoutubeAtomFeatureCardOverlay.tsx | 1 + 4 files changed, 15 insertions(+), 19 deletions(-) diff --git a/dotcom-rendering/src/components/Card/Card.tsx b/dotcom-rendering/src/components/Card/Card.tsx index af9a6419f42..da6d893899d 100644 --- a/dotcom-rendering/src/components/Card/Card.tsx +++ b/dotcom-rendering/src/components/Card/Card.tsx @@ -593,17 +593,21 @@ export const Card = ({ }; const hideTrailTextUntil = () => { - if (isFlexibleContainer || (isOnwardContainer && !!isFlexSplash)) { + if (isFlexibleContainer) { + return 'tablet'; + } + if (isOnwardContainer && !!isFlexSplash) { return undefined; - } else if ( + } + if ( mediaSize === 'large' && mediaPositionOnDesktop === 'right' && media?.type !== 'avatar' ) { return 'desktop'; - } else { - return 'tablet'; } + + return 'tablet'; }; const shouldShowTrailText = isOnwardContainer @@ -1105,9 +1109,6 @@ export const Card = ({ trailText={trailText} trailTextSize={trailTextSize} padTop={headlinePosition === 'inner'} - hideOnSmallBreakpoints={ - !(isOnwardContainer && isFlexSplash) - } hideUntil={hideTrailTextUntil()} /> )} diff --git a/dotcom-rendering/src/components/Card/components/TrailText.tsx b/dotcom-rendering/src/components/Card/components/TrailText.tsx index 6a2af963dd1..c65a8af7f59 100644 --- a/dotcom-rendering/src/components/Card/components/TrailText.tsx +++ b/dotcom-rendering/src/components/Card/components/TrailText.tsx @@ -4,20 +4,16 @@ import { space, textSans14, textSans17, - until, } from '@guardian/source/foundations'; import { Hide } from '@guardian/source/react-components'; import { palette } from '../../../palette'; export type TrailTextSize = 'regular' | 'large'; -const trailTextStyles = (hide?: boolean) => { - return css` - display: flex; - flex-direction: column; - ${hide ? `${until.tablet} { display: none; }` : ''} - `; -}; +const trailTextStyles = css` + display: flex; + flex-direction: column; +`; const bottomPadding = css` padding-bottom: ${space[2]}px; @@ -48,8 +44,6 @@ type Props = { padBottom?: boolean; /** Adds padding to the top of the trail text */ padTop?: boolean; - /** hide trail text on small breakpoints */ - hideOnSmallBreakpoints?: boolean; }; export const TrailText = ({ @@ -59,12 +53,11 @@ export const TrailText = ({ hideUntil, padBottom = true, padTop = false, - hideOnSmallBreakpoints = true, }: Props) => { const trailText = ( <div css={[ - trailTextStyles(hideOnSmallBreakpoints), + trailTextStyles, css` color: ${trailTextColour}; `, diff --git a/dotcom-rendering/src/components/FeatureCard.tsx b/dotcom-rendering/src/components/FeatureCard.tsx index 7b3cc22a799..f2e59ba9a64 100644 --- a/dotcom-rendering/src/components/FeatureCard.tsx +++ b/dotcom-rendering/src/components/FeatureCard.tsx @@ -638,6 +638,7 @@ export const FeatureCard = ({ )} trailTextSize="regular" padBottom={false} + hideUntil="tablet" /> </div> )} diff --git a/dotcom-rendering/src/components/YoutubeAtom/YoutubeAtomFeatureCardOverlay.tsx b/dotcom-rendering/src/components/YoutubeAtom/YoutubeAtomFeatureCardOverlay.tsx index f6f8a984d1a..a2063288d82 100644 --- a/dotcom-rendering/src/components/YoutubeAtom/YoutubeAtomFeatureCardOverlay.tsx +++ b/dotcom-rendering/src/components/YoutubeAtom/YoutubeAtomFeatureCardOverlay.tsx @@ -254,6 +254,7 @@ export const YoutubeAtomFeatureCardOverlay = ({ )} trailTextSize="regular" padBottom={false} + hideUntil="tablet" /> </div> )} From ce987dbf0e2936a1d905893f1c76b2233c8f18b9 Mon Sep 17 00:00:00 2001 From: Marjan Kalanaki <marjan.kalanaki@guardian.co.uk> Date: Fri, 3 Oct 2025 10:25:19 +0100 Subject: [PATCH 6/7] Address review comments --- .../src/components/MoreGalleries.stories.tsx | 29 +++++++-------- .../src/components/MoreGalleries.tsx | 35 ++++++++----------- dotcom-rendering/src/paletteDeclarations.ts | 2 +- 3 files changed, 29 insertions(+), 37 deletions(-) diff --git a/dotcom-rendering/src/components/MoreGalleries.stories.tsx b/dotcom-rendering/src/components/MoreGalleries.stories.tsx index 4ca6afa0232..1eff4d2774f 100644 --- a/dotcom-rendering/src/components/MoreGalleries.stories.tsx +++ b/dotcom-rendering/src/components/MoreGalleries.stories.tsx @@ -15,14 +15,11 @@ type Story = StoryObj<typeof meta>; export const MoreGalleries = { args: { absoluteServerTimes: false, - discussionApiUrl: - 'https://discussion.code.dev-theguardian.com/discussion-api', - heading: 'More galleries', - url: 'http://localhost:9000/more-galleries', - onwardsSource: 'more-galleries', + discussionApiUrl: 'https://discussion.theguardian.com/discussion-api', + headingLink: 'https://www.theguardian.com/inpictures/all', trails: [ { - url: 'http://localhost:9000/environment/gallery/2025/aug/22/week-in-wildlife-a-clumsy-fox-swinging-orangutang-and-rescued-jaguarundi-cub', + url: 'https://www.theguardian.com/environment/gallery/2025/aug/22/week-in-wildlife-a-clumsy-fox-swinging-orangutang-and-rescued-jaguarundi-cub', linkText: 'Week in wildlife: a clumsy fox, a swinging orangutan and a rescued jaguarundi cub', showByline: false, @@ -56,11 +53,11 @@ export const MoreGalleries = { ), trailText: 'Guinness World Records is looking back at the extraordinary feats achieved since its inception - as well as unveiling 70 whacky and unclaimed records ', - kickerText: 'Politics', // Get data for this - mainMedia: { type: 'Gallery', count: '6' }, // TODO: get data for this + kickerText: 'Politics', + mainMedia: { type: 'Gallery', count: '6' }, }, { - url: 'http://localhost:9000/money/gallery/2025/aug/22/characterful-cottages-for-sale-in-england-in-pictures', + url: 'https://www.theguardian.com/money/gallery/2025/aug/22/characterful-cottages-for-sale-in-england-in-pictures', linkText: 'Characterful cottages for sale in England – in pictures', showByline: false, @@ -94,10 +91,10 @@ export const MoreGalleries = { ), trailText: 'Picked from a record 60,636 entries, the first images from the Natural History Museum’s wildlife photographer of the year competition have been released. The photographs, which range from a lion facing down a cobra to magnified mould spores, show the diversity, beauty and complexity of the natural world and humanity’s relationship with it', - mainMedia: { type: 'Gallery', count: '6' }, // TODO: get data for this + mainMedia: { type: 'Gallery', count: '6' }, }, { - url: 'http://localhost:9000/news/gallery/2025/aug/22/sunsets-aid-parachutes-and-giant-pandas-photos-of-the-day-friday', + url: 'https://www.theguardian.com/news/gallery/2025/aug/22/sunsets-aid-parachutes-and-giant-pandas-photos-of-the-day-friday', linkText: 'Sunsets, aid parachutes and giant pandas: photos of the day – Friday ', showByline: false, @@ -131,10 +128,10 @@ export const MoreGalleries = { ), trailText: 'From the mock-Tudor fad of the 1920s to drivers refuelling on a roundabout, each era produces its own distinctive petrol stations – as photographer Philip Butler discovered', - mainMedia: { type: 'Gallery', count: '6' }, // TODO: get data for this + mainMedia: { type: 'Gallery', count: '6' }, }, { - url: 'http://localhost:9000/fashion/gallery/2025/aug/22/what-to-wear-to-notting-hill-carnival', + url: 'https://www.theguardian.com/fashion/gallery/2025/aug/22/what-to-wear-to-notting-hill-carnival', linkText: 'On parade: what to wear to Notting Hill carnival', showByline: false, byline: 'Melanie Wilkinson', @@ -166,10 +163,10 @@ export const MoreGalleries = { ), trailText: 'The Guardian’s picture editors select photographs from around the world', - mainMedia: { type: 'Gallery', count: '6' }, // TODO: get data for thismainMedia: { type: 'Gallery', count: '6' }, // TODO: get data for this + mainMedia: { type: 'Gallery', count: '6' }, }, { - url: 'http://localhost:9000/artanddesign/gallery/2025/aug/21/psychedelic-rock-glass-mountain-michael-lundgren', + url: 'https://www.theguardian.com/artanddesign/gallery/2025/aug/21/psychedelic-rock-glass-mountain-michael-lundgren', linkText: 'Psychedelic rock! Formations that mess with your mind – in pictures ', showByline: false, @@ -202,7 +199,7 @@ export const MoreGalleries = { ), trailText: 'Politicians and their partners put on their best show at this year’s Midwinter Ball, an annual dinner hosted by the Federal Parliamentary Press Gallery in Canberra', - mainMedia: { type: 'Gallery', count: '6' }, // TODO: get data for this + mainMedia: { type: 'Gallery', count: '6' }, }, ], }, diff --git a/dotcom-rendering/src/components/MoreGalleries.tsx b/dotcom-rendering/src/components/MoreGalleries.tsx index 5bbe4db44e6..d3bf828a940 100644 --- a/dotcom-rendering/src/components/MoreGalleries.tsx +++ b/dotcom-rendering/src/components/MoreGalleries.tsx @@ -21,9 +21,7 @@ type Props = { absoluteServerTimes: boolean; trails: TrailType[]; discussionApiUrl: string; - heading: string; - onwardsSource: OnwardsSource; - url?: string; + headingLink?: string; }; const wrapperStyle = css` @@ -110,9 +108,6 @@ const headerStylesWithUrl = css` const titleStyle = css` color: ${palette('--onward-text')}; display: inline-block; - &::first-letter { - text-transform: capitalize; - } `; const getDefaultCardProps = ( @@ -158,6 +153,9 @@ export const MoreGalleries = (props: Props) => { const [firstTrail, ...standardCards] = props.trails; if (!firstTrail) return null; + const heading = 'More galleries'; + const onwardsSource: OnwardsSource = 'more-galleries'; + const defaultProps = getDefaultCardProps( firstTrail, props.absoluteServerTimes, @@ -171,25 +169,22 @@ export const MoreGalleries = (props: Props) => { backgroundColour={palette('--onward-background')} showTopBorder={false} > - <div - css={wrapperStyle} - data-link-name={formatAttrString(props.heading)} - > + <div css={wrapperStyle} data-link-name={formatAttrString(heading)}> <LeftColumn size={'compact'} borderColour={palette('--onward-content-border')} hasPageSkin={false} > - <Title title={props.heading} url={props.url} /> + <Title title={heading} url={props.headingLink} /> </LeftColumn> <div css={containerStyles} - data-component={props.onwardsSource} - data-link={formatAttrString(props.heading)} + data-component={onwardsSource} + data-link={formatAttrString(heading)} > <Hide when="above" breakpoint="leftCol"> - <Title title={props.heading} url={props.url} /> + <Title title={heading} url={props.headingLink} /> </Hide> <MoreGalleriesSplashCard defaultProps={defaultProps} /> @@ -203,14 +198,14 @@ export const MoreGalleries = (props: Props) => { <ul css={standardCardsListStyles}> {standardCards.map((trail) => ( <li key={trail.url} css={standardCardStyles}> - {Card({ - ...getDefaultCardProps( + <Card + {...getDefaultCardProps( trail, props.absoluteServerTimes, props.discussionApiUrl, - ), - mediaSize: 'medium', - })} + )} + mediaSize="medium" + /> </li> ))} </ul> @@ -244,7 +239,7 @@ const MoreGalleriesSplashCard = ({ padding: ${space[2]}px; `} > - {Card({ ...defaultProps, ...cardProps })} + <Card {...defaultProps} {...cardProps} /> </div> ); }; diff --git a/dotcom-rendering/src/paletteDeclarations.ts b/dotcom-rendering/src/paletteDeclarations.ts index c3efd7acae1..b3428e481bf 100644 --- a/dotcom-rendering/src/paletteDeclarations.ts +++ b/dotcom-rendering/src/paletteDeclarations.ts @@ -7427,7 +7427,7 @@ const paletteColours = { }, '--onward-background': { light: () => sourcePalette.neutral[100], - dark: () => sourcePalette.neutral[0], + dark: () => sourcePalette.neutral[10], }, '--onward-card-background': { light: () => sourcePalette.neutral[97], From 73cd9107cc1d1ba6fe012055200479f3a4b902b0 Mon Sep 17 00:00:00 2001 From: Marjan Kalanaki <marjan.kalanaki@guardian.co.uk> Date: Fri, 3 Oct 2025 12:23:10 +0100 Subject: [PATCH 7/7] decoupling a flexible splash card with an onward container splash card --- dotcom-rendering/src/components/Card/Card.tsx | 9 ++++++--- dotcom-rendering/src/components/MoreGalleries.tsx | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/dotcom-rendering/src/components/Card/Card.tsx b/dotcom-rendering/src/components/Card/Card.tsx index 547852d7ffb..33fa5a6d630 100644 --- a/dotcom-rendering/src/components/Card/Card.tsx +++ b/dotcom-rendering/src/components/Card/Card.tsx @@ -144,8 +144,10 @@ export type Props = { * For example, the first splash card in the second collection would be: "collection-1-splash-0" */ uniqueId?: string; - /** The Splash card in a flexible or onward container gets a different visual treatment to other cards */ + /** The Splash card in a flexible container gets a different visual treatment to other cards */ isFlexSplash?: boolean; + /** The Splash card in an onward container gets a different visual treatment to other cards */ + isOnwardSplash?: boolean; showTopBarDesktop?: boolean; showTopBarMobile?: boolean; trailTextSize?: TrailTextSize; @@ -388,6 +390,7 @@ export const Card = ({ index = 0, uniqueId = '', isFlexSplash, + isOnwardSplash, showTopBarDesktop = true, showTopBarMobile = true, trailTextSize, @@ -599,7 +602,7 @@ export const Card = ({ if (isFlexibleContainer) { return 'tablet'; } - if (isOnwardContainer && !!isFlexSplash) { + if (isOnwardSplash) { return undefined; } if ( @@ -614,7 +617,7 @@ export const Card = ({ }; const shouldShowTrailText = isOnwardContainer - ? media?.type !== 'podcast' && isFlexSplash + ? media?.type !== 'podcast' && isOnwardSplash : media?.type !== 'podcast'; /** diff --git a/dotcom-rendering/src/components/MoreGalleries.tsx b/dotcom-rendering/src/components/MoreGalleries.tsx index d3bf828a940..55e721018b0 100644 --- a/dotcom-rendering/src/components/MoreGalleries.tsx +++ b/dotcom-rendering/src/components/MoreGalleries.tsx @@ -229,7 +229,7 @@ const MoreGalleriesSplashCard = ({ mediaPositionOnDesktop: 'right', mediaPositionOnMobile: 'top', mediaSize: 'medium', - isFlexSplash: true, + isOnwardSplash: true, }; return ( <div