From 2719b83b067583d7b53119bba8e0c49dfc3c670e Mon Sep 17 00:00:00 2001 From: Dominik Lander Date: Thu, 26 Feb 2026 11:18:18 +0000 Subject: [PATCH] Add Most Viewed Front Right component and Slim Homepage AB test --- ab-testing/config/abTests.ts | 13 ++ .../src/components/LinkHeadline.tsx | 24 ++- .../MostPopularFrontRight.stories.tsx | 29 ++++ .../src/components/MostPopularFrontRight.tsx | 153 ++++++++++++++++++ dotcom-rendering/src/paletteDeclarations.ts | 12 ++ 5 files changed, 227 insertions(+), 4 deletions(-) create mode 100644 dotcom-rendering/src/components/MostPopularFrontRight.stories.tsx create mode 100644 dotcom-rendering/src/components/MostPopularFrontRight.tsx diff --git a/ab-testing/config/abTests.ts b/ab-testing/config/abTests.ts index d795763977a..fd162b1f6ab 100644 --- a/ab-testing/config/abTests.ts +++ b/ab-testing/config/abTests.ts @@ -107,6 +107,19 @@ const ABTests: ABTest[] = [ groups: ["control"], shouldForceMetricsCollection: false, }, + { + name: "fronts-and-curation-slim-homepage", + description: + "Test placing the Most Viewed and Deeply Read components in the right-hand column on the homepage.", + owners: ["fronts.and.curation@guardian.co.uk"], + status: "OFF", + expirationDate: `2026-04-28`, + type: "server", + audienceSize: 0 / 100, + audienceSpace: "A", + groups: ["control", "variant"], + shouldForceMetricsCollection: false, + }, { name: "growth-holdback-group", description: diff --git a/dotcom-rendering/src/components/LinkHeadline.tsx b/dotcom-rendering/src/components/LinkHeadline.tsx index 8bcb4089cbd..8feaac3c670 100644 --- a/dotcom-rendering/src/components/LinkHeadline.tsx +++ b/dotcom-rendering/src/components/LinkHeadline.tsx @@ -5,6 +5,7 @@ import { headlineMedium20, headlineMedium24, headlineMedium28, + textSans15, textSans17, textSans20, textSans24, @@ -33,9 +34,23 @@ type Props = { size?: SmallHeadlineSize; link?: HeadlineLink; // An optional link object configures if/how the component renders an anchor tag byline?: string; + /** + * This headline is being used on the right-hand side of a front in a most popular container, + * either Most Viewed or Deeply Read, as part of the Slim Homepage AB test. + */ + isInSlimHomepageAbTest?: boolean; }; -const fontStyles = (size: SmallHeadlineSize) => { +const fontStyles = ( + size: SmallHeadlineSize, + isInSlimHomepageAbTest: boolean, +) => { + if (isInSlimHomepageAbTest) { + return css` + ${textSans15}; + `; + } + switch (size) { case 'ginormous': case 'huge': @@ -124,9 +139,10 @@ export const LinkHeadline = ({ size = 'medium', link, byline, + isInSlimHomepageAbTest = false, }: Props) => { return ( -

+

{!!kickerText && ( @@ -175,7 +191,7 @@ export const LinkHeadline = ({ fontStyles={ isLabs ? bylineLabsStyles(size) - : fontStyles(size) + : fontStyles(size, isInSlimHomepageAbTest) } text={byline} /> diff --git a/dotcom-rendering/src/components/MostPopularFrontRight.stories.tsx b/dotcom-rendering/src/components/MostPopularFrontRight.stories.tsx new file mode 100644 index 00000000000..5beec8cf891 --- /dev/null +++ b/dotcom-rendering/src/components/MostPopularFrontRight.stories.tsx @@ -0,0 +1,29 @@ +import type { Meta, StoryObj } from '@storybook/react-webpack5'; +import { rightColumnDecorator } from '../../.storybook/decorators/gridDecorators'; +import { trails } from '../../fixtures/manual/trails'; +import { MostPopularFrontRight } from './MostPopularFrontRight'; + +const meta = { + component: MostPopularFrontRight, + title: 'Components/MostPopularFrontRight', + decorators: [rightColumnDecorator], + render: (args) => , +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const MostViewed = { + args: { + heading: 'Most viewed', + trails: trails.slice(0, 10), + }, +} satisfies Story; + +export const DeeplyRead = { + args: { + heading: 'Deeply read', + trails: trails.slice(10, 20), + }, +} satisfies Story; diff --git a/dotcom-rendering/src/components/MostPopularFrontRight.tsx b/dotcom-rendering/src/components/MostPopularFrontRight.tsx new file mode 100644 index 00000000000..a05da13bac3 --- /dev/null +++ b/dotcom-rendering/src/components/MostPopularFrontRight.tsx @@ -0,0 +1,153 @@ +import { css } from '@emotion/react'; +import { + headlineBold24, + space, + textSans15, +} from '@guardian/source/foundations'; +import { ArticleDesign, ArticleSpecial } from '../lib/articleFormat'; +import { palette as schemePalette } from '../palette'; +import type { TrailType } from '../types/trails'; +import { AgeWarning } from './AgeWarning'; +import { BigNumber } from './BigNumber'; +import { FormatBoundary } from './FormatBoundary'; +import { LinkHeadline } from './LinkHeadline'; + +const containerStyles = css` + padding-top: ${space[2]}px; + display: flex; + flex-direction: column; + gap: ${space[8]}px; +`; + +const headingStyles = css` + padding-left: 80px; + ${headlineBold24}; + color: ${schemePalette('--slim-homepage-most-viewed-header')}; + overflow-wrap: break-word; +`; + +const gridItem = css` + position: relative; + border-top: 1px solid ${schemePalette('--article-section-border')}; + min-height: 52px; + + &:hover { + cursor: pointer; + } + + &:hover, + :focus { + background: ${schemePalette('--most-viewed-footer-hover')}; + } +`; + +const bigNumber = css` + position: absolute; + top: 6px; + left: 10px; + fill: ${schemePalette('--slim-homepage-most-viewed-big-number')}; + svg { + height: 40px; + } +`; + +const headlineLink = css` + display: block; /* To ensure focus outline works okay */ + padding: 3px 10px 18px 80px; + ${textSans15}; + text-decoration: none; + color: ${schemePalette('--slim-homepage-most-viewed-headline')}; + font-weight: 500; + word-wrap: break-word; + overflow: hidden; +`; + +const ageWarningStyles = css` + padding-left: 75px; + margin-top: -16px; + margin-bottom: 16px; +`; + +type Props = { + heading: 'Most viewed' | 'Deeply read'; + trails: TrailType[]; +}; + +/** + * Renders the Most Viewed or Deeply Read component, often seen at the bottom of the page on + * network front, into a newly created right column high up on a front page. This component is + * only used as a part of an ongoing AB test and should not be used outside of this AB test. + */ +export const MostPopularFrontRight = ({ heading, trails }: Props) => { + if (trails.length === 0) return null; + + return ( +
+

{heading}

+
    + {trails.slice(0, 10).map((trail: TrailType, index: number) => { + const { url, format, headline, ageWarning } = trail; + + return ( +
  1. + + + + + + {format.design === + ArticleDesign.LiveBlog ? ( + + ) : ( + + )} + + {!!ageWarning && ( +
    + +
    + )} +
    +
  2. + ); + })} +
+
+ ); +}; diff --git a/dotcom-rendering/src/paletteDeclarations.ts b/dotcom-rendering/src/paletteDeclarations.ts index 42c5f2f68f9..c3191163df0 100644 --- a/dotcom-rendering/src/paletteDeclarations.ts +++ b/dotcom-rendering/src/paletteDeclarations.ts @@ -7995,6 +7995,18 @@ const paletteColours = { light: slideshowPaginationDotActiveLight, dark: slideshowPaginationDotActiveDark, }, + '--slim-homepage-most-viewed-big-number': { + light: () => sourcePalette.neutral[60], + dark: () => sourcePalette.neutral[60], + }, + '--slim-homepage-most-viewed-header': { + light: () => sourcePalette.neutral[46], + dark: () => sourcePalette.neutral[46], + }, + '--slim-homepage-most-viewed-headline': { + light: () => sourcePalette.neutral[0], + dark: () => sourcePalette.neutral[0], + }, '--speech-bubble-background': { light: speechBubbleBackgroundLight, dark: speechBubbleBackgroundLight,