From 0975c3b79fae34123e7b73c376b36b7bfdef6106 Mon Sep 17 00:00:00 2001 From: DanielCliftonGuardian <110032454+DanielCliftonGuardian@users.noreply.github.com> Date: Wed, 15 Apr 2026 15:23:21 +0100 Subject: [PATCH 1/8] World cup 2026 Nav Add links, update title and background colour Remove subnav above header Remove subnav for world cup 2026 fixtures page Update SportDataPageLayout.tsx Update SportDataPageLayout.tsx Hf/tweak world cup nav (#15886) * update style to match new design and add image assets * add wide images to old headers * fix nav style and add mobLandscape and phablet image * remove unused inline svg wrap with AB test lint remove empty height styles use source space large link top spacing mock ab test in story Fix active/current page boldness --- ab-testing/config/abTests.ts | 12 + .../components/DirectoryPageNav.stories.tsx | 22 ++ .../src/components/DirectoryPageNav.tsx | 214 +++++++++++------- dotcom-rendering/src/layouts/FrontLayout.tsx | 10 +- .../src/layouts/SportDataPageLayout.tsx | 9 +- .../src/layouts/TagPageLayout.tsx | 10 +- dotcom-rendering/src/lib/worldCup2026.ts | 7 + 7 files changed, 203 insertions(+), 81 deletions(-) create mode 100644 dotcom-rendering/src/lib/worldCup2026.ts diff --git a/ab-testing/config/abTests.ts b/ab-testing/config/abTests.ts index 906f8059bac..ec977fae768 100644 --- a/ab-testing/config/abTests.ts +++ b/ab-testing/config/abTests.ts @@ -133,6 +133,18 @@ const ABTests: ABTest[] = [ groups: ["control", "variant"], shouldForceMetricsCollection: true, }, + { + name: "webx-world-cup-2026-subnav", + description: + "Test of World Cup 2026 subnav on world cup related content", + owners: ["dotcom.platform@guardian.co.uk:"], + expirationDate: "2026-07-20", + type: "server", + status: "ON", + audienceSize: 0 / 100, + groups: ["enable"], + shouldForceMetricsCollection: false, + }, ]; const activeABtests = ABTests.filter((test) => test.status === "ON"); diff --git a/dotcom-rendering/src/components/DirectoryPageNav.stories.tsx b/dotcom-rendering/src/components/DirectoryPageNav.stories.tsx index 74e0f21aedf..f76b9365f61 100644 --- a/dotcom-rendering/src/components/DirectoryPageNav.stories.tsx +++ b/dotcom-rendering/src/components/DirectoryPageNav.stories.tsx @@ -1,7 +1,17 @@ import { allModes } from '../../.storybook/modes'; import preview from '../../.storybook/preview'; +import { BetaABTests } from '../experiments/lib/beta-ab-tests'; +import { setBetaABTests } from '../lib/useAB'; import { DirectoryPageNav } from './DirectoryPageNav'; +const mockAB = new BetaABTests({ + isServer: true, + serverSideABTests: { + 'webx-world-cup-2026-subnav': 'enable', + }, +}); +setBetaABTests(mockAB); + const meta = preview.meta({ component: DirectoryPageNav, title: 'Components/Directory Page Nav', @@ -22,6 +32,18 @@ export const WomensEuro2025 = meta.story({ }, }); +export const WorldCup2026 = meta.story({ + args: { + pageId: 'football/world-cup-2026', + }, +}); + +export const WorldCup2026MatchCenter = meta.story({ + args: { + pageId: 'football/world-cup-2026/overview', + }, +}); + export const OtherCompetition = meta.story({ args: { pageId: 'football/premierleague/table', diff --git a/dotcom-rendering/src/components/DirectoryPageNav.tsx b/dotcom-rendering/src/components/DirectoryPageNav.tsx index 660a0297c7f..92d309c226f 100644 --- a/dotcom-rendering/src/components/DirectoryPageNav.tsx +++ b/dotcom-rendering/src/components/DirectoryPageNav.tsx @@ -3,16 +3,17 @@ import { type Breakpoint, breakpoints, from, - headlineBold15Object, - headlineBold17Object, headlineBold24Object, headlineBold42Object, - headlineMedium15Object, - headlineMedium17Object, palette, + space, + textSans14Object, + textSansBold14Object, } from '@guardian/source/foundations'; import { grid } from '../grid'; import { generateImageURL } from '../lib/image'; +import { useBetaAB } from '../lib/useAB'; +import { worldCup2026PageIds } from '../lib/worldCup2026'; import type { TagType } from '../types/tag'; type Props = { @@ -25,6 +26,7 @@ interface DirectoryPageNavConfig { tagIds: string[]; textColor: string; backgroundColor: string; + titleIcon?: React.ReactElement; title: { label: string; id: string }; links: Array<{ label: string; id: string }>; backgroundImages?: { @@ -33,10 +35,77 @@ interface DirectoryPageNavConfig { phablet: string; tablet: string; desktop: string; + wide: string; }; } +const WorldCup2026Icon = () => ( + + + + + + +); + const configs = [ + // World Cup 2026 + { + pageIds: worldCup2026PageIds, + tagIds: [], + textColor: palette.neutral[100], + backgroundColor: palette.brand[400], + title: { + label: 'World Cup', + id: 'football/world-cup-2026', + }, + titleIcon: , + links: [ + { + label: 'Match centre', + id: 'football/world-cup-2026/overview', + }, + { + label: 'Player guide', + id: '', + }, + { + label: 'Bracketology', + id: '', + }, + { + label: 'Golden boot', + id: '', + }, + { + label: 'More football', + id: 'football', + }, + ], + backgroundImages: { + mobile: 'https://media.guim.co.uk/4ba0caac6d18c1fe6a5a3267b270d8c21ae6f940/0_0_750_376/750.jpg', + mobileLandscape: + 'https://media.guim.co.uk/8e1356cc926c6bbfcdb3da5908252ba0b4cbd3bb/0_0_960_376/960.jpg', + phablet: + 'https://media.guim.co.uk/ed4fe540c6a114db35c1f73fc41ee802c3fea7d3/0_0_1320_282/1320.jpg', + tablet: 'https://media.guim.co.uk/861646115875f3f246313036f754b2f5f1480b1a/0_0_1480_276/1480.jpg', + desktop: + 'https://media.guim.co.uk/167bec4a208bfc7fdc6b2127186b9bb183932259/0_0_1960_276/1960.jpg', + wide: 'https://media.guim.co.uk/4e44f9a88fcc9a3b1b5294f7e581644baa75c904/0_0_2600_276/2600.jpg', + }, + }, // Winter Olympics 2026 { pageIds: [ @@ -79,6 +148,7 @@ const configs = [ tablet: 'https://uploads.guim.co.uk/2026/02/03/winter-olympics-740px-thin.jpg', desktop: 'https://uploads.guim.co.uk/2026/02/03/winter-olympics-980px.jpg', + wide: 'https://uploads.guim.co.uk/2026/02/03/winter-olympics-980px.jpg', }, }, // Winter Paralympics 2026 @@ -118,11 +188,14 @@ const configs = [ tablet: 'https://uploads.guim.co.uk/2026/03/03/winter-paralympics-740px-thin.jpg', desktop: 'https://uploads.guim.co.uk/2026/03/03/winter-paralympics-980px.jpg', + wide: 'https://uploads.guim.co.uk/2026/03/03/winter-paralympics-980px.jpg', }, }, ] satisfies DirectoryPageNavConfig[]; export const DirectoryPageNav = ({ pageId, pageTags }: Props) => { + const ab = useBetaAB(); + const config = configs.find( (cfg) => cfg.pageIds.includes(pageId) || @@ -135,20 +208,33 @@ export const DirectoryPageNav = ({ pageId, pageTags }: Props) => { return null; } + if ( + config.title.id === 'football/world-cup-2026' && + ab?.isUserInTest('webx-world-cup-2026-subnav') !== true + ) { + return null; + } + const { textColor, backgroundColor } = config; const nav = css({ backgroundColor, '&': css(grid.paddedContainer), alignContent: 'space-between', + position: 'relative', }); const largeLinkStyles = css({ + position: 'absolute', + top: space[3], + left: 0, ...headlineBold24Object, color: textColor, textDecoration: 'none', '&': css(grid.column.centre), gridRow: 1, + display: 'flex', + alignItems: 'flex-start', [from.tablet]: headlineBold42Object, [from.leftCol]: css( grid.between('left-column-start', 'right-column-end'), @@ -156,38 +242,35 @@ export const DirectoryPageNav = ({ pageId, pageTags }: Props) => { }); const list = css({ - display: 'flex', - flexWrap: 'wrap', '&': css(grid.column.all), - gridRow: 2, - alignSelf: 'end', + display: 'flex', + alignItems: 'center', position: 'relative', - '--top-border-gap': '1.55rem', + overflowX: 'scroll', + scrollbarWidth: 'none', + borderTop: '1px solid', + borderColor: palette.brand[600], + padding: `0 ${space[3]}px`, + height: space[10], [from.mobileLandscape]: { - paddingLeft: 10, + padding: `0 ${space[5]}px`, + height: space[12], }, - [from.tablet]: { - '--top-border-gap': '3rem', + // This creates a gradient fade on the right side to indicate that there's more to scroll for. + '&:after': { + content: '""', + position: 'sticky', + right: `-${space[3]}px`, + top: 0, + height: '100%', + minWidth: 40, + background: `linear-gradient(to left, ${backgroundColor}, transparent)`, + [from.mobileLandscape]: { + right: `-${space[5]}px`, + }, }, - backgroundImage: ` - linear-gradient( - ${textColor} 0, - ${textColor} 1px, - transparent 1px, - transparent var(--top-border-gap), - ${textColor} var(--top-border-gap), - ${textColor} calc(var(--top-border-gap) + 1px), - transparent 1px, - transparent var(--top-border-gap) - ) - `, }); - const selectedStyles = { - '--selected-height': '4px', - '--selected-opacity': '1', - }; - const listItem = css({ position: 'relative', '&::before': { @@ -202,61 +285,45 @@ export const DirectoryPageNav = ({ pageId, pageTags }: Props) => { backgroundColor: textColor, transition: 'height 0.3s ease-in-out, opacity 0.05s 0.1s linear', }, - '&:hover': selectedStyles, - [from.leftCol]: { - flexBasis: 160, + [from.desktop]: { + '&:hover a': { + textDecoration: 'underline', + color: 'var(--masthead-nav-link-text-hover)', + }, }, }); const smallLink = css({ - ...headlineBold15Object, - padding: '4px 10px 6px', + ...textSans14Object, + paddingRight: space[3], display: 'block', lineHeight: 1, color: textColor, textDecoration: 'none', - '&::after': { - content: '""', - display: 'block', - position: 'absolute', - top: 0, - right: 0, - width: 1, - height: '1.3rem', - backgroundColor: textColor, - }, - [from.tablet]: headlineBold17Object, - [from.leftCol]: { - padding: '9px 10px 10px', - }, + whiteSpace: 'nowrap', }); - const lastSmallLink = css(smallLink, { - ...headlineMedium15Object, - lineHeight: 1, - [from.tablet]: headlineMedium17Object, + const boldSmallLink = css({ + ...textSansBold14Object, }); return ( -