From d792eb99d960729c39203089d9b66bdcc6b7c540 Mon Sep 17 00:00:00 2001 From: Juarez Mota Date: Tue, 3 Mar 2026 11:29:35 +0000 Subject: [PATCH 01/19] feat: add identity headers to support banner requests when user is signed in --- .../components/TopBarSupport.importable.tsx | 19 +++++++++++++++---- dotcom-rendering/src/lib/sdcRequests.ts | 3 ++- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/dotcom-rendering/src/components/TopBarSupport.importable.tsx b/dotcom-rendering/src/components/TopBarSupport.importable.tsx index c5ff084cc00..71c3afc8f21 100644 --- a/dotcom-rendering/src/components/TopBarSupport.importable.tsx +++ b/dotcom-rendering/src/components/TopBarSupport.importable.tsx @@ -5,7 +5,6 @@ import { css } from '@emotion/react'; import { getCookie, isUndefined } from '@guardian/libs'; import type { ComponentEvent } from '@guardian/ophan-tracker-js'; -import { getHeader } from '@guardian/support-dotcom-components'; import type { HeaderPayload, ModuleData, @@ -21,8 +20,10 @@ import { getPurchaseInfo, shouldHideSupportMessaging, } from '../lib/contributions'; +import { getOptionsHeaders } from '../lib/identity'; +import { getHeader } from '../lib/sdcRequests'; import { useBetaAB } from '../lib/useAB'; -import { useIsSignedIn } from '../lib/useAuthStatus'; +import { useAuthStatus } from '../lib/useAuthStatus'; import { useCountryCode } from '../lib/useCountryCode'; import { usePageViewId } from '../lib/usePageViewId'; import { useConfig } from './ConfigContext'; @@ -56,7 +57,11 @@ const ReaderRevenueLinksRemote = ({ useState | null>(null); const [SupportHeader, setSupportHeader] = useState | null>(null); - const isSignedIn = useIsSignedIn(); + const authStatus = useAuthStatus(); + const isSignedIn = + authStatus.kind === 'Pending' + ? 'Pending' + : authStatus.kind === 'SignedIn'; const { renderingTarget } = useConfig(); const abTests = useBetaAB(); @@ -90,7 +95,12 @@ const ReaderRevenueLinksRemote = ({ }, }; - getHeader(contributionsServiceUrl, requestData) + const headers = + authStatus.kind === 'SignedIn' + ? getOptionsHeaders(authStatus).headers + : undefined; + + getHeader(contributionsServiceUrl, requestData, headers) .then((response: ModuleDataResponse) => { if (!response.data) { return null; @@ -131,6 +141,7 @@ const ReaderRevenueLinksRemote = ({ pageViewId, pageUrl, abTests, + authStatus, ]); if (SupportHeader !== null && supportHeaderResponse) { diff --git a/dotcom-rendering/src/lib/sdcRequests.ts b/dotcom-rendering/src/lib/sdcRequests.ts index 36656c2b8d2..e2d9ff9d4d6 100644 --- a/dotcom-rendering/src/lib/sdcRequests.ts +++ b/dotcom-rendering/src/lib/sdcRequests.ts @@ -112,5 +112,6 @@ export const getGutterLiveblog = ( export const getHeader = ( baseUrl: string, payload: HeaderPayload, + headers?: HeadersInit, ): Promise> => - getModuleData('header', baseUrl, payload); + getModuleData('header', baseUrl, payload, headers); From 7c3ea21d26b4074b81a67e727d368f17d7c7d1c9 Mon Sep 17 00:00:00 2001 From: Juarez Mota Date: Tue, 3 Mar 2026 14:33:25 +0000 Subject: [PATCH 02/19] feat: use getAuthHeaders for support banner requests to include identity information --- .../components/TopBarSupport.importable.tsx | 21 +++++++------------ 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/dotcom-rendering/src/components/TopBarSupport.importable.tsx b/dotcom-rendering/src/components/TopBarSupport.importable.tsx index 71c3afc8f21..4859b2579eb 100644 --- a/dotcom-rendering/src/components/TopBarSupport.importable.tsx +++ b/dotcom-rendering/src/components/TopBarSupport.importable.tsx @@ -17,13 +17,13 @@ import type { import { useEffect, useState } from 'react'; import { submitComponentEvent } from '../client/ophan/ophan'; import { + getAuthHeaders, getPurchaseInfo, shouldHideSupportMessaging, } from '../lib/contributions'; -import { getOptionsHeaders } from '../lib/identity'; import { getHeader } from '../lib/sdcRequests'; import { useBetaAB } from '../lib/useAB'; -import { useAuthStatus } from '../lib/useAuthStatus'; +import { useIsSignedIn } from '../lib/useAuthStatus'; import { useCountryCode } from '../lib/useCountryCode'; import { usePageViewId } from '../lib/usePageViewId'; import { useConfig } from './ConfigContext'; @@ -57,11 +57,7 @@ const ReaderRevenueLinksRemote = ({ useState | null>(null); const [SupportHeader, setSupportHeader] = useState | null>(null); - const authStatus = useAuthStatus(); - const isSignedIn = - authStatus.kind === 'Pending' - ? 'Pending' - : authStatus.kind === 'SignedIn'; + const isSignedIn = useIsSignedIn(); const { renderingTarget } = useConfig(); const abTests = useBetaAB(); @@ -95,12 +91,10 @@ const ReaderRevenueLinksRemote = ({ }, }; - const headers = - authStatus.kind === 'SignedIn' - ? getOptionsHeaders(authStatus).headers - : undefined; - - getHeader(contributionsServiceUrl, requestData, headers) + void getAuthHeaders() + .then((headers) => + getHeader(contributionsServiceUrl, requestData, headers), + ) .then((response: ModuleDataResponse) => { if (!response.data) { return null; @@ -141,7 +135,6 @@ const ReaderRevenueLinksRemote = ({ pageViewId, pageUrl, abTests, - authStatus, ]); if (SupportHeader !== null && supportHeaderResponse) { From 032f324bce53ccd29693cb5fcda4c7bacf3f60a5 Mon Sep 17 00:00:00 2001 From: Juarez Mota Date: Wed, 4 Mar 2026 22:34:48 +0000 Subject: [PATCH 03/19] feat: mock contributions API in storybook to prevent hanging on consent state. --- dotcom-rendering/.storybook/main.ts | 46 ++++++++++--------- .../.storybook/mocks/contributions.ts | 29 ++++++++++++ .../components/TopBarSupport.importable.tsx | 5 +- 3 files changed, 57 insertions(+), 23 deletions(-) create mode 100644 dotcom-rendering/.storybook/mocks/contributions.ts diff --git a/dotcom-rendering/.storybook/main.ts b/dotcom-rendering/.storybook/main.ts index dc727ad7b88..c4f81e0818d 100644 --- a/dotcom-rendering/.storybook/main.ts +++ b/dotcom-rendering/.storybook/main.ts @@ -103,24 +103,33 @@ const webpackConfig = (config: Configuration) => { const rules = config.module?.rules ?? []; config.resolve ??= {}; - config.resolve.alias ??= {}; + // config.resolve.alias ??= {}; - // Mock JSDOM for storybook - it relies on native node.js packages - // Allows us to use enhancers in stories for better testing of components & full articles - config.resolve.alias['jsdom$'] = path.resolve( - __dirname, - './mocks/jsdom.ts', - ); + config.resolve.alias = { + ...config.resolve?.alias, + + Buffer: 'buffer', + react: 'react', + 'react-dom': 'react-dom', + + // Mock JSDOM for storybook - it relies on native node.js packages + // Allows us to use enhancers in stories for better testing of components & full articles + jsdom$: path.resolve(__dirname, './mocks/jsdom.ts'), + + // log4js tries to call "fs" in storybook -- we can ignore it + [`${path.resolve(__dirname, '../src/server/lib/logging')}$`]: + path.resolve(__dirname, './mocks/log4js.ts'), - // log4js tries to call "fs" in storybook -- we can ignore it - config.resolve.alias[ - `${path.resolve(__dirname, '../src/server/lib/logging')}$` - ] = path.resolve(__dirname, './mocks/log4js.ts'); + // Mock BridgetApi for storybook + [`${path.resolve(__dirname, '../src/lib/bridgetApi')}$`]: path.resolve( + __dirname, + './mocks/bridgetApi.ts', + ), - // Mock BridgetApi for storybook - config.resolve.alias[ - `${path.resolve(__dirname, '../src/lib/bridgetApi')}$` - ] = path.resolve(__dirname, './mocks/bridgetApi.ts'); + // Mock contributions API so getAuthHeaders does not hang on consent state in Storybook + [`${path.resolve(__dirname, '../src/lib/contributions')}$`]: + path.resolve(__dirname, './mocks/contributions.ts'), + }; const webpackLoaders = getLoaders('client.web'); @@ -149,12 +158,7 @@ const webpackConfig = (config: Configuration) => { config.resolve.modules = [ ...((config && config.resolve && config.resolve.modules) || []), ]; - config.resolve.alias = { - ...config.resolve.alias, - Buffer: 'buffer', - react: 'react', - 'react-dom': 'react-dom', - }; + return config; }; diff --git a/dotcom-rendering/.storybook/mocks/contributions.ts b/dotcom-rendering/.storybook/mocks/contributions.ts new file mode 100644 index 00000000000..a3af83b54c5 --- /dev/null +++ b/dotcom-rendering/.storybook/mocks/contributions.ts @@ -0,0 +1,29 @@ +/** + * Mock for use in Storybook + * Avoids circular dependencies from the Webpack alias by explicitly mocking + * only the functions required by the components. + */ + +export const getAuthHeaders = (): Promise => { + console.log('[Storybook Mock] getAuthHeaders returning undefined'); + return Promise.resolve(undefined); +}; + +export const getPurchaseInfo = (): any => undefined; + +export const shouldHideSupportMessaging = (): boolean | 'Pending' => false; + +// Additional missing exports required by other components in Storybook +export const useHasOptedOutOfArticleCount = (): boolean | 'Pending' => false; +export const hasOptedOutOfArticleCount = async (): Promise => false; +export const hasOptedOutOfWeeklyArticleCount = async (): Promise => + false; +export const hasCmpConsentForWeeklyArticleCount = async (): Promise => + false; +export const recentlyClosedBanner = (): boolean => false; +export const withinLocalNoBannerCachePeriod = (): boolean => false; +export const setLocalNoBannerCachePeriod = (): void => {}; +export const getContributionsServiceUrl = (): string => ''; +export const hasCmpConsentForBrowserId = async (): Promise => false; +export const SUPPORT_ONE_OFF_CONTRIBUTION_COOKIE = + 'gu.contributions.contrib-timestamp'; diff --git a/dotcom-rendering/src/components/TopBarSupport.importable.tsx b/dotcom-rendering/src/components/TopBarSupport.importable.tsx index 4859b2579eb..c59562d645d 100644 --- a/dotcom-rendering/src/components/TopBarSupport.importable.tsx +++ b/dotcom-rendering/src/components/TopBarSupport.importable.tsx @@ -69,6 +69,7 @@ const ReaderRevenueLinksRemote = ({ const hideSupportMessagingForUser = shouldHideSupportMessaging(isSignedIn); + if (hideSupportMessagingForUser === 'Pending') { // We don't yet know the user's supporter status return; @@ -106,9 +107,9 @@ const ReaderRevenueLinksRemote = ({ return ( module.name === 'SignInPromptHeader' ? /* webpackChunkName: "sign-in-prompt-header" */ - import(`./marketing/header/SignInPromptHeader`) + import('./marketing/header/SignInPromptHeader') : /* webpackChunkName: "header" */ - import(`./marketing/header/Header`) + import('./marketing/header/Header') ).then( (headerModule: { [key: string]: React.ElementType; From a0a25b5c9a6749858902fd929fb02a5a9d64d484 Mon Sep 17 00:00:00 2001 From: Juarez Mota Date: Wed, 4 Mar 2026 22:36:56 +0000 Subject: [PATCH 04/19] chore: remove commented-out alias assignment in Storybook config --- dotcom-rendering/.storybook/main.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/dotcom-rendering/.storybook/main.ts b/dotcom-rendering/.storybook/main.ts index c4f81e0818d..a54d733138e 100644 --- a/dotcom-rendering/.storybook/main.ts +++ b/dotcom-rendering/.storybook/main.ts @@ -103,7 +103,6 @@ const webpackConfig = (config: Configuration) => { const rules = config.module?.rules ?? []; config.resolve ??= {}; - // config.resolve.alias ??= {}; config.resolve.alias = { ...config.resolve?.alias, From 5dda22e5e62be93fec6d0f5d133b990346eeb718 Mon Sep 17 00:00:00 2001 From: Juarez Mota Date: Thu, 5 Mar 2026 11:29:14 +0000 Subject: [PATCH 05/19] fix: webpack server build failing due broken parse5 source maps inside node_modules. --- dotcom-rendering/webpack/webpack.config.server.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dotcom-rendering/webpack/webpack.config.server.js b/dotcom-rendering/webpack/webpack.config.server.js index f7cb667c4db..7136036c21a 100644 --- a/dotcom-rendering/webpack/webpack.config.server.js +++ b/dotcom-rendering/webpack/webpack.config.server.js @@ -3,6 +3,8 @@ const nodeExternals = require('webpack-node-externals'); const swcConfig = require('./.swcrc.json'); const { svgr } = require('./svg.cjs'); +const { transpileExclude } = require('./webpack.config.client.js'); + const DEV = process.env.NODE_ENV === 'development'; const nodeVersion = process.versions.node; @@ -77,6 +79,7 @@ module.exports = { rules: [ { test: /(\.tsx|\.js|\.ts)$/, + exclude: transpileExclude, use: swcLoader, }, svgr, From 42c78877c3776ffdbdf4078525d4054caebb43ab Mon Sep 17 00:00:00 2001 From: Juarez Mota Date: Thu, 5 Mar 2026 11:32:36 +0000 Subject: [PATCH 06/19] refactor: update TopBarSupport to use `useAuthStatus`, add a timeout for fetching authentication headers, and centralize error reporting. --- .../components/TopBarSupport.importable.tsx | 61 +++++++++++++++---- 1 file changed, 49 insertions(+), 12 deletions(-) diff --git a/dotcom-rendering/src/components/TopBarSupport.importable.tsx b/dotcom-rendering/src/components/TopBarSupport.importable.tsx index c59562d645d..5c563867d95 100644 --- a/dotcom-rendering/src/components/TopBarSupport.importable.tsx +++ b/dotcom-rendering/src/components/TopBarSupport.importable.tsx @@ -21,9 +21,10 @@ import { getPurchaseInfo, shouldHideSupportMessaging, } from '../lib/contributions'; +import { getOptionsHeaders } from '../lib/identity'; import { getHeader } from '../lib/sdcRequests'; import { useBetaAB } from '../lib/useAB'; -import { useIsSignedIn } from '../lib/useAuthStatus'; +import { useAuthStatus } from '../lib/useAuthStatus'; import { useCountryCode } from '../lib/useCountryCode'; import { usePageViewId } from '../lib/usePageViewId'; import { useConfig } from './ConfigContext'; @@ -57,11 +58,26 @@ const ReaderRevenueLinksRemote = ({ useState | null>(null); const [SupportHeader, setSupportHeader] = useState | null>(null); - const isSignedIn = useIsSignedIn(); + const authStatus = useAuthStatus(); + const isSignedIn = + authStatus.kind === 'Pending' + ? 'Pending' + : authStatus.kind === 'SignedIn'; const { renderingTarget } = useConfig(); const abTests = useBetaAB(); + const reportError = (error: unknown, context: string) => { + const msg = `Error importing RR header links for ${context}: ${String( + error, + )}`; + console.log(msg); + window.guardian.modules.sentry.reportError( + new Error(msg), + 'rr-header-links', + ); + }; + useEffect((): void => { if (isUndefined(countryCode) || isSignedIn === 'Pending') { return; @@ -92,9 +108,35 @@ const ReaderRevenueLinksRemote = ({ }, }; - void getAuthHeaders() - .then((headers) => - getHeader(contributionsServiceUrl, requestData, headers), + const headers = + authStatus.kind === 'SignedIn' + ? getOptionsHeaders(authStatus).headers + : undefined; + + const fetchAuthHeadersWithTimeout = () => { + return Promise.race([ + getAuthHeaders(), + new Promise((resolve) => { + setTimeout(() => { + resolve(undefined); + }, 2000); + }), + ]); + }; + + void fetchAuthHeadersWithTimeout() + .catch((error: unknown) => { + // Catch any errors from getAuthHeaders itself or the timeout + reportError(error, 'getAuthHeaders'); + return undefined; + }) + .then((fallbackHeaders) => + // Fallback to fallbackHeaders if present, otherwise use real headers + getHeader( + contributionsServiceUrl, + requestData, + fallbackHeaders ?? headers, + ), ) .then((response: ModuleDataResponse) => { if (!response.data) { @@ -121,13 +163,7 @@ const ReaderRevenueLinksRemote = ({ ); }) .catch((error) => { - const msg = `Error importing RR header links: ${String(error)}`; - - console.log(msg); - window.guardian.modules.sentry.reportError( - new Error(msg), - 'rr-header-links', - ); + reportError(error, 'fetching headers or importing component'); }); }, [ countryCode, @@ -136,6 +172,7 @@ const ReaderRevenueLinksRemote = ({ pageViewId, pageUrl, abTests, + authStatus, ]); if (SupportHeader !== null && supportHeaderResponse) { From 08cbf93031eda631e126ce8be1839c698f859dec Mon Sep 17 00:00:00 2001 From: Juarez Mota Date: Thu, 5 Mar 2026 11:45:33 +0000 Subject: [PATCH 07/19] chore: fix empty line lint rule --- dotcom-rendering/webpack/webpack.config.server.js | 1 - 1 file changed, 1 deletion(-) diff --git a/dotcom-rendering/webpack/webpack.config.server.js b/dotcom-rendering/webpack/webpack.config.server.js index 7136036c21a..e6d6c4fc719 100644 --- a/dotcom-rendering/webpack/webpack.config.server.js +++ b/dotcom-rendering/webpack/webpack.config.server.js @@ -2,7 +2,6 @@ const nodeExternals = require('webpack-node-externals'); const swcConfig = require('./.swcrc.json'); const { svgr } = require('./svg.cjs'); - const { transpileExclude } = require('./webpack.config.client.js'); const DEV = process.env.NODE_ENV === 'development'; From 854d94bb3a1c4d1920b571694c5acca056de56fa Mon Sep 17 00:00:00 2001 From: Juarez Mota Date: Thu, 5 Mar 2026 11:54:00 +0000 Subject: [PATCH 08/19] chore: better naming header variables --- .../components/TopBarSupport.importable.tsx | 8 +++---- pnpm-lock.yaml | 24 +++++++++---------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/dotcom-rendering/src/components/TopBarSupport.importable.tsx b/dotcom-rendering/src/components/TopBarSupport.importable.tsx index 5c563867d95..8937e2c784c 100644 --- a/dotcom-rendering/src/components/TopBarSupport.importable.tsx +++ b/dotcom-rendering/src/components/TopBarSupport.importable.tsx @@ -108,7 +108,7 @@ const ReaderRevenueLinksRemote = ({ }, }; - const headers = + const fallbackHeaders = authStatus.kind === 'SignedIn' ? getOptionsHeaders(authStatus).headers : undefined; @@ -130,12 +130,12 @@ const ReaderRevenueLinksRemote = ({ reportError(error, 'getAuthHeaders'); return undefined; }) - .then((fallbackHeaders) => - // Fallback to fallbackHeaders if present, otherwise use real headers + .then((headers) => + // Fallback to fallbackHeaders if headers are not present getHeader( contributionsServiceUrl, requestData, - fallbackHeaders ?? headers, + headers ?? fallbackHeaders, ), ) .then((response: ModuleDataResponse) => { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2b28ca418d9..f79764289f1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12502,13 +12502,13 @@ snapshots: '@guardian/eslint-config-typescript@12.0.0(eslint@8.57.1)(tslib@2.6.2)(typescript@5.5.3)': dependencies: - '@guardian/eslint-config': 9.0.0(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.1)(tslib@2.6.2) + '@guardian/eslint-config': 9.0.0(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)(tslib@2.6.2) '@stylistic/eslint-plugin': 2.6.2(eslint@8.57.1)(typescript@5.5.3) '@typescript-eslint/eslint-plugin': 8.1.0(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1)(typescript@5.5.3) '@typescript-eslint/parser': 8.1.0(eslint@8.57.1)(typescript@5.5.3) eslint: 8.57.1 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-plugin-import@2.29.1)(eslint@8.57.1) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1))(eslint@8.57.1) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) tslib: 2.6.2 typescript: 5.5.3 transitivePeerDependencies: @@ -12537,12 +12537,12 @@ snapshots: - supports-color - typescript - '@guardian/eslint-config@9.0.0(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.1)(tslib@2.6.2)': + '@guardian/eslint-config@9.0.0(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)(tslib@2.6.2)': dependencies: eslint: 8.57.1 eslint-config-prettier: 9.1.0(eslint@8.57.1) eslint-plugin-eslint-comments: 3.2.0(eslint@8.57.1) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.1) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) tslib: 2.6.2 transitivePeerDependencies: - '@typescript-eslint/parser' @@ -16546,13 +16546,13 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-plugin-import@2.29.1)(eslint@8.57.1): + eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1))(eslint@8.57.1): dependencies: debug: 4.4.3(supports-color@8.1.1) enhanced-resolve: 5.19.0 eslint: 8.57.1 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.1) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.1) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) fast-glob: 3.3.3 get-tsconfig: 4.7.2 is-core-module: 2.16.1 @@ -16580,14 +16580,14 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.1): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.1.0(eslint@8.57.1)(typescript@5.5.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-plugin-import@2.29.1)(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1))(eslint@8.57.1) transitivePeerDependencies: - supports-color @@ -16632,7 +16632,7 @@ snapshots: - supports-color - typescript - eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.1): + eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): dependencies: array-includes: 3.1.9 array.prototype.findlastindex: 1.2.6 @@ -16642,7 +16642,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.1) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@8.1.0(eslint@8.57.1)(typescript@5.5.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 From 2c89d8e6d9e22ef09ac85f51d8c34f468ded4a7c Mon Sep 17 00:00:00 2001 From: Juarez Mota Date: Thu, 5 Mar 2026 13:13:21 +0000 Subject: [PATCH 09/19] fix: missing headers in DecideLayout, Gallery and Sport Data storybooks --- dotcom-rendering/.storybook/main.ts | 6 ++++++ .../.storybook/mocks/identityAuthFrontend.ts | 8 ++++++++ dotcom-rendering/src/lib/mockRESTCalls.ts | 15 +++++++-------- 3 files changed, 21 insertions(+), 8 deletions(-) create mode 100644 dotcom-rendering/.storybook/mocks/identityAuthFrontend.ts diff --git a/dotcom-rendering/.storybook/main.ts b/dotcom-rendering/.storybook/main.ts index a54d733138e..990e7842dd9 100644 --- a/dotcom-rendering/.storybook/main.ts +++ b/dotcom-rendering/.storybook/main.ts @@ -128,6 +128,12 @@ const webpackConfig = (config: Configuration) => { // Mock contributions API so getAuthHeaders does not hang on consent state in Storybook [`${path.resolve(__dirname, '../src/lib/contributions')}$`]: path.resolve(__dirname, './mocks/contributions.ts'), + + // Mock identity auth frontend to prevent Storybook components from hanging in Pending + '@guardian/identity-auth-frontend': path.resolve( + __dirname, + './mocks/identityAuthFrontend.ts', + ), }; const webpackLoaders = getLoaders('client.web'); diff --git a/dotcom-rendering/.storybook/mocks/identityAuthFrontend.ts b/dotcom-rendering/.storybook/mocks/identityAuthFrontend.ts new file mode 100644 index 00000000000..22531ab7bc8 --- /dev/null +++ b/dotcom-rendering/.storybook/mocks/identityAuthFrontend.ts @@ -0,0 +1,8 @@ +export const getIdentityAuth = () => ({ + isSignedInWithAuthState: () => + Promise.resolve({ + isAuthenticated: false, + accessToken: undefined, + idToken: undefined, + }), +}); diff --git a/dotcom-rendering/src/lib/mockRESTCalls.ts b/dotcom-rendering/src/lib/mockRESTCalls.ts index 1fad65c2f9a..70d757754e4 100644 --- a/dotcom-rendering/src/lib/mockRESTCalls.ts +++ b/dotcom-rendering/src/lib/mockRESTCalls.ts @@ -202,15 +202,14 @@ export const mockFetch: typeof global.fetch = ( url, ): return createMockResponse(200, discussion); - case /.*contributions\.(code\.dev-)?guardianapis\.com\/header/.test( - url, - ) && requestInit?.method === 'POST': - return createMockResponse(200, contributionsHeaderResponse); - // Get contributions header - case /.*contributions\.(code\.dev-)?guardianapis\.com\/header/.test( - url, - ): + // Get contributions header or local Storybook header fetches + case /.*\/header/.test(url): return createMockResponse(200, contributionsHeaderResponse); + // Catch local Storybook epic fetches + case /.*\/epic/.test(url): + return createMockResponse(200, { + data: { module: { url: '', name: 'Epic', props: {} } }, + }); // Get Ophan case /.*ophan\.theguardian\.com\/img\/.*/.test(url): return createMockResponse(200); From 4d1160702a84333268ce742178ec96ed2ac5bb01 Mon Sep 17 00:00:00 2001 From: Juarez Mota Date: Thu, 5 Mar 2026 16:01:41 +0000 Subject: [PATCH 10/19] feat: use `useIsSignedIn` hook to determine user sign-in status. --- .../src/components/TopBarSupport.importable.tsx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/dotcom-rendering/src/components/TopBarSupport.importable.tsx b/dotcom-rendering/src/components/TopBarSupport.importable.tsx index 8937e2c784c..42fd0989282 100644 --- a/dotcom-rendering/src/components/TopBarSupport.importable.tsx +++ b/dotcom-rendering/src/components/TopBarSupport.importable.tsx @@ -24,7 +24,7 @@ import { import { getOptionsHeaders } from '../lib/identity'; import { getHeader } from '../lib/sdcRequests'; import { useBetaAB } from '../lib/useAB'; -import { useAuthStatus } from '../lib/useAuthStatus'; +import { useAuthStatus, useIsSignedIn } from '../lib/useAuthStatus'; import { useCountryCode } from '../lib/useCountryCode'; import { usePageViewId } from '../lib/usePageViewId'; import { useConfig } from './ConfigContext'; @@ -59,10 +59,7 @@ const ReaderRevenueLinksRemote = ({ const [SupportHeader, setSupportHeader] = useState | null>(null); const authStatus = useAuthStatus(); - const isSignedIn = - authStatus.kind === 'Pending' - ? 'Pending' - : authStatus.kind === 'SignedIn'; + const isSignedIn = useIsSignedIn(); const { renderingTarget } = useConfig(); const abTests = useBetaAB(); From f1d4c66c9f5bbd614057964f3fd2f4c138459610 Mon Sep 17 00:00:00 2001 From: Dominik Lander Date: Thu, 5 Mar 2026 16:56:09 +0000 Subject: [PATCH 11/19] Slimify all sections and reduce font size in AB test --- ab-testing/config/abTests.ts | 6 +- .../src/components/CardHeadline.tsx | 23 ++- .../src/components/DecideContainer.tsx | 17 ++ .../components/FlexibleGeneral.stories.tsx | 12 ++ .../src/components/FlexibleGeneral.tsx | 163 ++++++++++++------ .../components/FlexibleSpecial.stories.tsx | 11 ++ .../src/components/FlexibleSpecial.tsx | 44 ++++- .../src/components/FrontSection.tsx | 89 +++++----- .../ScrollableFeature.importable.tsx | 15 +- .../components/ScrollableFeature.stories.tsx | 10 ++ .../ScrollableMedium.importable.tsx | 13 +- .../components/ScrollableMedium.stories.tsx | 11 ++ .../components/StaticMediumFour.stories.tsx | 12 ++ .../src/components/StaticMediumFour.tsx | 12 +- dotcom-rendering/src/layouts/FrontLayout.tsx | 54 ++++-- .../src/lib/SlimHomepageAbTestHelpers.ts | 31 ++++ 16 files changed, 396 insertions(+), 127 deletions(-) create mode 100644 dotcom-rendering/src/lib/SlimHomepageAbTestHelpers.ts diff --git a/ab-testing/config/abTests.ts b/ab-testing/config/abTests.ts index 8074f1eadf3..77b8be05d73 100644 --- a/ab-testing/config/abTests.ts +++ b/ab-testing/config/abTests.ts @@ -109,14 +109,14 @@ const ABTests: ABTest[] = [ { name: "fronts-and-curation-slim-homepage", description: - "Test placing the Most Viewed and Deeply Read components in the right-hand column on the homepage.", + "Test slimming content and placing Most Popular components on the right-hand side on the UK front.", owners: ["fronts.and.curation@guardian.co.uk"], status: "ON", - expirationDate: `2026-04-28`, + expirationDate: "2026-04-28", type: "server", audienceSize: 0 / 100, audienceSpace: "A", - groups: ["control", "variant"], + groups: ["control", "variant-one", "variant-two"], shouldForceMetricsCollection: false, }, { diff --git a/dotcom-rendering/src/components/CardHeadline.tsx b/dotcom-rendering/src/components/CardHeadline.tsx index 43786c45804..00c8d482cc8 100644 --- a/dotcom-rendering/src/components/CardHeadline.tsx +++ b/dotcom-rendering/src/components/CardHeadline.tsx @@ -1,6 +1,7 @@ import { css } from '@emotion/react'; import { between, + from, headlineMedium14, headlineMedium15, headlineMedium17, @@ -142,6 +143,7 @@ export enum FontFamily { export type HeadlineSize = keyof typeof fontFamilies.headlineMedium; export type ResponsiveFontSize = { + wide?: HeadlineSize; desktop: HeadlineSize; tablet?: HeadlineSize; mobile?: HeadlineSize; @@ -151,7 +153,7 @@ export type ResponsiveFontSize = { const getFontSize = (sizes: ResponsiveFontSize, family: FontFamily) => { const font = fontFamilies[family]; - const { desktop, tablet, mobileMedium, mobile } = sizes; + const { wide, desktop, tablet, mobileMedium, mobile } = sizes; return css` ${font[desktop]}; @@ -176,6 +178,13 @@ const getFontSize = (sizes: ResponsiveFontSize, family: FontFamily) => { ${font[mobileMedium]}; } `} + + ${wide && + css` + ${from.wide} { + ${font[wide]}; + } + `} `; }; @@ -204,6 +213,15 @@ export const WithLink = ({ return <>{children}; }; +/** + * headline medium 20 on desktop and headline medium 17 on tablet and mobile + */ +export const defaultFontSizes: ResponsiveFontSize = { + desktop: 'xsmall', + tablet: 'xxsmall', + mobile: 'xxsmall', +}; + export const CardHeadline = ({ headlineText, format, @@ -211,8 +229,7 @@ export const CardHeadline = ({ kickerText, showPulsingDot, hasInlineKicker, - /** headline medium 20 on desktop and headline medium 17 on tablet and mobile */ - fontSizes = { desktop: 'xsmall', tablet: 'xxsmall', mobile: 'xxsmall' }, + fontSizes = defaultFontSizes, byline, showByline, linkTo, diff --git a/dotcom-rendering/src/components/DecideContainer.tsx b/dotcom-rendering/src/components/DecideContainer.tsx index 5023f2e4474..2fa9b20109b 100644 --- a/dotcom-rendering/src/components/DecideContainer.tsx +++ b/dotcom-rendering/src/components/DecideContainer.tsx @@ -47,6 +47,7 @@ type Props = { frontId?: string; collectionId: number; containerLevel?: DCRContainerLevel; + isInSlimHomepageAbTestVariant?: boolean; }; export const DecideContainer = ({ @@ -62,6 +63,7 @@ export const DecideContainer = ({ frontId, collectionId, containerLevel, + isInSlimHomepageAbTestVariant = false, }: Props) => { switch (containerType) { case 'dynamic/fast': @@ -244,6 +246,9 @@ export const DecideContainer = ({ imageLoading={imageLoading} aspectRatio={aspectRatio} collectionId={collectionId} + isInSlimHomepageAbTestVariant={ + isInSlimHomepageAbTestVariant + } /> ); case 'flexible/general': @@ -257,6 +262,9 @@ export const DecideContainer = ({ aspectRatio={aspectRatio} containerLevel={containerLevel} collectionId={collectionId} + isInSlimHomepageAbTestVariant={ + isInSlimHomepageAbTestVariant + } /> ); case 'scrollable/small': @@ -284,6 +292,9 @@ export const DecideContainer = ({ serverTime={serverTime} aspectRatio={aspectRatio} sectionId={sectionId} + isInSlimHomepageAbTestVariant={ + isInSlimHomepageAbTestVariant + } /> ); @@ -296,6 +307,9 @@ export const DecideContainer = ({ serverTime={serverTime} imageLoading={imageLoading} aspectRatio={aspectRatio} + isInSlimHomepageAbTestVariant={ + isInSlimHomepageAbTestVariant + } /> ); case 'scrollable/feature': @@ -308,6 +322,9 @@ export const DecideContainer = ({ serverTime={serverTime} aspectRatio={aspectRatio} collectionId={collectionId} + isInSlimHomepageAbTestVariant={ + isInSlimHomepageAbTestVariant + } /> ); diff --git a/dotcom-rendering/src/components/FlexibleGeneral.stories.tsx b/dotcom-rendering/src/components/FlexibleGeneral.stories.tsx index 68d2f463cde..81dc250856c 100644 --- a/dotcom-rendering/src/components/FlexibleGeneral.stories.tsx +++ b/dotcom-rendering/src/components/FlexibleGeneral.stories.tsx @@ -160,6 +160,7 @@ const meta = { imageLoading: 'eager', aspectRatio: '5:4', collectionId: 1, + isInSlimHomepageAbTestVariant: false, }, render: ({ frontSectionTitle, ...args }) => ( @@ -193,6 +197,14 @@ export const SplashWithStandards: Story = { }, }; +export const SplashWithStandardsInSlimHomepageAbTest: Story = { + name: 'Splash with big and standard cards in the Slim Homepage AB Test', + args: { + ...SplashWithStandards.args, + isInSlimHomepageAbTestVariant: true, + }, +}; + export const SplashWithSublinks: Story = { name: 'Standard splash with sublinks', args: { diff --git a/dotcom-rendering/src/components/FlexibleGeneral.tsx b/dotcom-rendering/src/components/FlexibleGeneral.tsx index 9b09cae20d8..c4fcca3d6f9 100644 --- a/dotcom-rendering/src/components/FlexibleGeneral.tsx +++ b/dotcom-rendering/src/components/FlexibleGeneral.tsx @@ -17,7 +17,7 @@ import type { } from './Card/components/MediaWrapper'; import type { TrailTextSize } from './Card/components/TrailText'; import { UL } from './Card/components/UL'; -import type { ResponsiveFontSize } from './CardHeadline'; +import { defaultFontSizes, type ResponsiveFontSize } from './CardHeadline'; import type { Loading } from './CardPicture'; import { FeatureCard } from './FeatureCard'; import { FrontCard } from './FrontCard'; @@ -35,6 +35,7 @@ type Props = { collectionId: number; /** Passed through to cards to enable tag page storyline section specific rendering */ isStorylines?: boolean; + isInSlimHomepageAbTestVariant?: boolean; }; type RowLayout = 'oneCardHalfWidth' | 'oneCardFullWidth' | 'twoCard'; @@ -90,6 +91,7 @@ type ImmersiveCardLayoutProps = { imageLoading: Loading; collectionId: number; isStorylines?: boolean; + isInSlimHomepageAbTestVariant?: boolean; }; /** @@ -105,47 +107,57 @@ const ImmersiveCardLayout = ({ imageLoading, collectionId, isStorylines, -}: ImmersiveCardLayoutProps) => ( -
    -
  • - -
  • -
-); + isInSlimHomepageAbTestVariant, +}: ImmersiveCardLayoutProps) => { + const headlineSizes = { desktop: 'medium', tablet: 'small' } as const; + + return ( +
    +
  • + +
  • +
+ ); +}; type BoostedSplashProperties = { headlineSizes: ResponsiveFontSize; @@ -170,13 +182,18 @@ const decideSplashCardProperties = ( useLargerHeadlineSizeDesktop: boolean, avatarUrl: boolean, isStorylines?: boolean, + isInSlimHomepageAbTestVariant?: boolean, ): BoostedSplashProperties => { switch (boostLevel) { // The default boost level is equal to no boost. It is the same as the default card layout. case 'default': return { headlineSizes: { - desktop: useLargerHeadlineSizeDesktop ? 'large' : 'medium', + desktop: + useLargerHeadlineSizeDesktop && + !isInSlimHomepageAbTestVariant + ? 'large' + : 'medium', tablet: 'medium', mobile: 'medium', }, @@ -190,13 +207,13 @@ const decideSplashCardProperties = ( subtitleSize: 'medium', }; case 'boost': - const boostSupportingContentAlignment: Alignment = - isStorylines || supportingContentLength < 4 - ? 'vertical' - : 'horizontal'; - return { headlineSizes: { + wide: + !isInSlimHomepageAbTestVariant && + useLargerHeadlineSizeDesktop + ? 'xlarge' + : 'large', desktop: useLargerHeadlineSizeDesktop ? 'xlarge' : 'large', tablet: 'large', mobile: 'large', @@ -204,7 +221,10 @@ const decideSplashCardProperties = ( mediaPositionOnDesktop: 'right', mediaPositionOnMobile: mediaCard ? 'top' : 'bottom', mediaSize: avatarUrl ? 'large' : 'xlarge', - supportingContentAlignment: boostSupportingContentAlignment, + supportingContentAlignment: + isStorylines || supportingContentLength < 4 + ? 'vertical' + : 'horizontal', liveUpdatesAlignment: 'vertical', trailTextSize: 'regular', subtitleSize: 'medium', @@ -212,6 +232,11 @@ const decideSplashCardProperties = ( case 'megaboost': return { headlineSizes: { + wide: + !isInSlimHomepageAbTestVariant && + useLargerHeadlineSizeDesktop + ? 'xxlarge' + : 'xlarge', desktop: useLargerHeadlineSizeDesktop ? 'xxlarge' : 'xlarge', @@ -229,6 +254,11 @@ const decideSplashCardProperties = ( case 'gigaboost': return { headlineSizes: { + wide: isInSlimHomepageAbTestVariant + ? 'xlarge' + : useLargerHeadlineSizeDesktop + ? 'xxxlarge' + : 'xxlarge', desktop: useLargerHeadlineSizeDesktop ? 'xxxlarge' : 'xxlarge', @@ -257,6 +287,7 @@ type SplashCardLayoutProps = { containerLevel: DCRContainerLevel; collectionId: number; isStorylines?: boolean; + isInSlimHomepageAbTestVariant?: boolean; }; const SplashCardLayout = ({ @@ -270,6 +301,7 @@ const SplashCardLayout = ({ containerLevel, collectionId, isStorylines, + isInSlimHomepageAbTestVariant, }: SplashCardLayoutProps) => { const card = cards[0]; if (!card) return null; @@ -283,6 +315,7 @@ const SplashCardLayout = ({ serverTime={serverTime} imageLoading={imageLoading} collectionId={collectionId} + isInSlimHomepageAbTestVariant={isInSlimHomepageAbTestVariant} /> ); } @@ -309,6 +342,7 @@ const SplashCardLayout = ({ useLargerHeadlineSizeDesktop, !!card.avatarUrl, isStorylines, + isInSlimHomepageAbTestVariant, ); return ( @@ -355,7 +389,7 @@ const SplashCardLayout = ({ subtitleSize={subtitleSize} headlinePosition={card.showLivePlayable ? 'outer' : 'inner'} isStorylines={isStorylines} - starRatingSize={'medium'} + starRatingSize="medium" /> @@ -368,6 +402,7 @@ type BoostedCardProperties = { liveUpdatesPosition: Position; supportingContentAlignment: Alignment; subtitleSize: SubtitleSize; + isInSlimHomepageAbTestVariant?: boolean; }; /** @@ -378,11 +413,13 @@ const decideCardProperties = ( supportingContentLength: number, boostLevel: Omit = 'boost', avatarUrl?: string, + isInSlimHomepageAbTestVariant = false, ): BoostedCardProperties => { switch (boostLevel) { case 'megaboost': return { headlineSizes: { + wide: isInSlimHomepageAbTestVariant ? 'small' : 'medium', desktop: 'medium', tablet: 'small', mobile: 'medium', @@ -422,6 +459,7 @@ type FullWidthCardLayoutProps = { containerLevel: DCRContainerLevel; collectionId: number; isStorylines?: boolean; + isInSlimHomepageAbTestVariant?: boolean; }; const FullWidthCardLayout = ({ @@ -436,6 +474,7 @@ const FullWidthCardLayout = ({ containerLevel, collectionId, isStorylines, + isInSlimHomepageAbTestVariant, }: FullWidthCardLayoutProps) => { const card = cards[0]; if (!card) return null; @@ -450,6 +489,7 @@ const FullWidthCardLayout = ({ card.supportingContent?.length ?? 0, card.boostLevel, card.avatarUrl, + isInSlimHomepageAbTestVariant, ); const shouldShowImmersive = card.isImmersive; @@ -512,7 +552,7 @@ const FullWidthCardLayout = ({ showKickerImage={card.format.design === ArticleDesign.Audio} subtitleSize={subtitleSize} isStorylines={isStorylines} - starRatingSize={'medium'} + starRatingSize="medium" /> @@ -531,6 +571,7 @@ type HalfWidthCardLayoutProps = { isLastRow: boolean; containerLevel: DCRContainerLevel; isStorylines?: boolean; + isInSlimHomepageAbTestVariant?: boolean; }; const HalfWidthCardLayout = ({ @@ -545,6 +586,7 @@ const HalfWidthCardLayout = ({ isLastRow, containerLevel, isStorylines, + isInSlimHomepageAbTestVariant, }: HalfWidthCardLayoutProps) => { if (cards.length === 0) return null; @@ -597,7 +639,14 @@ const HalfWidthCardLayout = ({ (containerLevel !== 'Primary' && cardIndex > 0) } trailText={undefined} - headlineSizes={undefined} + headlineSizes={ + isInSlimHomepageAbTestVariant + ? { + ...defaultFontSizes, + wide: defaultFontSizes.tablet, + } + : defaultFontSizes + } canPlayInline={false} isStorylines={isStorylines} /> @@ -618,6 +667,7 @@ export const FlexibleGeneral = ({ containerLevel = 'Primary', collectionId, isStorylines = false, + isInSlimHomepageAbTestVariant = false, }: Props) => { const splash = [...groupedTrails.splash].slice(0, 1).map((snap) => ({ ...snap, @@ -647,6 +697,9 @@ export const FlexibleGeneral = ({ containerLevel={containerLevel} collectionId={collectionId} isStorylines={isStorylines} + isInSlimHomepageAbTestVariant={ + isInSlimHomepageAbTestVariant + } /> )} {groupedCards.map((row, i) => { @@ -666,6 +719,9 @@ export const FlexibleGeneral = ({ containerLevel={containerLevel} collectionId={collectionId} isStorylines={isStorylines} + isInSlimHomepageAbTestVariant={ + isInSlimHomepageAbTestVariant + } /> ); @@ -686,6 +742,9 @@ export const FlexibleGeneral = ({ isLastRow={i === groupedCards.length - 1} containerLevel={containerLevel} isStorylines={isStorylines} + isInSlimHomepageAbTestVariant={ + isInSlimHomepageAbTestVariant + } /> ); } diff --git a/dotcom-rendering/src/components/FlexibleSpecial.stories.tsx b/dotcom-rendering/src/components/FlexibleSpecial.stories.tsx index b69daa232d7..44530a868ef 100644 --- a/dotcom-rendering/src/components/FlexibleSpecial.stories.tsx +++ b/dotcom-rendering/src/components/FlexibleSpecial.stories.tsx @@ -88,6 +88,7 @@ const meta = { imageLoading: 'eager', aspectRatio: '5:4', frontSectionTitle: 'Flexible special', + isInSlimHomepageAbTestVariant: false, }, render: ({ frontSectionTitle, ...args }) => ( @@ -160,6 +164,13 @@ export const Five: Story = { collectionId: 1, }, }; +export const FiveSlimHomepageAbTest: Story = { + name: 'With one splash card and four standard cards in the Slim Homepage AB Test', + args: { + ...Five.args, + isInSlimHomepageAbTestVariant: true, + }, +}; export const OpinionStandardCards: Story = { name: 'With one splash card and two standard opinion cards', diff --git a/dotcom-rendering/src/components/FlexibleSpecial.tsx b/dotcom-rendering/src/components/FlexibleSpecial.tsx index c8be889350e..adc431514ed 100644 --- a/dotcom-rendering/src/components/FlexibleSpecial.tsx +++ b/dotcom-rendering/src/components/FlexibleSpecial.tsx @@ -16,7 +16,7 @@ import type { } from './Card/components/MediaWrapper'; import type { TrailTextSize } from './Card/components/TrailText'; import { UL } from './Card/components/UL'; -import type { ResponsiveFontSize } from './CardHeadline'; +import { defaultFontSizes, type ResponsiveFontSize } from './CardHeadline'; import type { Loading } from './CardPicture'; import { FrontCard } from './FrontCard'; import type { SubtitleSize } from './SelfHostedVideoPlayer'; @@ -31,6 +31,7 @@ type Props = { aspectRatio: AspectRatio; containerLevel?: DCRContainerLevel; collectionId: number; + isInSlimHomepageAbTestVariant?: boolean; }; type BoostProperties = { @@ -54,6 +55,7 @@ const determineCardProperties = ( mediaCard: boolean, imageSuppressed: boolean, hasLiveUpdates: boolean, + isInSlimHomepageAbTestVariant: boolean, ): BoostProperties => { const shouldDisplaySublinksHorizontally = supportingContentLength >= 3 || hasLiveUpdates; @@ -63,6 +65,11 @@ const determineCardProperties = ( case 'default': return { headlineSizes: { + wide: isInSlimHomepageAbTestVariant + ? 'large' + : imageSuppressed + ? 'xxlarge' + : 'xlarge', desktop: imageSuppressed ? 'xxlarge' : 'xlarge', tablet: 'large', mobile: 'medium', @@ -80,6 +87,11 @@ const determineCardProperties = ( case 'boost': return { headlineSizes: { + wide: isInSlimHomepageAbTestVariant + ? 'xlarge' + : imageSuppressed + ? 'xxxlarge' + : 'xxlarge', desktop: imageSuppressed ? 'xxxlarge' : 'xxlarge', tablet: 'xlarge', mobile: 'large', @@ -97,6 +109,11 @@ const determineCardProperties = ( case 'megaboost': return { headlineSizes: { + wide: isInSlimHomepageAbTestVariant + ? 'xlarge' + : imageSuppressed + ? 'xxxlarge' + : 'xxlarge', desktop: imageSuppressed ? 'xxxlarge' : 'xxlarge', tablet: 'xlarge', mobile: 'xlarge', @@ -112,6 +129,9 @@ const determineCardProperties = ( case 'gigaboost': return { headlineSizes: { + wide: isInSlimHomepageAbTestVariant + ? 'xxlarge' + : 'xxxlarge', desktop: 'xxxlarge', tablet: 'xxlarge', mobile: 'xxlarge', @@ -138,6 +158,7 @@ type OneCardLayoutProps = { isFirstRow: boolean; containerLevel: DCRContainerLevel; isSplashCard?: boolean; + isInSlimHomepageAbTestVariant?: boolean; }; export const OneCardLayout = ({ @@ -151,6 +172,7 @@ export const OneCardLayout = ({ isFirstRow, containerLevel, isSplashCard, + isInSlimHomepageAbTestVariant = false, }: OneCardLayoutProps) => { const card = cards[0]; if (!card) return null; @@ -170,6 +192,7 @@ export const OneCardLayout = ({ isMediaCard(card.format), !card.image, card.showLivePlayable, + isInSlimHomepageAbTestVariant, ); return ( @@ -233,6 +256,7 @@ type TwoOrFourCardLayoutProps = { aspectRatio: AspectRatio; isFirstRow: boolean; containerLevel: DCRContainerLevel; + isInSlimHomepageAbTestVariant?: boolean; }; const TwoOrFourCardLayout = ({ @@ -245,6 +269,7 @@ const TwoOrFourCardLayout = ({ aspectRatio, isFirstRow, containerLevel, + isInSlimHomepageAbTestVariant, }: TwoOrFourCardLayoutProps) => { if (cards.length === 0) return null; const hasTwoOrFewerCards = cards.length <= 2; @@ -273,7 +298,14 @@ const TwoOrFourCardLayout = ({ isMediaCard(card.format) || !!card.isNewsletter, )} mediaPositionOnMobile="left" - headlineSizes={undefined} + headlineSizes={ + isInSlimHomepageAbTestVariant + ? { + ...defaultFontSizes, + wide: defaultFontSizes.tablet, + } + : defaultFontSizes + } /* we don't want to support sublinks on standard cards here so we hard code to undefined */ supportingContent={undefined} mediaSize="small" @@ -304,6 +336,7 @@ export const FlexibleSpecial = ({ aspectRatio, containerLevel = 'Primary', collectionId, + isInSlimHomepageAbTestVariant = false, }: Props) => { const snaps = [...groupedTrails.snap].slice(0, 1).map((snap) => ({ ...snap, @@ -332,6 +365,9 @@ export const FlexibleSpecial = ({ isLastRow={splash.length === 0 && cards.length === 0} containerLevel={containerLevel} isSplashCard={false} + isInSlimHomepageAbTestVariant={ + isInSlimHomepageAbTestVariant + } /> )} {isNonEmptyArray(splash) && ( @@ -346,6 +382,9 @@ export const FlexibleSpecial = ({ isFirstRow={!isNonEmptyArray(snaps)} containerLevel={containerLevel} isSplashCard={true} + isInSlimHomepageAbTestVariant={ + isInSlimHomepageAbTestVariant + } /> )} @@ -358,6 +397,7 @@ export const FlexibleSpecial = ({ aspectRatio={aspectRatio} isFirstRow={!isNonEmptyArray(snaps) && !isNonEmptyArray(splash)} containerLevel={containerLevel} + isInSlimHomepageAbTestVariant={isInSlimHomepageAbTestVariant} /> ); diff --git a/dotcom-rendering/src/components/FrontSection.tsx b/dotcom-rendering/src/components/FrontSection.tsx index fcea812ff74..0b8ad72763a 100644 --- a/dotcom-rendering/src/components/FrontSection.tsx +++ b/dotcom-rendering/src/components/FrontSection.tsx @@ -97,7 +97,8 @@ type Props = { * The Slim Homepage AB test requires some sections to have reduced width so that * the Most Popular Front Right component can be placed on the right-hand side. */ - slimifySectionForAbTest?: boolean; + slimifySectionForSlimHomepageAbTest?: boolean; + showRightContentForSlimHomepageAbTest?: boolean; discussionApiUrl: string; collectionBranding?: CollectionBranding; isTagPage?: boolean; @@ -650,7 +651,8 @@ export const FrontSection = ({ isOnPaidContentFront, targetedTerritory, hasPageSkin = false, - slimifySectionForAbTest = false, + slimifySectionForSlimHomepageAbTest = false, + showRightContentForSlimHomepageAbTest = false, discussionApiUrl, collectionBranding, isTagPage = false, @@ -724,10 +726,8 @@ export const FrontSection = ({ title, showSectionColours, ), - // To reduce the width of the border line between Features - // and More features in the Slim Homepage AB test. - slimifySectionForAbTest && - sectionId === 'more-features' && + slimifySectionForSlimHomepageAbTest && + containerLevel === 'Secondary' && css` ${from.wide} { grid-column: 2 / 14; @@ -827,14 +827,15 @@ export const FrontSection = ({
- {slimifySectionForAbTest && sectionId === 'news' && ( -
- - - -
- )} + {showRightContentForSlimHomepageAbTest && + sectionId === 'news' && ( +
+ + + +
+ )} - {slimifySectionForAbTest && sectionId === 'features' && ( -
- - - -
- )} + {showRightContentForSlimHomepageAbTest && + sectionId === 'features' && ( +
+ + + +
+ )}
{ const isBelowTabletBreakpoint = useMatchMedia( removeMediaRulePrefix(until.tablet), ); + const headlineSizes = { + desktop: 'xsmall', + tablet: 'xxsmall', + mobile: 'xsmall', + } as const; + return ( ( @@ -100,6 +104,12 @@ type Story = StoryObj; export const Default = {}; +export const WithSlimHomepageAbTest = { + args: { + isInSlimHomepageAbTestVariant: true, + }, +} satisfies Story; + export const Media = { args: { trails: [galleryTrails[0], galleryTrails[1], audioTrails[0]], diff --git a/dotcom-rendering/src/components/ScrollableMedium.importable.tsx b/dotcom-rendering/src/components/ScrollableMedium.importable.tsx index 8946eb185f0..e26547e360c 100644 --- a/dotcom-rendering/src/components/ScrollableMedium.importable.tsx +++ b/dotcom-rendering/src/components/ScrollableMedium.importable.tsx @@ -17,6 +17,7 @@ type Props = { imageLoading: 'lazy' | 'eager'; aspectRatio: AspectRatio; sectionId: string; + isInSlimHomepageAbTestVariant?: boolean; }; /** @@ -34,6 +35,7 @@ export const ScrollableMedium = ({ showAge, aspectRatio, sectionId, + isInSlimHomepageAbTestVariant, }: Props) => { const isBelowTabletBreakpoint = useMatchMedia( removeMediaRulePrefix(until.tablet), @@ -52,6 +54,11 @@ export const ScrollableMedium = ({ ? 'top' : 'bottom'; + const headlineSizes = { + desktop: 'xsmall', + tablet: 'xxsmall', + } as const; + return ( ( @@ -55,6 +59,13 @@ export const WithFourCards = { }, } satisfies Story; +export const WithFourCardsInSlimHomepageAbTest = { + args: { + ...WithFourCards.args, + isInSlimHomepageAbTestVariant: true, + }, +} satisfies Story; + export const WithThreeCards = { args: { trails: trails.slice(0, 3), diff --git a/dotcom-rendering/src/components/StaticMediumFour.stories.tsx b/dotcom-rendering/src/components/StaticMediumFour.stories.tsx index da2d2636497..78beb99d01b 100644 --- a/dotcom-rendering/src/components/StaticMediumFour.stories.tsx +++ b/dotcom-rendering/src/components/StaticMediumFour.stories.tsx @@ -29,6 +29,7 @@ const meta = { showAge: true, imageLoading: 'eager', aspectRatio: '5:4', + isInSlimHomepageAbTestVariant: false, }, render: (args) => ( @@ -53,6 +57,14 @@ export const Four = { }, }; +export const FourSlimHomepageAbTest = { + name: 'With Four Cards in Slim Homepage AB Test', + args: { + trails: trails.slice(0, 4), + isInSlimHomepageAbTestVariant: true, + }, +}; + export const Three: Story = { name: 'With Three Cards', args: { diff --git a/dotcom-rendering/src/components/StaticMediumFour.tsx b/dotcom-rendering/src/components/StaticMediumFour.tsx index 470f607208d..600ec3b9696 100644 --- a/dotcom-rendering/src/components/StaticMediumFour.tsx +++ b/dotcom-rendering/src/components/StaticMediumFour.tsx @@ -9,6 +9,7 @@ import type { import { LI } from './Card/components/LI'; import type { MediaPositionType } from './Card/components/MediaWrapper'; import { UL } from './Card/components/UL'; +import { defaultFontSizes } from './CardHeadline'; import type { Loading } from './CardPicture'; import { FrontCard } from './FrontCard'; @@ -32,6 +33,7 @@ type Props = { showImage?: boolean; aspectRatio: AspectRatio; containerLevel?: DCRContainerLevel; + isInSlimHomepageAbTestVariant?: boolean; }; export const StaticMediumFour = ({ @@ -43,6 +45,7 @@ export const StaticMediumFour = ({ showImage = true, aspectRatio, containerLevel = 'Primary', + isInSlimHomepageAbTestVariant = false, }: Props) => { const cards = trails.slice(0, 4); @@ -70,7 +73,14 @@ export const StaticMediumFour = ({ !!card.isNewsletter, )} mediaPositionOnMobile="left" - headlineSizes={undefined} + headlineSizes={ + isInSlimHomepageAbTestVariant + ? { + ...defaultFontSizes, + wide: defaultFontSizes.tablet, + } + : defaultFontSizes + } /* we don't want to support sublinks on standard cards here so we hard code to undefined */ supportingContent={undefined} mediaSize="medium" diff --git a/dotcom-rendering/src/layouts/FrontLayout.tsx b/dotcom-rendering/src/layouts/FrontLayout.tsx index 7325e7e5bf1..fb8d851f238 100644 --- a/dotcom-rendering/src/layouts/FrontLayout.tsx +++ b/dotcom-rendering/src/layouts/FrontLayout.tsx @@ -40,6 +40,7 @@ import { } from '../lib/getFrontsAdPositions'; import { hideAge } from '../lib/hideAge'; import { ophanComponentId } from '../lib/ophan-helpers'; +import { doesPageQualifyForSlimHomepageAbTest } from '../lib/SlimHomepageAbTestHelpers'; import { useBetaAB } from '../lib/useAB'; import type { NavType } from '../model/extract-nav'; import { palette as schemePalette } from '../palette'; @@ -82,15 +83,16 @@ const isToggleable = ( * The show/hide button would be covered by the MostPopularFrontRight component * in the variant of the Slim Homepage AB test. */ - isTargetedContainerInSlimHomepageAbTest: boolean, + isShowingRightContentForSlimHomepageAbTest: boolean, ) => { + if (isShowingRightContentForSlimHomepageAbTest) return; + if (isNetworkFront) { return ( collection.displayName.toLowerCase() !== 'headlines' && !isNavList(collection) && !isHighlights(collection) && - !isLabs(collection) && - !isTargetedContainerInSlimHomepageAbTest + !isLabs(collection) ); } @@ -152,18 +154,33 @@ export const FrontLayout = ({ front, NAV }: Props) => { /** * The Slim Homepage AB test only runs on /uk and on screen widths >=1300px. - * In the variant of this test a Most Popular component is added to the right-hand side of the page. - * Page skins require slim content and is incompatible with this test. We do not run this test - * on pages where there is a page skin (a page skin takes precedence). + * In variant one and two of this test, the content is slimmed down. + * In variant two of this test, a Most Popular component is inserted into the right-hand side of the page. + * Page skins require slim content and is incompatible with this test, so we do not run + * this test on pages where there is a page skin (a page skin takes precedence). */ - const isInSlimHomepageAbTestVariant = - (pageId === 'uk' && - !hasPageSkin && + const pageQualifiesForSlimHomepageAbTest = + doesPageQualifyForSlimHomepageAbTest( + front.pressedPage.collections, + pageId, + hasPageSkin, + ); + const isInSlimHomepageAbTestVariantOne = + (pageQualifiesForSlimHomepageAbTest && abTests?.isUserInTestGroup( 'fronts-and-curation-slim-homepage', - 'variant', + 'variant-one', )) ?? false; + const isInSlimHomepageAbTestVariantTwo = + (pageQualifiesForSlimHomepageAbTest && + abTests?.isUserInTestGroup( + 'fronts-and-curation-slim-homepage', + 'variant-two', + )) ?? + false; + const isInEitherSlimHomepageAbTestVariant = + isInSlimHomepageAbTestVariantOne || isInSlimHomepageAbTestVariantTwo; const fallbackAspectRatio = (collectionType: DCRContainerType) => { switch (collectionType) { @@ -309,11 +326,6 @@ export const FrontLayout = ({ front, NAV }: Props) => { * We only shrink the content of certain sections to place the Most Popular * content on the right-hand side. Other sections remain full-width. */ - const isTargetedContainerInSlimHomepageAbTest = - isInSlimHomepageAbTestVariant && - (collection.displayName === 'News' || - collection.displayName === 'Features' || - collection.displayName === 'More features'); if (collection.collectionType === 'scrollable/highlights') { // Highlights are rendered in the Masthead component @@ -494,7 +506,7 @@ export const FrontLayout = ({ front, NAV }: Props) => { index, collection, front.isNetworkFront, - isTargetedContainerInSlimHomepageAbTest, + isInSlimHomepageAbTestVariantTwo, )} leftContent={decideLeftContent( front, @@ -534,8 +546,11 @@ export const FrontLayout = ({ front, NAV }: Props) => { index, )} isLabs={isLabs(collection)} - slimifySectionForAbTest={ - isTargetedContainerInSlimHomepageAbTest + slimifySectionForSlimHomepageAbTest={ + isInEitherSlimHomepageAbTestVariant + } + showRightContentForSlimHomepageAbTest={ + isInSlimHomepageAbTestVariantTwo } mostViewed={front.mostViewed} deeplyRead={front.deeplyRead} @@ -563,6 +578,9 @@ export const FrontLayout = ({ front, NAV }: Props) => { sectionId={ophanName} collectionId={index + 1} containerLevel={collection.containerLevel} + isInSlimHomepageAbTestVariant={ + isInEitherSlimHomepageAbTestVariant + } /> diff --git a/dotcom-rendering/src/lib/SlimHomepageAbTestHelpers.ts b/dotcom-rendering/src/lib/SlimHomepageAbTestHelpers.ts new file mode 100644 index 00000000000..9b057e6a2de --- /dev/null +++ b/dotcom-rendering/src/lib/SlimHomepageAbTestHelpers.ts @@ -0,0 +1,31 @@ +import type { DCRCollectionType } from '../types/front'; + +const requiredCollectionsForTest = ['news', 'features', 'more features']; + +const hasRequiredSlimHomepageAbTestCollections = ( + collections: DCRCollectionType[], +): boolean => { + return requiredCollectionsForTest.every((displayName) => + collections + .map((collection) => collection.displayName.toLowerCase()) + .includes(displayName), + ); +}; + +export const doesPageQualifyForSlimHomepageAbTest = ( + collections: DCRCollectionType[], + pageId: string, + hasPageSkin: boolean, +): boolean => { + const isFirstCollectionNews = + collections[0]?.displayName.toLowerCase() === 'news'; + const hasRequiredCollections = + hasRequiredSlimHomepageAbTestCollections(collections); + + return ( + pageId === 'uk' && + !hasPageSkin && + isFirstCollectionNews && + hasRequiredCollections + ); +}; From b84c8b8a325bdb4182e89226c8157a32221dc420 Mon Sep 17 00:00:00 2001 From: Dominik Lander Date: Thu, 5 Mar 2026 16:59:01 +0000 Subject: [PATCH 12/19] Temporarily comment out an AB test requirement for testing --- .../src/lib/SlimHomepageAbTestHelpers.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/dotcom-rendering/src/lib/SlimHomepageAbTestHelpers.ts b/dotcom-rendering/src/lib/SlimHomepageAbTestHelpers.ts index 9b057e6a2de..020f416dd05 100644 --- a/dotcom-rendering/src/lib/SlimHomepageAbTestHelpers.ts +++ b/dotcom-rendering/src/lib/SlimHomepageAbTestHelpers.ts @@ -17,15 +17,21 @@ export const doesPageQualifyForSlimHomepageAbTest = ( pageId: string, hasPageSkin: boolean, ): boolean => { - const isFirstCollectionNews = - collections[0]?.displayName.toLowerCase() === 'news'; + /** + * This is temporarily commmented out so that we can review this in a 0% test. + * There is currently a container above News on the UK front, which means the test won't run. + * We will only start this test once News is again the top container, but in the meantime, we + * would like to be able to review this test. + */ + // const isFirstCollectionNews = + // collections[0]?.displayName.toLowerCase() === 'news'; const hasRequiredCollections = hasRequiredSlimHomepageAbTestCollections(collections); return ( pageId === 'uk' && !hasPageSkin && - isFirstCollectionNews && + // isFirstCollectionNews && hasRequiredCollections ); }; From 8f2ad68d57d87b57c06dfa2cd649add90db3b5f0 Mon Sep 17 00:00:00 2001 From: Dominik Lander Date: Fri, 6 Mar 2026 11:28:31 +0000 Subject: [PATCH 13/19] Put sublinks in correct place for AB test --- dotcom-rendering/src/components/Card/Card.tsx | 22 ++++++++++++++++++- .../src/components/FlexibleGeneral.tsx | 9 ++++++++ .../src/components/FlexibleSpecial.tsx | 6 +++++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/dotcom-rendering/src/components/Card/Card.tsx b/dotcom-rendering/src/components/Card/Card.tsx index 87b52c2ee8a..ae056cf6e9b 100644 --- a/dotcom-rendering/src/components/Card/Card.tsx +++ b/dotcom-rendering/src/components/Card/Card.tsx @@ -167,6 +167,7 @@ export type Props = { headlinePosition?: 'inner' | 'outer'; isStorylines?: boolean; starRatingSize?: RatingSizeType; + isInSlimHomepageAbTestVariant?: boolean; }; const waveformWrapper = ( @@ -406,6 +407,7 @@ export const Card = ({ isStorylines = false, starRatingSize = 'small', articleMedia, + isInSlimHomepageAbTestVariant, }: Props) => { const hasSublinks = supportingContent && supportingContent.length > 0; const sublinkPosition = decideSublinkPosition( @@ -701,6 +703,14 @@ export const Card = ({ return ; } + if (isInSlimHomepageAbTestVariant) { + return ( + + + + ); + } + return ( @@ -712,7 +722,7 @@ export const Card = ({ if (!hasSublinks) return null; if (sublinkPosition !== 'inner') return null; - return ( + const Sublinks = () => ( {isStorylines ? ( ); + + if (isInSlimHomepageAbTestVariant) { + return ( + + + + ); + } + + return ; }; const determinePadContent = ( diff --git a/dotcom-rendering/src/components/FlexibleGeneral.tsx b/dotcom-rendering/src/components/FlexibleGeneral.tsx index c4fcca3d6f9..2b292be87be 100644 --- a/dotcom-rendering/src/components/FlexibleGeneral.tsx +++ b/dotcom-rendering/src/components/FlexibleGeneral.tsx @@ -390,6 +390,9 @@ const SplashCardLayout = ({ headlinePosition={card.showLivePlayable ? 'outer' : 'inner'} isStorylines={isStorylines} starRatingSize="medium" + isInSlimHomepageAbTestVariant={ + isInSlimHomepageAbTestVariant + } /> @@ -553,6 +556,9 @@ const FullWidthCardLayout = ({ subtitleSize={subtitleSize} isStorylines={isStorylines} starRatingSize="medium" + isInSlimHomepageAbTestVariant={ + isInSlimHomepageAbTestVariant + } /> @@ -649,6 +655,9 @@ const HalfWidthCardLayout = ({ } canPlayInline={false} isStorylines={isStorylines} + isInSlimHomepageAbTestVariant={ + isInSlimHomepageAbTestVariant + } /> ); diff --git a/dotcom-rendering/src/components/FlexibleSpecial.tsx b/dotcom-rendering/src/components/FlexibleSpecial.tsx index adc431514ed..0841527cc4f 100644 --- a/dotcom-rendering/src/components/FlexibleSpecial.tsx +++ b/dotcom-rendering/src/components/FlexibleSpecial.tsx @@ -229,6 +229,9 @@ export const OneCardLayout = ({ headlinePosition={isSplashCard ? 'outer' : 'inner'} subtitleSize={subtitleSize} starRatingSize="medium" + isInSlimHomepageAbTestVariant={ + isInSlimHomepageAbTestVariant + } /> @@ -319,6 +322,9 @@ const TwoOrFourCardLayout = ({ !isMediaCard(card.format)) } canPlayInline={false} + isInSlimHomepageAbTestVariant={ + isInSlimHomepageAbTestVariant + } /> ); From 5d70402625bd941570bd539e3083c68f241bb992 Mon Sep 17 00:00:00 2001 From: Tom Forbes Date: Fri, 6 Mar 2026 08:24:07 +0000 Subject: [PATCH 14/19] WIP --- .../src/components/TopBarSupport.importable.tsx | 8 ++++++-- dotcom-rendering/src/lib/contributions.ts | 8 ++++++-- dotcom-rendering/src/lib/sdcRequests.ts | 3 ++- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/dotcom-rendering/src/components/TopBarSupport.importable.tsx b/dotcom-rendering/src/components/TopBarSupport.importable.tsx index c5ff084cc00..26171162898 100644 --- a/dotcom-rendering/src/components/TopBarSupport.importable.tsx +++ b/dotcom-rendering/src/components/TopBarSupport.importable.tsx @@ -5,7 +5,6 @@ import { css } from '@emotion/react'; import { getCookie, isUndefined } from '@guardian/libs'; import type { ComponentEvent } from '@guardian/ophan-tracker-js'; -import { getHeader } from '@guardian/support-dotcom-components'; import type { HeaderPayload, ModuleData, @@ -18,9 +17,11 @@ import type { import { useEffect, useState } from 'react'; import { submitComponentEvent } from '../client/ophan/ophan'; import { + getAuthHeaders, getPurchaseInfo, shouldHideSupportMessaging, } from '../lib/contributions'; +import { getHeader } from '../lib/sdcRequests'; import { useBetaAB } from '../lib/useAB'; import { useIsSignedIn } from '../lib/useAuthStatus'; import { useCountryCode } from '../lib/useCountryCode'; @@ -90,7 +91,10 @@ const ReaderRevenueLinksRemote = ({ }, }; - getHeader(contributionsServiceUrl, requestData) + getAuthHeaders() + .then((headers) => + getHeader(contributionsServiceUrl, requestData, headers), + ) .then((response: ModuleDataResponse) => { if (!response.data) { return null; diff --git a/dotcom-rendering/src/lib/contributions.ts b/dotcom-rendering/src/lib/contributions.ts index f5a31632c69..1bb2d8b671b 100644 --- a/dotcom-rendering/src/lib/contributions.ts +++ b/dotcom-rendering/src/lib/contributions.ts @@ -227,10 +227,14 @@ export const getPurchaseInfo = (): PurchaseInfo => { return purchaseInfo; }; -const hasCanTargetConsent = (): Promise => - onConsent() +const hasCanTargetConsent = (): Promise => { + if (getCookie({ name: 'gu-cmp-disabled', shouldMemoize: true })) { + return Promise.resolve(true); + } + return onConsent() .then(({ canTarget }: ConsentState) => canTarget) .catch(() => false); +}; // Returns Auth headers only if user has targeting consent and is signed in export const getAuthHeaders = async (): Promise => { diff --git a/dotcom-rendering/src/lib/sdcRequests.ts b/dotcom-rendering/src/lib/sdcRequests.ts index 36656c2b8d2..e2d9ff9d4d6 100644 --- a/dotcom-rendering/src/lib/sdcRequests.ts +++ b/dotcom-rendering/src/lib/sdcRequests.ts @@ -112,5 +112,6 @@ export const getGutterLiveblog = ( export const getHeader = ( baseUrl: string, payload: HeaderPayload, + headers?: HeadersInit, ): Promise> => - getModuleData('header', baseUrl, payload); + getModuleData('header', baseUrl, payload, headers); From e0928e69d739c7798fbcce867834db78fcda9e71 Mon Sep 17 00:00:00 2001 From: Charlotte Emms <43961396+cemms1@users.noreply.github.com> Date: Mon, 9 Mar 2026 16:40:56 +0000 Subject: [PATCH 15/19] Labs palette updates for front section (#15491) * fixes issue of card and section background being transparent for Labs containers which causes bug on fronts with page skins applied * resolves TODO comments in the containerOverrides for Branded content & uses inherit rather than a named variable colour in most scenarios --- .../src/components/ContainerOverrides.tsx | 85 ++++++++++--------- dotcom-rendering/src/paletteDeclarations.ts | 16 ---- 2 files changed, 44 insertions(+), 57 deletions(-) diff --git a/dotcom-rendering/src/components/ContainerOverrides.tsx b/dotcom-rendering/src/components/ContainerOverrides.tsx index d8d18e05601..aac3b18be2d 100644 --- a/dotcom-rendering/src/components/ContainerOverrides.tsx +++ b/dotcom-rendering/src/components/ContainerOverrides.tsx @@ -28,9 +28,8 @@ const cardHeadlineLight: ContainerFunction = ( return sourcePalette.neutral[93]; case 'SpecialReportAltPalette': return sourcePalette.specialReportAlt[100]; - // TODO: @commercial-dev to update to neutral[7] when launching Redesigned Labs Containers case 'Branded': - return sourcePalette.neutral[20]; + return 'inherit'; } }; const cardHeadlineDark: ContainerFunction = ( @@ -56,7 +55,7 @@ const cardHeadlineDark: ContainerFunction = ( case 'SpecialReportAltPalette': return sourcePalette.specialReportAlt[700]; case 'Branded': - return sourcePalette.neutral[86]; + return 'inherit'; } }; @@ -83,8 +82,7 @@ const cardTrailTextLight: ContainerFunction = ( case 'SpecialReportAltPalette': return sourcePalette.specialReportAlt[100]; case 'Branded': - // TODO: @commercial-dev to update to palette('--card-trail-text') when launching Redesigned Labs Containers - return sourcePalette.neutral[20]; + return 'inherit'; } }; const cardTrailTextDark: ContainerFunction = ( @@ -110,8 +108,7 @@ const cardTrailTextDark: ContainerFunction = ( case 'SpecialReportAltPalette': return sourcePalette.specialReportAlt[700]; case 'Branded': - // TODO: @commercial-dev to update to palette('--card-trail-text') when launching Redesigned Labs Containers - return sourcePalette.neutral[86]; + return 'inherit'; } }; @@ -136,8 +133,7 @@ const cardKickerTextLight: ContainerFunction = (containerPalette) => { case 'SpecialReportAltPalette': return sourcePalette.specialReportAlt[100]; case 'Branded': - // TODO: @commercial-dev to update to labs[100] when launching Redesigned Labs Containers - return sourcePalette.labs[200]; + return sourcePalette.labs[100]; } }; const cardKickerTextDark: ContainerFunction = (containerPalette) => { @@ -186,8 +182,7 @@ const cardQuoteIconLight: ContainerFunction = (containerPalette) => { case 'SpecialReportAltPalette': return sourcePalette.specialReportAlt[100]; case 'Branded': - // TODO: @commercial-dev to update to labs[100] when launching Redesigned Labs Containers - return sourcePalette.labs[200]; + return sourcePalette.labs[100]; } }; const cardQuoteIconDark: ContainerFunction = (containerPalette) => { @@ -335,7 +330,7 @@ const sectionToggleButtonLight: ContainerFunction = (containerPalette) => { case 'SpecialReportAltPalette': return sourcePalette.neutral[60]; case 'Branded': - return palette('--section-toggle-button'); + return 'inherit'; } }; const sectionToggleButtonDark: ContainerFunction = (containerPalette) => { @@ -359,7 +354,7 @@ const sectionToggleButtonDark: ContainerFunction = (containerPalette) => { case 'SpecialReportAltPalette': return sourcePalette.neutral[60]; case 'Branded': - return palette('--section-toggle-button'); + return 'inherit'; } }; const sectionToggleButtonHoverLight: ContainerFunction = (containerPalette) => { @@ -383,7 +378,7 @@ const sectionToggleButtonHoverLight: ContainerFunction = (containerPalette) => { case 'SpecialReportAltPalette': return sourcePalette.neutral[93]; case 'Branded': - return palette('--section-toggle-button-hover'); + return 'inherit'; } }; const sectionToggleButtonHoverDark: ContainerFunction = (containerPalette) => { @@ -431,7 +426,7 @@ const cardBorderTopLight: ContainerFunction = (containerPalette) => { case 'SpecialReportAltPalette': return transparentColour(sourcePalette.neutral[46], 0.3); case 'Branded': - return sourcePalette.neutral[73]; + return 'inherit'; } }; @@ -454,7 +449,7 @@ const cardBorderTopDark: ContainerFunction = (containerPalette) => { case 'SpecialReportAltPalette': return transparentColour(sourcePalette.neutral[46], 0.3); case 'Branded': - return sourcePalette.neutral[60]; + return 'inherit'; } }; @@ -486,7 +481,7 @@ const articleBorderLight: ContainerFunction = (containerPalette) => { case 'SpecialReportAltPalette': return transparentColour(sourcePalette.neutral[46], 0.3); case 'Branded': - return sourcePalette.neutral[73]; + return 'inherit'; } }; @@ -508,7 +503,7 @@ const articleBorderDark: ContainerFunction = (containerPalette) => { case 'SpecialReportAltPalette': return sourcePalette.neutral[38]; case 'Branded': - return sourcePalette.neutral[38]; + return 'inherit'; } }; @@ -533,7 +528,7 @@ const cardBackgroundLight: ContainerFunction = (containerPalette) => { case 'SpecialReportAltPalette': return sourcePalette.specialReportAlt[800]; case 'Branded': - return palette('--section-background'); + return 'inherit'; } }; @@ -550,7 +545,7 @@ const cardBackgroundDark: ContainerFunction = (containerPalette) => { case 'SpecialReportAltPalette': return sourcePalette.specialReportAlt[100]; case 'Branded': - return palette('--section-background'); + return 'inherit'; case 'BreakingPalette': return palette('--section-background'); case 'SombreAltPalette': @@ -560,11 +555,23 @@ const cardBackgroundDark: ContainerFunction = (containerPalette) => { } }; -const cardMediaBackgroundLight: ContainerFunction = (containerPalette) => - transparentColour(cardHeadlineLight(containerPalette), 0.1); -const cardMediaBackgroundDark: ContainerFunction = (containerPalette) => - transparentColour(cardHeadlineDark(containerPalette), 0.1); +const cardMediaBackgroundLight: ContainerFunction = (containerPalette) => { + switch (containerPalette) { + case 'Branded': + return 'inherit'; + default: + return transparentColour(cardHeadlineLight(containerPalette), 0.1); + } +}; +const cardMediaBackgroundDark: ContainerFunction = (containerPalette) => { + switch (containerPalette) { + case 'Branded': + return 'inherit'; + default: + return transparentColour(cardHeadlineDark(containerPalette), 0.1); + } +}; const cardMediaWaveformLight: ContainerFunction = (containerPalette) => { switch (containerPalette) { case 'InvestigationPalette': @@ -583,8 +590,7 @@ const cardMediaWaveformLight: ContainerFunction = (containerPalette) => { case 'SpecialReportAltPalette': return sourcePalette.specialReportAlt[800]; case 'Branded': - // TODO: @commercial-dev to update to palette('--card-media-waveform') when launching Redesigned Labs Containers - return sourcePalette.neutral[86]; + return 'inherit'; } }; @@ -604,8 +610,7 @@ const cardMediaWaveformDark: ContainerFunction = (containerPalette) => { case 'EventAltPalette': return sourcePalette.culture[300]; case 'Branded': - // TODO: @commercial-dev to update to palette('--card-media-waveform') when launching Redesigned Labs Containers - return sourcePalette.neutral[38]; + return 'inherit'; } }; @@ -630,7 +635,7 @@ const sectionBackgroundLight: ContainerFunction = (containerPalette) => { case 'SpecialReportAltPalette': return sourcePalette.specialReportAlt[800]; case 'Branded': - return palette('--section-background'); + return 'inherit'; } }; @@ -654,7 +659,7 @@ const sectionBackgroundDark: ContainerFunction = (containerPalette) => { case 'SpecialReportAltPalette': return sourcePalette.specialReportAlt[100]; case 'Branded': - return palette('--section-background'); + return 'inherit'; } }; @@ -712,8 +717,7 @@ const cardBorderSupportingLight: ContainerFunction = (containerPalette) => { case 'SpecialReportAltPalette': return transparentColour(sourcePalette.neutral[46], 0.3); case 'Branded': - // TODO: @commercial-dev to update to palette('--card-border-supporting') when launching Redesigned Labs Containers - return sourcePalette.neutral[73]; + return 'inherit'; } }; const cardBorderSupportingDark: ContainerFunction = (containerPalette) => { @@ -735,8 +739,7 @@ const cardBorderSupportingDark: ContainerFunction = (containerPalette) => { case 'SpecialReportAltPalette': return transparentColour(sourcePalette.neutral[46], 0.3); case 'Branded': - // TODO: @commercial-dev to update to palette('--card-border-supporting') when launching Redesigned Labs Containers - return sourcePalette.neutral[46]; + return 'inherit'; } }; @@ -746,7 +749,6 @@ const sectionBorderLight: ContainerFunction = (containerPalette) => { case 'LongRunningAltPalette': case 'EventPalette': case 'EventAltPalette': - case 'Branded': return sourcePalette.neutral[86]; case 'SombrePalette': case 'SombreAltPalette': @@ -757,6 +759,8 @@ const sectionBorderLight: ContainerFunction = (containerPalette) => { return sourcePalette.news[600]; case 'SpecialReportAltPalette': return transparentColour(sourcePalette.neutral[46], 0.3); + case 'Branded': + return 'inherit'; } }; const sectionBorderDark: ContainerFunction = (containerPalette) => { @@ -778,7 +782,7 @@ const sectionBorderDark: ContainerFunction = (containerPalette) => { case 'SpecialReportAltPalette': return transparentColour(sourcePalette.neutral[46], 0.3); case 'Branded': - return sourcePalette.neutral[46]; + return 'inherit'; } }; @@ -803,7 +807,7 @@ const sectionDateLight: ContainerFunction = (containerPalette) => { case 'SpecialReportAltPalette': return sourcePalette.specialReportAlt[100]; case 'Branded': - return palette('--section-date'); + return 'inherit'; } }; @@ -825,7 +829,7 @@ const sectionDateDark: ContainerFunction = (containerPalette) => { case 'SpecialReportAltPalette': return sourcePalette.specialReportAlt[700]; case 'Branded': - return palette('--section-date'); + return 'inherit'; } }; @@ -849,9 +853,8 @@ const sectionTitleLight: ContainerFunction = (containerPalette) => { return sourcePalette.neutral[93]; case 'SpecialReportAltPalette': return sourcePalette.specialReportAlt[100]; - // Branded is expected to be used with LabsSection case 'Branded': - return palette('--article-section-title'); + return 'inherit'; } }; @@ -876,7 +879,7 @@ const sectionTitleDark: ContainerFunction = (containerPalette) => { case 'SpecialReportAltPalette': return sourcePalette.specialReportAlt[700]; case 'Branded': - return palette('--article-section-title'); + return 'inherit'; } }; diff --git a/dotcom-rendering/src/paletteDeclarations.ts b/dotcom-rendering/src/paletteDeclarations.ts index 8cb053ae763..36e9a5873b7 100644 --- a/dotcom-rendering/src/paletteDeclarations.ts +++ b/dotcom-rendering/src/paletteDeclarations.ts @@ -7421,22 +7421,6 @@ const paletteColours = { light: () => sourcePalette.neutral[0], dark: () => sourcePalette.neutral[100], }, - '--labs-legacy-article-section-title': { - light: () => sourcePalette.neutral[100], - dark: () => sourcePalette.neutral[97], - }, - '--labs-legacy-section-background': { - light: () => sourcePalette.neutral[93], - dark: () => sourcePalette.neutral[20], - }, - '--labs-legacy-section-background-left': { - light: () => sourcePalette.labs[400], - dark: () => sourcePalette.labs[200], - }, - '--labs-legacy-treat-text': { - light: () => sourcePalette.neutral[46], - dark: () => sourcePalette.neutral[38], - }, '--labs-logo-background': { light: () => sourcePalette.labs[100], dark: () => sourcePalette.labs[200], From 76a21a660f4c56112fe77c846a23557c8fa377a9 Mon Sep 17 00:00:00 2001 From: andresilva-guardian Date: Tue, 10 Mar 2026 12:34:52 +0000 Subject: [PATCH 16/19] Braze Banners Wrapper Design improvements (#15508) * Refactor padding in Braze Banners System display for improved layout * Adjust margins and padding in Braze Banners System display for improved layout --- .../src/lib/braze/BrazeBannersSystem.tsx | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/dotcom-rendering/src/lib/braze/BrazeBannersSystem.tsx b/dotcom-rendering/src/lib/braze/BrazeBannersSystem.tsx index 7df0bf1d9dc..109752d4a4a 100644 --- a/dotcom-rendering/src/lib/braze/BrazeBannersSystem.tsx +++ b/dotcom-rendering/src/lib/braze/BrazeBannersSystem.tsx @@ -955,7 +955,7 @@ export const BrazeBannersSystemDisplay = ({ position: relative; display: grid; margin: 0px auto; - padding: 12px 4px 0px 12px; + padding: 0px 4px 0px 12px; bottom: 0px; column-gap: 10px; align-self: stretch; @@ -981,7 +981,7 @@ export const BrazeBannersSystemDisplay = ({ '. copy-container close-button close-button' / minmax(0px, 0.5fr) 492px max-content minmax(0px, 0.5fr); - padding: 12px 12px 0px; + padding: 0px 12px 0px; } ${until.phablet} { max-width: 660px; @@ -1024,7 +1024,7 @@ export const BrazeBannersSystemDisplay = ({ background-color: ${wrapperModeForegroundColor}; width: 1px; opacity: 0.2; - margin: 24px 8px 0px; + margin: 18px 8px 0px; ${until.leftCol} { display: none; @@ -1042,18 +1042,14 @@ export const BrazeBannersSystemDisplay = ({ grid-area: copy-container; padding-left: 12px; padding-right: 12px; - padding-top: 24px; - padding-bottom: 12px; + padding-top: 18px; + padding-bottom: 24px; ${until.leftCol} { padding-left: 0px; padding-right: 0px; } - ${until.phablet} { - padding-top: 0px; - padding-bottom: 0px; - } - ${until.phablet} { - padding-top: 24px; + ${until.desktop} { + padding-top: 12px; } ` : undefined From 65c071ea33f356328c139c04ac152283312730bd Mon Sep 17 00:00:00 2001 From: Juarez Mota Date: Tue, 10 Mar 2026 13:04:55 +0000 Subject: [PATCH 17/19] refactor: remove storybook contributions mock and simplify TopBarSupport auth handling Removes the Storybook mock for contributions API and associated webpack alias. The mock was intended to prevent getAuthHeaders from hanging on consent state, but is no longer needed. Simplifies TopBarSupport by removing the timeout wrapper around getAuthHeaders, removing the fallback to getOptionsHeaders, and removing the authStatus dependency. The component now directly uses getAuthHeaders without race conditions or fall --- dotcom-rendering/.storybook/main.ts | 4 -- .../.storybook/mocks/contributions.ts | 29 ---------- .../components/TopBarSupport.importable.tsx | 53 ++++--------------- 3 files changed, 9 insertions(+), 77 deletions(-) delete mode 100644 dotcom-rendering/.storybook/mocks/contributions.ts diff --git a/dotcom-rendering/.storybook/main.ts b/dotcom-rendering/.storybook/main.ts index 990e7842dd9..e3a1ab5c94b 100644 --- a/dotcom-rendering/.storybook/main.ts +++ b/dotcom-rendering/.storybook/main.ts @@ -125,10 +125,6 @@ const webpackConfig = (config: Configuration) => { './mocks/bridgetApi.ts', ), - // Mock contributions API so getAuthHeaders does not hang on consent state in Storybook - [`${path.resolve(__dirname, '../src/lib/contributions')}$`]: - path.resolve(__dirname, './mocks/contributions.ts'), - // Mock identity auth frontend to prevent Storybook components from hanging in Pending '@guardian/identity-auth-frontend': path.resolve( __dirname, diff --git a/dotcom-rendering/.storybook/mocks/contributions.ts b/dotcom-rendering/.storybook/mocks/contributions.ts deleted file mode 100644 index a3af83b54c5..00000000000 --- a/dotcom-rendering/.storybook/mocks/contributions.ts +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Mock for use in Storybook - * Avoids circular dependencies from the Webpack alias by explicitly mocking - * only the functions required by the components. - */ - -export const getAuthHeaders = (): Promise => { - console.log('[Storybook Mock] getAuthHeaders returning undefined'); - return Promise.resolve(undefined); -}; - -export const getPurchaseInfo = (): any => undefined; - -export const shouldHideSupportMessaging = (): boolean | 'Pending' => false; - -// Additional missing exports required by other components in Storybook -export const useHasOptedOutOfArticleCount = (): boolean | 'Pending' => false; -export const hasOptedOutOfArticleCount = async (): Promise => false; -export const hasOptedOutOfWeeklyArticleCount = async (): Promise => - false; -export const hasCmpConsentForWeeklyArticleCount = async (): Promise => - false; -export const recentlyClosedBanner = (): boolean => false; -export const withinLocalNoBannerCachePeriod = (): boolean => false; -export const setLocalNoBannerCachePeriod = (): void => {}; -export const getContributionsServiceUrl = (): string => ''; -export const hasCmpConsentForBrowserId = async (): Promise => false; -export const SUPPORT_ONE_OFF_CONTRIBUTION_COOKIE = - 'gu.contributions.contrib-timestamp'; diff --git a/dotcom-rendering/src/components/TopBarSupport.importable.tsx b/dotcom-rendering/src/components/TopBarSupport.importable.tsx index 42fd0989282..f1d76407bd2 100644 --- a/dotcom-rendering/src/components/TopBarSupport.importable.tsx +++ b/dotcom-rendering/src/components/TopBarSupport.importable.tsx @@ -21,10 +21,9 @@ import { getPurchaseInfo, shouldHideSupportMessaging, } from '../lib/contributions'; -import { getOptionsHeaders } from '../lib/identity'; import { getHeader } from '../lib/sdcRequests'; import { useBetaAB } from '../lib/useAB'; -import { useAuthStatus, useIsSignedIn } from '../lib/useAuthStatus'; +import { useIsSignedIn } from '../lib/useAuthStatus'; import { useCountryCode } from '../lib/useCountryCode'; import { usePageViewId } from '../lib/usePageViewId'; import { useConfig } from './ConfigContext'; @@ -58,23 +57,11 @@ const ReaderRevenueLinksRemote = ({ useState | null>(null); const [SupportHeader, setSupportHeader] = useState | null>(null); - const authStatus = useAuthStatus(); const isSignedIn = useIsSignedIn(); const { renderingTarget } = useConfig(); const abTests = useBetaAB(); - const reportError = (error: unknown, context: string) => { - const msg = `Error importing RR header links for ${context}: ${String( - error, - )}`; - console.log(msg); - window.guardian.modules.sentry.reportError( - new Error(msg), - 'rr-header-links', - ); - }; - useEffect((): void => { if (isUndefined(countryCode) || isSignedIn === 'Pending') { return; @@ -105,35 +92,9 @@ const ReaderRevenueLinksRemote = ({ }, }; - const fallbackHeaders = - authStatus.kind === 'SignedIn' - ? getOptionsHeaders(authStatus).headers - : undefined; - - const fetchAuthHeadersWithTimeout = () => { - return Promise.race([ - getAuthHeaders(), - new Promise((resolve) => { - setTimeout(() => { - resolve(undefined); - }, 2000); - }), - ]); - }; - - void fetchAuthHeadersWithTimeout() - .catch((error: unknown) => { - // Catch any errors from getAuthHeaders itself or the timeout - reportError(error, 'getAuthHeaders'); - return undefined; - }) + getAuthHeaders() .then((headers) => - // Fallback to fallbackHeaders if headers are not present - getHeader( - contributionsServiceUrl, - requestData, - headers ?? fallbackHeaders, - ), + getHeader(contributionsServiceUrl, requestData, headers), ) .then((response: ModuleDataResponse) => { if (!response.data) { @@ -160,7 +121,12 @@ const ReaderRevenueLinksRemote = ({ ); }) .catch((error) => { - reportError(error, 'fetching headers or importing component'); + const msg = `Error importing RR header links: ${String(error)}`; + console.log(msg); + window.guardian.modules.sentry.reportError( + new Error(msg), + 'rr-header-links', + ); }); }, [ countryCode, @@ -169,7 +135,6 @@ const ReaderRevenueLinksRemote = ({ pageViewId, pageUrl, abTests, - authStatus, ]); if (SupportHeader !== null && supportHeaderResponse) { From b079e0b138c73ac5a6df136424a861767232a455 Mon Sep 17 00:00:00 2001 From: Dominik Lander Date: Mon, 9 Mar 2026 17:11:31 +0000 Subject: [PATCH 18/19] Remove deprecated 'fixed' containers --- .../fixtures/manual/frontCollections.ts | 48 +- .../src/components/DecideContainer.tsx | 143 ---- .../components/FixedLargeSlowXIV.stories.tsx | 34 - .../src/components/FixedLargeSlowXIV.tsx | 115 ---- .../components/FixedMediumFastXI.stories.tsx | 140 ---- .../src/components/FixedMediumFastXI.tsx | 95 --- .../components/FixedMediumFastXII.stories.tsx | 34 - .../src/components/FixedMediumFastXII.tsx | 68 -- .../components/FixedMediumSlowVI.stories.tsx | 34 - .../src/components/FixedMediumSlowVI.tsx | 76 --- .../components/FixedMediumSlowVII.stories.tsx | 35 - .../src/components/FixedMediumSlowVII.tsx | 82 --- .../FixedMediumSlowXIIMPU.stories.tsx | 154 ----- .../src/components/FixedMediumSlowXIIMPU.tsx | 206 ------ .../components/FixedSmallFastVIII.stories.tsx | 35 - .../src/components/FixedSmallFastVIII.tsx | 78 --- .../components/FixedSmallSlowI.stories.tsx | 30 - .../src/components/FixedSmallSlowI.tsx | 39 -- .../components/FixedSmallSlowIII.stories.tsx | 34 - .../src/components/FixedSmallSlowIII.tsx | 56 -- .../components/FixedSmallSlowIV.stories.tsx | 30 - .../src/components/FixedSmallSlowIV.tsx | 41 -- .../FixedSmallSlowVHalf.stories.tsx | 34 - .../src/components/FixedSmallSlowVHalf.tsx | 64 -- .../components/FixedSmallSlowVMPU.stories.tsx | 83 --- .../src/components/FixedSmallSlowVMPU.tsx | 46 -- .../FixedSmallSlowVThird.stories.tsx | 34 - .../src/components/FixedSmallSlowVThird.tsx | 69 -- dotcom-rendering/src/frontend/feFront.ts | 13 - .../src/frontend/schemas/feFront.json | 13 - dotcom-rendering/src/lib/cardWrappers.tsx | 620 +----------------- dotcom-rendering/src/lib/dynamicSlices.tsx | 61 -- .../src/lib/getFrontsAdPositions.test.ts | 114 ++-- .../src/lib/getFrontsAdPositions.ts | 15 - 34 files changed, 88 insertions(+), 2685 deletions(-) delete mode 100644 dotcom-rendering/src/components/FixedLargeSlowXIV.stories.tsx delete mode 100644 dotcom-rendering/src/components/FixedLargeSlowXIV.tsx delete mode 100644 dotcom-rendering/src/components/FixedMediumFastXI.stories.tsx delete mode 100644 dotcom-rendering/src/components/FixedMediumFastXI.tsx delete mode 100644 dotcom-rendering/src/components/FixedMediumFastXII.stories.tsx delete mode 100644 dotcom-rendering/src/components/FixedMediumFastXII.tsx delete mode 100644 dotcom-rendering/src/components/FixedMediumSlowVI.stories.tsx delete mode 100644 dotcom-rendering/src/components/FixedMediumSlowVI.tsx delete mode 100644 dotcom-rendering/src/components/FixedMediumSlowVII.stories.tsx delete mode 100644 dotcom-rendering/src/components/FixedMediumSlowVII.tsx delete mode 100644 dotcom-rendering/src/components/FixedMediumSlowXIIMPU.stories.tsx delete mode 100644 dotcom-rendering/src/components/FixedMediumSlowXIIMPU.tsx delete mode 100644 dotcom-rendering/src/components/FixedSmallFastVIII.stories.tsx delete mode 100644 dotcom-rendering/src/components/FixedSmallFastVIII.tsx delete mode 100644 dotcom-rendering/src/components/FixedSmallSlowI.stories.tsx delete mode 100644 dotcom-rendering/src/components/FixedSmallSlowI.tsx delete mode 100644 dotcom-rendering/src/components/FixedSmallSlowIII.stories.tsx delete mode 100644 dotcom-rendering/src/components/FixedSmallSlowIII.tsx delete mode 100644 dotcom-rendering/src/components/FixedSmallSlowIV.stories.tsx delete mode 100644 dotcom-rendering/src/components/FixedSmallSlowIV.tsx delete mode 100644 dotcom-rendering/src/components/FixedSmallSlowVHalf.stories.tsx delete mode 100644 dotcom-rendering/src/components/FixedSmallSlowVHalf.tsx delete mode 100644 dotcom-rendering/src/components/FixedSmallSlowVMPU.stories.tsx delete mode 100644 dotcom-rendering/src/components/FixedSmallSlowVMPU.tsx delete mode 100644 dotcom-rendering/src/components/FixedSmallSlowVThird.stories.tsx delete mode 100644 dotcom-rendering/src/components/FixedSmallSlowVThird.tsx diff --git a/dotcom-rendering/fixtures/manual/frontCollections.ts b/dotcom-rendering/fixtures/manual/frontCollections.ts index 43c2e4d5b5b..3a3dabb0078 100644 --- a/dotcom-rendering/fixtures/manual/frontCollections.ts +++ b/dotcom-rendering/fixtures/manual/frontCollections.ts @@ -9,7 +9,7 @@ const defaultGrouped = { const defaultValues = { backfill: [], - collectionType: 'fixed/small/slow-IV', + collectionType: 'static/medium/4', config: { showDateHeader: false, }, @@ -35,13 +35,13 @@ export const testCollectionsUk = [ }, { ...defaultValues, - collectionType: 'fixed/small/slow-IV', + collectionType: 'static/medium/4', containerPalette: 'LongRunningAltPalette', displayName: 'Ukraine invasion', }, { ...defaultValues, - collectionType: 'fixed/small/slow-V-mpu', + collectionType: 'static/medium/4', displayName: 'News extra', }, { @@ -71,12 +71,12 @@ export const testCollectionsUk = [ }, { ...defaultValues, - collectionType: 'fixed/medium/slow-VI', + collectionType: 'flexible/general', displayName: 'Lifestyle', }, { ...defaultValues, - collectionType: 'fixed/medium/slow-VII', + collectionType: 'flexible/general', displayName: 'Culture', }, { @@ -91,7 +91,7 @@ export const testCollectionsUk = [ }, { ...defaultValues, - collectionType: 'fixed/small/slow-IV', + collectionType: 'static/medium/4', displayName: 'The rural network', }, { @@ -111,23 +111,23 @@ export const testCollectionsUk = [ }, { ...defaultValues, - collectionType: 'fixed/medium/slow-VI', + collectionType: 'flexible/general', displayName: 'Multimedia', }, { ...defaultValues, - collectionType: 'fixed/small/slow-IV', + collectionType: 'static/medium/4', containerPalette: 'Branded', displayName: 'Guardian Labs', }, { ...defaultValues, - collectionType: 'fixed/medium/slow-XII-mpu', + collectionType: 'flexible/general', displayName: 'Explore', }, { ...defaultValues, - collectionType: 'fixed/small/slow-I', + collectionType: 'flexible/general', displayName: 'The big picture', }, { @@ -162,7 +162,7 @@ export const testCollectionsUs = [ }, { ...defaultValues, - collectionType: 'fixed/small/slow-IV', + collectionType: 'static/medium/4', displayName: 'In depth', }, { @@ -172,13 +172,13 @@ export const testCollectionsUs = [ }, { ...defaultValues, - collectionType: 'fixed/small/slow-IV', + collectionType: 'static/medium/4', containerPalette: 'LongRunningAltPalette', displayName: 'Ukraine invasion', }, { ...defaultValues, - collectionType: 'fixed/medium/slow-VI', + collectionType: 'flexible/general', displayName: 'Opinion', }, { @@ -193,7 +193,7 @@ export const testCollectionsUs = [ }, { ...defaultValues, - collectionType: 'fixed/small/slow-IV', + collectionType: 'static/medium/4', displayName: 'Climate crisis', }, { @@ -218,7 +218,7 @@ export const testCollectionsUs = [ }, { ...defaultValues, - collectionType: 'fixed/medium/slow-VII', + collectionType: 'flexible/general', displayName: 'Podcasts', }, { @@ -228,28 +228,28 @@ export const testCollectionsUs = [ }, { ...defaultValues, - collectionType: 'fixed/medium/slow-VI', + collectionType: 'flexible/general', displayName: 'Culture', }, { ...defaultValues, - collectionType: 'fixed/small/slow-IV', + collectionType: 'static/medium/4', containerPalette: 'Branded', displayName: 'Business briefs', }, { ...defaultValues, - collectionType: 'fixed/medium/slow-VI', + collectionType: 'flexible/general', displayName: 'Lifestyle', }, { ...defaultValues, - collectionType: 'fixed/small/slow-IV', + collectionType: 'static/medium/4', displayName: 'Take part', }, { ...defaultValues, - collectionType: 'fixed/medium/slow-VI', + collectionType: 'flexible/general', displayName: 'Explore', }, { @@ -259,7 +259,7 @@ export const testCollectionsUs = [ }, { ...defaultValues, - collectionType: 'fixed/medium/slow-VI', + collectionType: 'flexible/general', displayName: 'In pictures', }, { @@ -282,7 +282,7 @@ export const brandedTestCollections = [ }, { ...defaultValues, - collectionType: 'fixed/small/slow-IV', + collectionType: 'static/medium/4', containerPalette: 'Branded', displayName: 'Guardian Labs', }, @@ -293,7 +293,7 @@ export const brandedTestCollections = [ }, { ...defaultValues, - collectionType: 'fixed/small/slow-IV', + collectionType: 'static/medium/4', containerPalette: 'Branded', displayName: 'Guardian Labs', }, @@ -304,7 +304,7 @@ export const brandedTestCollections = [ }, { ...defaultValues, - collectionType: 'fixed/small/slow-IV', + collectionType: 'static/medium/4', containerPalette: 'Branded', displayName: 'Guardian Labs', }, diff --git a/dotcom-rendering/src/components/DecideContainer.tsx b/dotcom-rendering/src/components/DecideContainer.tsx index 39b7b7a67c8..fea97f0f8c8 100644 --- a/dotcom-rendering/src/components/DecideContainer.tsx +++ b/dotcom-rendering/src/components/DecideContainer.tsx @@ -7,19 +7,6 @@ import type { DCRFrontCard, DCRGroupedTrails, } from '../types/front'; -import { FixedLargeSlowXIV } from './FixedLargeSlowXIV'; -import { FixedMediumFastXI } from './FixedMediumFastXI'; -import { FixedMediumFastXII } from './FixedMediumFastXII'; -import { FixedMediumSlowVI } from './FixedMediumSlowVI'; -import { FixedMediumSlowVII } from './FixedMediumSlowVII'; -import { FixedMediumSlowXIIMPU } from './FixedMediumSlowXIIMPU'; -import { FixedSmallFastVIII } from './FixedSmallFastVIII'; -import { FixedSmallSlowI } from './FixedSmallSlowI'; -import { FixedSmallSlowIII } from './FixedSmallSlowIII'; -import { FixedSmallSlowIV } from './FixedSmallSlowIV'; -import { FixedSmallSlowVHalf } from './FixedSmallSlowVHalf'; -import { FixedSmallSlowVMPU } from './FixedSmallSlowVMPU'; -import { FixedSmallSlowVThird } from './FixedSmallSlowVThird'; import { FlexibleGeneral } from './FlexibleGeneral'; import { FlexibleSpecial } from './FlexibleSpecial'; import { Island } from './Island'; @@ -63,136 +50,6 @@ export const DecideContainer = ({ isInSlimHomepageAbTestVariant = false, }: Props) => { switch (containerType) { - case 'fixed/large/slow-XIV': - return ( - - ); - case 'fixed/small/slow-IV': - return ( - - ); - case 'fixed/small/slow-V-mpu': - return ( - - ); - case 'fixed/small/slow-III': - return ( - - ); - case 'fixed/small/slow-I': - return ( - - ); - case 'fixed/small/slow-V-third': - return ( - - ); - case 'fixed/small/slow-V-half': - return ( - - ); - case 'fixed/medium/slow-VI': - return ( - - ); - case 'fixed/medium/slow-VII': - return ( - - ); - case 'fixed/medium/slow-XII-mpu': - return ( - - ); - case 'fixed/medium/fast-XII': - return ( - - ); - case 'fixed/medium/fast-XI': - return ( - - ); - case 'fixed/small/fast-VIII': - return ( - - ); case 'nav/list': return ; case 'nav/media-list': diff --git a/dotcom-rendering/src/components/FixedLargeSlowXIV.stories.tsx b/dotcom-rendering/src/components/FixedLargeSlowXIV.stories.tsx deleted file mode 100644 index 58220573981..00000000000 --- a/dotcom-rendering/src/components/FixedLargeSlowXIV.stories.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { breakpoints } from '@guardian/source/foundations'; -import { discussionApiUrl } from '../../fixtures/manual/discussionApiUrl'; -import { trails } from '../../fixtures/manual/trails'; -import { FixedLargeSlowXIV } from './FixedLargeSlowXIV'; -import { FrontSection } from './FrontSection'; - -export default { - component: FixedLargeSlowXIV, - title: 'Front Containers/Deprecated Containers/FixedLargeSlowXIV', - parameters: { - chromatic: { - viewports: [ - breakpoints.mobile, - breakpoints.tablet, - breakpoints.wide, - ], - }, - }, -}; - -export const Default = () => ( - - - -); -Default.storyName = 'FixedLargeSlowXIV'; diff --git a/dotcom-rendering/src/components/FixedLargeSlowXIV.tsx b/dotcom-rendering/src/components/FixedLargeSlowXIV.tsx deleted file mode 100644 index 4c5a87245a0..00000000000 --- a/dotcom-rendering/src/components/FixedLargeSlowXIV.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import { - Card25Media25, - Card25Media25SmallHeadline, - Card75Media50Right, - CardDefault, -} from '../lib/cardWrappers'; -import { shouldPadWrappableRows } from '../lib/dynamicSlices'; -import type { DCRContainerPalette, DCRFrontCard } from '../types/front'; -import { LI } from './Card/components/LI'; -import { UL } from './Card/components/UL'; -import type { Loading } from './CardPicture'; - -type Props = { - trails: DCRFrontCard[]; - imageLoading: Loading; - containerPalette?: DCRContainerPalette; - showAge?: boolean; - serverTime?: number; -}; - -export const FixedLargeSlowXIV = ({ - trails, - containerPalette, - showAge, - serverTime, - imageLoading, -}: Props) => { - const firstSlice75 = trails.slice(0, 1); - const firstSlice25 = trails.slice(1, 2); - const secondSlice25 = trails.slice(2, 6); - const thirdSlice25 = trails.slice(6, 14); - - return ( - <> -
    - {firstSlice75.map((card) => { - return ( -
  • - -
  • - ); - })} - {firstSlice25.map((card) => { - return ( -
  • - -
  • - ); - })} -
-
    - {secondSlice25.map((card, cardIndex) => { - return ( -
  • 0} - key={card.url} - > - -
  • - ); - })} -
-
    - {thirdSlice25.map((card, cardIndex, { length }) => { - const columns = 4; - return ( -
  • - -
  • - ); - })} -
- - ); -}; diff --git a/dotcom-rendering/src/components/FixedMediumFastXI.stories.tsx b/dotcom-rendering/src/components/FixedMediumFastXI.stories.tsx deleted file mode 100644 index 348f25b4782..00000000000 --- a/dotcom-rendering/src/components/FixedMediumFastXI.stories.tsx +++ /dev/null @@ -1,140 +0,0 @@ -import { breakpoints } from '@guardian/source/foundations'; -import { discussionApiUrl } from '../../fixtures/manual/discussionApiUrl'; -import { trails } from '../../fixtures/manual/trails'; -import { FixedMediumFastXI } from './FixedMediumFastXI'; -import { FrontSection } from './FrontSection'; - -export default { - component: FixedMediumFastXI, - title: 'Front Containers/Deprecated Containers/FixedMediumFastXI', - parameters: { - chromatic: { - viewports: [ - breakpoints.mobile, - breakpoints.tablet, - breakpoints.wide, - ], - }, - }, -}; - -export const OneTrail = () => ( - - - -); -OneTrail.storyName = 'with one trail'; - -export const TwoTrails = () => ( - - - -); -TwoTrails.storyName = 'with two trails'; - -export const ThreeTrails = () => ( - - - -); -ThreeTrails.storyName = 'with three trails'; - -export const FourTrails = () => ( - - - -); -FourTrails.storyName = 'with four trails'; - -export const FiveTrails = () => ( - - - -); -FiveTrails.storyName = 'with five trails'; - -export const SixTrails = () => ( - - - -); -SixTrails.storyName = 'with six trails'; - -export const SevenTrails = () => ( - - - -); -SevenTrails.storyName = 'with seven trails'; - -export const EightTrails = () => ( - - - -); -EightTrails.storyName = 'with eight trails'; - -export const NineTrails = () => ( - - - -); -NineTrails.storyName = 'with nine trails'; - -export const TenTrails = () => ( - - - -); -TenTrails.storyName = 'with ten trails'; - -export const ElevenTrails = () => ( - - - -); -ElevenTrails.storyName = 'with eleven trails'; diff --git a/dotcom-rendering/src/components/FixedMediumFastXI.tsx b/dotcom-rendering/src/components/FixedMediumFastXI.tsx deleted file mode 100644 index 70a75ac4ec9..00000000000 --- a/dotcom-rendering/src/components/FixedMediumFastXI.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import { CardDefault } from '../lib/cardWrappers'; -import { Card50_Card25_Card25 } from '../lib/dynamicSlices'; -import type { DCRContainerPalette, DCRFrontCard } from '../types/front'; -import { LI } from './Card/components/LI'; -import { UL } from './Card/components/UL'; -import type { Loading } from './CardPicture'; - -type Props = { - trails: DCRFrontCard[]; - imageLoading: Loading; - containerPalette?: DCRContainerPalette; - showAge?: boolean; - serverTime?: number; -}; - -const decideOffset = ({ - length, - position, -}: { - length: number; - position: number; -}): boolean => { - if (length <= 5) return false; - switch (length) { - case 6: { - switch (position) { - case 2: - return true; - default: - return false; - } - } - case 7: - return false; - case 8: { - if (position <= 4) return true; - else return false; - } - } - return false; -}; - -export const FixedMediumFastXI = ({ - trails, - containerPalette, - showAge, - serverTime, - imageLoading, -}: Props) => { - const firstSlice = trails.slice(0, 3); - const remaining = trails.slice(3, 11); - return ( - <> - - {/* - * This pattern of using wrapCards on the UL + percentage=25 and stretch=true - * on the LI creates a dynanic list of cards over two rows where the second row - * only appears when there are more than 4 cards - * - * E.g: - * ._____._____._____._____. - * |_____|_____|_____|_____| - * |___________|___________| - */} -
    - {remaining.map((trail, trailIndex) => ( -
  • - -
  • - ))} -
- - ); -}; diff --git a/dotcom-rendering/src/components/FixedMediumFastXII.stories.tsx b/dotcom-rendering/src/components/FixedMediumFastXII.stories.tsx deleted file mode 100644 index edffdc1fffd..00000000000 --- a/dotcom-rendering/src/components/FixedMediumFastXII.stories.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { breakpoints } from '@guardian/source/foundations'; -import { discussionApiUrl } from '../../fixtures/manual/discussionApiUrl'; -import { trails } from '../../fixtures/manual/trails'; -import { FixedMediumFastXII } from './FixedMediumFastXII'; -import { FrontSection } from './FrontSection'; - -export default { - component: FixedMediumFastXII, - title: 'Front Containers/Deprecated Containers/FixedMediumFastXII', - parameters: { - chromatic: { - viewports: [ - breakpoints.mobile, - breakpoints.tablet, - breakpoints.wide, - ], - }, - }, -}; - -export const Default = () => ( - - - -); -Default.storyName = 'FixedMediumFastXII'; diff --git a/dotcom-rendering/src/components/FixedMediumFastXII.tsx b/dotcom-rendering/src/components/FixedMediumFastXII.tsx deleted file mode 100644 index f5f06ec66f7..00000000000 --- a/dotcom-rendering/src/components/FixedMediumFastXII.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { Card25Media25, CardDefault } from '../lib/cardWrappers'; -import type { DCRContainerPalette, DCRFrontCard } from '../types/front'; -import { LI } from './Card/components/LI'; -import { UL } from './Card/components/UL'; -import type { Loading } from './CardPicture'; - -type Props = { - trails: DCRFrontCard[]; - imageLoading: Loading; - containerPalette?: DCRContainerPalette; - showAge?: boolean; - serverTime?: number; -}; - -export const FixedMediumFastXII = ({ - trails, - containerPalette, - showAge, - serverTime, - imageLoading, -}: Props) => { - const firstSlice25 = trails.slice(0, 4); - const remaining = trails.slice(4, 12); - - return ( - <> -
    - {firstSlice25.map((trail, index) => { - return ( -
  • 0} - > - -
  • - ); - })} -
-
    - {remaining.map((trail, index) => { - return ( -
  • - -
  • - ); - })} -
- - ); -}; diff --git a/dotcom-rendering/src/components/FixedMediumSlowVI.stories.tsx b/dotcom-rendering/src/components/FixedMediumSlowVI.stories.tsx deleted file mode 100644 index fa7244462ce..00000000000 --- a/dotcom-rendering/src/components/FixedMediumSlowVI.stories.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { breakpoints } from '@guardian/source/foundations'; -import { discussionApiUrl } from '../../fixtures/manual/discussionApiUrl'; -import { trails } from '../../fixtures/manual/trails'; -import { FixedMediumSlowVI } from './FixedMediumSlowVI'; -import { FrontSection } from './FrontSection'; - -export default { - component: FixedMediumSlowVI, - title: 'Front Containers/Deprecated Containers/FixedMediumSlowVI', - parameters: { - chromatic: { - viewports: [ - breakpoints.mobile, - breakpoints.tablet, - breakpoints.wide, - ], - }, - }, -}; - -export const Default = () => ( - - - -); -Default.storyName = 'FixedMediumSlowVI'; diff --git a/dotcom-rendering/src/components/FixedMediumSlowVI.tsx b/dotcom-rendering/src/components/FixedMediumSlowVI.tsx deleted file mode 100644 index fc956efa2d9..00000000000 --- a/dotcom-rendering/src/components/FixedMediumSlowVI.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { - Card25Media25TallNoTrail, - Card25Media25TallSmallHeadline, - Card75Media50Right, -} from '../lib/cardWrappers'; -import type { DCRContainerPalette, DCRFrontCard } from '../types/front'; -import { LI } from './Card/components/LI'; -import { UL } from './Card/components/UL'; -import type { Loading } from './CardPicture'; - -type Props = { - trails: DCRFrontCard[]; - imageLoading: Loading; - containerPalette?: DCRContainerPalette; - showAge?: boolean; - serverTime?: number; -}; - -export const FixedMediumSlowVI = ({ - trails, - containerPalette, - showAge, - imageLoading, - serverTime, -}: Props) => { - const firstSlice75 = trails.slice(0, 1); - const firstSlice25 = trails.slice(1, 2); - const secondSlice25 = trails.slice(2, 6); - - return ( - <> -
    - {firstSlice75.map((trail) => ( -
  • - -
  • - ))} - {firstSlice25.map((trail) => ( -
  • - -
  • - ))} -
-
    - {secondSlice25.map((trail, index) => ( -
  • 0}> - -
  • - ))} -
- - ); -}; diff --git a/dotcom-rendering/src/components/FixedMediumSlowVII.stories.tsx b/dotcom-rendering/src/components/FixedMediumSlowVII.stories.tsx deleted file mode 100644 index ac23ff1102b..00000000000 --- a/dotcom-rendering/src/components/FixedMediumSlowVII.stories.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { breakpoints } from '@guardian/source/foundations'; -import { discussionApiUrl } from '../../fixtures/manual/discussionApiUrl'; -import { trails } from '../../fixtures/manual/trails'; -import { FixedMediumSlowVII } from './FixedMediumSlowVII'; -import { FrontSection } from './FrontSection'; - -export default { - component: FixedMediumSlowVII, - title: 'Front Containers/Deprecated Containers/FixedMediumSlowVII', - parameters: { - chromatic: { - viewports: [ - breakpoints.mobile, - breakpoints.tablet, - breakpoints.wide, - ], - }, - }, -}; - -export const Default = () => ( - - - -); -Default.storyName = 'FixedMediumSlowVII'; diff --git a/dotcom-rendering/src/components/FixedMediumSlowVII.tsx b/dotcom-rendering/src/components/FixedMediumSlowVII.tsx deleted file mode 100644 index 2a55104f77b..00000000000 --- a/dotcom-rendering/src/components/FixedMediumSlowVII.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import { - Card25Media25Tall, - Card25Media25TallSmallHeadline, - Card50Media50, -} from '../lib/cardWrappers'; -import type { DCRContainerPalette, DCRFrontCard } from '../types/front'; -import { LI } from './Card/components/LI'; -import { UL } from './Card/components/UL'; -import type { Loading } from './CardPicture'; - -type Props = { - trails: DCRFrontCard[]; - imageLoading: Loading; - containerPalette?: DCRContainerPalette; - showAge?: boolean; - serverTime?: number; -}; - -export const FixedMediumSlowVII = ({ - trails, - containerPalette, - showAge, - serverTime, - imageLoading, -}: Props) => { - const firstSlice50 = trails.slice(0, 1); - const firstSlice25 = trails.slice(1, 3); - const secondSlice25 = trails.slice(3, 7); - - return ( - <> -
    - {firstSlice50.map((trail) => ( -
  • - -
  • - ))} - - {firstSlice25.map((trail) => ( -
  • - -
  • - ))} -
-
    - {secondSlice25.map((trail, index) => ( -
  • 0}> - -
  • - ))} -
- - ); -}; diff --git a/dotcom-rendering/src/components/FixedMediumSlowXIIMPU.stories.tsx b/dotcom-rendering/src/components/FixedMediumSlowXIIMPU.stories.tsx deleted file mode 100644 index cf4c1f7914d..00000000000 --- a/dotcom-rendering/src/components/FixedMediumSlowXIIMPU.stories.tsx +++ /dev/null @@ -1,154 +0,0 @@ -import { breakpoints } from '@guardian/source/foundations'; -import { discussionApiUrl } from '../../fixtures/manual/discussionApiUrl'; -import { trails } from '../../fixtures/manual/trails'; -import { FixedMediumSlowXIIMPU } from './FixedMediumSlowXIIMPU'; -import { FrontSection } from './FrontSection'; - -export default { - component: FixedMediumSlowXIIMPU, - title: 'Front Containers/Deprecated Containers/FixedMediumSlowXIIMPU', - parameters: { - chromatic: { - viewports: [ - breakpoints.mobile, - breakpoints.tablet, - breakpoints.wide, - ], - }, - }, -}; - -export const OneTrail = () => ( - - - -); -OneTrail.storyName = 'with one trail'; - -export const TwoTrails = () => ( - - - -); -TwoTrails.storyName = 'with two trails'; - -export const ThreeTrails = () => ( - - - -); -ThreeTrails.storyName = 'with three trails'; - -export const FourTrails = () => ( - - - -); -FourTrails.storyName = 'with four trails'; - -export const FiveTrails = () => ( - - - -); -FiveTrails.storyName = 'with five trails'; - -export const SixTrails = () => ( - - - -); -SixTrails.storyName = 'with six trails'; - -export const SevenTrails = () => ( - - - -); -SevenTrails.storyName = 'with seven trails'; - -export const EightTrails = () => ( - - - -); -EightTrails.storyName = 'with eight trails'; - -export const NineTrails = () => ( - - - -); -NineTrails.storyName = 'with nine trails'; diff --git a/dotcom-rendering/src/components/FixedMediumSlowXIIMPU.tsx b/dotcom-rendering/src/components/FixedMediumSlowXIIMPU.tsx deleted file mode 100644 index 03d706ec422..00000000000 --- a/dotcom-rendering/src/components/FixedMediumSlowXIIMPU.tsx +++ /dev/null @@ -1,206 +0,0 @@ -import { - Card100Media100Tall, - Card33Media33MobileTopTall, - Card33Media33Tall, - CardDefault, -} from '../lib/cardWrappers'; -import { shouldPadWrappableRows } from '../lib/dynamicSlices'; -import type { DCRContainerPalette, DCRFrontCard } from '../types/front'; -import { LI } from './Card/components/LI'; -import { UL } from './Card/components/UL'; -import type { Loading } from './CardPicture'; - -type Props = { - trails: DCRFrontCard[]; - containerPalette?: DCRContainerPalette; - imageLoading: Loading; - serverTime?: number; - showAge?: boolean; -}; - -/* .___________.___________.___________. - * |###########|###########|###########| - * | | | | - * |___________|___________|___________| - */ -const Card33_Card33_Card33 = ({ - trails, - containerPalette, - showAge, - padBottom, - imageLoading, - serverTime, -}: { - trails: DCRFrontCard[]; - imageLoading: Loading; - serverTime?: number; - containerPalette?: DCRContainerPalette; - showAge?: boolean; - padBottom?: boolean; -}) => { - const card33 = trails.slice(0, 1); - const cards33 = trails.slice(1, 3); - - return ( -
    - {card33.map((trail) => ( -
  • - -
  • - ))} - {cards33.map((trail) => ( -
  • - -
  • - ))} -
- ); -}; - -/* .___________.___________. - * |###########|###########| - * | | | - * |___________|___________| - */ -const Card50_Card50 = ({ - trails, - containerPalette, - showAge, - padBottom, - imageLoading, - serverTime, -}: { - trails: DCRFrontCard[]; - imageLoading: Loading; - serverTime?: number; - containerPalette?: DCRContainerPalette; - showAge?: boolean; - padBottom?: boolean; -}) => { - const card50 = trails.slice(0, 1); - const cards50 = trails.slice(1); - return ( -
    - {card50.map((trail) => ( -
  • - -
  • - ))} - {cards50.map((trail) => ( -
  • - -
  • - ))} -
- ); -}; - -/** - * @deprecated Adverts are no longer shown within containers. We no - * longer use containers that reserve a space for an MPU advert. - */ -export const FixedMediumSlowXIIMPU = ({ - trails, - containerPalette, - showAge, - imageLoading, - serverTime, -}: Props) => { - const firstSlice = trails.slice(0, 3); - const remaining = trails.slice(3, 9); - return ( - <> - {trails.length === 1 ? ( -
    -
  • - {trails.map((trail) => ( - - ))} -
  • -
- ) : trails.length === 2 ? ( - - ) : ( - - )} - -
    - {remaining.map((trail, trailIndex) => ( -
  • - -
  • - ))} -
- - ); -}; diff --git a/dotcom-rendering/src/components/FixedSmallFastVIII.stories.tsx b/dotcom-rendering/src/components/FixedSmallFastVIII.stories.tsx deleted file mode 100644 index ab825c68e9f..00000000000 --- a/dotcom-rendering/src/components/FixedSmallFastVIII.stories.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { breakpoints } from '@guardian/source/foundations'; -import { discussionApiUrl } from '../../fixtures/manual/discussionApiUrl'; -import { trails } from '../../fixtures/manual/trails'; -import { FixedSmallFastVIII } from './FixedSmallFastVIII'; -import { FrontSection } from './FrontSection'; - -export default { - component: FixedSmallFastVIII, - title: 'Front Containers/Deprecated Containers/FixedSmallFastVIII', - parameters: { - chromatic: { - viewports: [ - breakpoints.mobile, - breakpoints.tablet, - breakpoints.wide, - ], - }, - }, -}; - -export const Default = () => ( - - - -); -Default.storyName = 'FixedSmallFastVIII'; diff --git a/dotcom-rendering/src/components/FixedSmallFastVIII.tsx b/dotcom-rendering/src/components/FixedSmallFastVIII.tsx deleted file mode 100644 index e74d16ce15e..00000000000 --- a/dotcom-rendering/src/components/FixedSmallFastVIII.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import { Card25Media25, CardDefault } from '../lib/cardWrappers'; -import { shouldPadWrappableRows } from '../lib/dynamicSlices'; -import type { DCRContainerPalette, DCRFrontCard } from '../types/front'; -import { LI } from './Card/components/LI'; -import { UL } from './Card/components/UL'; -import type { Loading } from './CardPicture'; - -type Props = { - trails: DCRFrontCard[]; - imageLoading: Loading; - containerPalette?: DCRContainerPalette; - showAge?: boolean; - serverTime?: number; -}; - -export const FixedSmallFastVIII = ({ - trails, - containerPalette, - showAge, - serverTime, - imageLoading, -}: Props) => { - if (!trails[0]) return null; - const firstSlice25 = trails.slice(0, 2); - const remaining = trails.slice(2, 8); - - return ( -
    - {firstSlice25.map((card, cardIndex) => { - return ( -
  • - -
  • - ); - })} -
  • -
      - {remaining.map((card, cardIndex) => { - const columns = 2; - return ( -
    • - -
    • - ); - })} -
    -
  • -
- ); -}; diff --git a/dotcom-rendering/src/components/FixedSmallSlowI.stories.tsx b/dotcom-rendering/src/components/FixedSmallSlowI.stories.tsx deleted file mode 100644 index a309b053d6a..00000000000 --- a/dotcom-rendering/src/components/FixedSmallSlowI.stories.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { breakpoints } from '@guardian/source/foundations'; -import { discussionApiUrl } from '../../fixtures/manual/discussionApiUrl'; -import { trails } from '../../fixtures/manual/trails'; -import { FixedSmallSlowI } from './FixedSmallSlowI'; -import { FrontSection } from './FrontSection'; - -export default { - component: FixedSmallSlowI, - title: 'Front Containers/Deprecated Containers/FixedSmallSlowI', - parameters: { - chromatic: { - viewports: [ - breakpoints.mobile, - breakpoints.tablet, - breakpoints.wide, - ], - }, - }, -}; - -export const Default = () => ( - - - -); -Default.storyName = 'FixedSmallSlowI'; diff --git a/dotcom-rendering/src/components/FixedSmallSlowI.tsx b/dotcom-rendering/src/components/FixedSmallSlowI.tsx deleted file mode 100644 index 69c8d18182e..00000000000 --- a/dotcom-rendering/src/components/FixedSmallSlowI.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { Card100Media75 } from '../lib/cardWrappers'; -import type { DCRContainerPalette, DCRFrontCard } from '../types/front'; -import { LI } from './Card/components/LI'; -import { UL } from './Card/components/UL'; -import type { Loading } from './CardPicture'; - -type Props = { - trails: DCRFrontCard[]; - imageLoading: Loading; - containerPalette?: DCRContainerPalette; - showAge?: boolean; - serverTime?: number; -}; - -export const FixedSmallSlowI = ({ - trails, - containerPalette, - showAge, - serverTime, - imageLoading, -}: Props) => { - const firstSlice100 = trails.slice(0, 1); - - return ( -
    - {firstSlice100.map((card) => ( -
  • - -
  • - ))} -
- ); -}; diff --git a/dotcom-rendering/src/components/FixedSmallSlowIII.stories.tsx b/dotcom-rendering/src/components/FixedSmallSlowIII.stories.tsx deleted file mode 100644 index df9f8a3da56..00000000000 --- a/dotcom-rendering/src/components/FixedSmallSlowIII.stories.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { breakpoints } from '@guardian/source/foundations'; -import { discussionApiUrl } from '../../fixtures/manual/discussionApiUrl'; -import { trails } from '../../fixtures/manual/trails'; -import { FixedSmallSlowIII } from './FixedSmallSlowIII'; -import { FrontSection } from './FrontSection'; - -export default { - component: FixedSmallSlowIII, - title: 'Front Containers/Deprecated Containers/FixedSmallSlowIII', - parameters: { - chromatic: { - viewports: [ - breakpoints.mobile, - breakpoints.tablet, - breakpoints.wide, - ], - }, - }, -}; - -export const Default = () => ( - - - -); -Default.storyName = 'FixedSmallSlowIII'; diff --git a/dotcom-rendering/src/components/FixedSmallSlowIII.tsx b/dotcom-rendering/src/components/FixedSmallSlowIII.tsx deleted file mode 100644 index 16be9d1b7c9..00000000000 --- a/dotcom-rendering/src/components/FixedSmallSlowIII.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { Card25Media25Tall, Card50Media50 } from '../lib/cardWrappers'; -import type { DCRContainerPalette, DCRFrontCard } from '../types/front'; -import { LI } from './Card/components/LI'; -import { UL } from './Card/components/UL'; -import type { Loading } from './CardPicture'; - -type Props = { - trails: DCRFrontCard[]; - imageLoading: Loading; - containerPalette?: DCRContainerPalette; - showAge?: boolean; - serverTime?: number; -}; - -export const FixedSmallSlowIII = ({ - trails, - containerPalette, - showAge, - serverTime, - imageLoading, -}: Props) => { - const firstSlice50 = trails.slice(0, 1); - const firstSlice25 = trails.slice(1, 3); - - return ( -
    - {firstSlice50.map((trail) => ( -
  • - -
  • - ))} - {firstSlice25.map((trail) => ( -
  • - -
  • - ))} -
- ); -}; diff --git a/dotcom-rendering/src/components/FixedSmallSlowIV.stories.tsx b/dotcom-rendering/src/components/FixedSmallSlowIV.stories.tsx deleted file mode 100644 index 88599e5b485..00000000000 --- a/dotcom-rendering/src/components/FixedSmallSlowIV.stories.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { breakpoints } from '@guardian/source/foundations'; -import { discussionApiUrl } from '../../fixtures/manual/discussionApiUrl'; -import { trails } from '../../fixtures/manual/trails'; -import { FixedSmallSlowIV } from './FixedSmallSlowIV'; -import { FrontSection } from './FrontSection'; - -export default { - component: FixedSmallSlowIV, - title: 'Front Containers/Deprecated Containers/FixedSmallSlowIV', - parameters: { - chromatic: { - viewports: [ - breakpoints.mobile, - breakpoints.tablet, - breakpoints.wide, - ], - }, - }, -}; - -export const Default = () => ( - - - -); -Default.storyName = 'FixedSmallSlowIV'; diff --git a/dotcom-rendering/src/components/FixedSmallSlowIV.tsx b/dotcom-rendering/src/components/FixedSmallSlowIV.tsx deleted file mode 100644 index 61abae4aac6..00000000000 --- a/dotcom-rendering/src/components/FixedSmallSlowIV.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { Card25Media25 } from '../lib/cardWrappers'; -import type { DCRContainerPalette, DCRFrontCard } from '../types/front'; -import { LI } from './Card/components/LI'; -import { UL } from './Card/components/UL'; -import type { Loading } from './CardPicture'; - -type Props = { - trails: DCRFrontCard[]; - imageLoading: Loading; - containerPalette?: DCRContainerPalette; - showAge?: boolean; - serverTime?: number; -}; - -export const FixedSmallSlowIV = ({ - trails, - containerPalette, - showAge, - serverTime, - imageLoading, -}: Props) => { - const firstSlice25 = trails.slice(0, 4); - - return ( -
    - {firstSlice25.map((trail, index) => { - return ( -
  • 0}> - -
  • - ); - })} -
- ); -}; diff --git a/dotcom-rendering/src/components/FixedSmallSlowVHalf.stories.tsx b/dotcom-rendering/src/components/FixedSmallSlowVHalf.stories.tsx deleted file mode 100644 index 35da2505f36..00000000000 --- a/dotcom-rendering/src/components/FixedSmallSlowVHalf.stories.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { breakpoints } from '@guardian/source/foundations'; -import { discussionApiUrl } from '../../fixtures/manual/discussionApiUrl'; -import { trails } from '../../fixtures/manual/trails'; -import { FixedSmallSlowVHalf } from './FixedSmallSlowVHalf'; -import { FrontSection } from './FrontSection'; - -export default { - component: FixedSmallSlowVHalf, - title: 'Front Containers/Deprecated Containers/FixedSmallSlowVHalf', - parameters: { - chromatic: { - viewports: [ - breakpoints.mobile, - breakpoints.tablet, - breakpoints.wide, - ], - }, - }, -}; - -export const Default = () => ( - - - -); -Default.storyName = 'FixedSmallSlowVHalf'; diff --git a/dotcom-rendering/src/components/FixedSmallSlowVHalf.tsx b/dotcom-rendering/src/components/FixedSmallSlowVHalf.tsx deleted file mode 100644 index 049dc5995e2..00000000000 --- a/dotcom-rendering/src/components/FixedSmallSlowVHalf.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { Card50Media50, CardDefaultMediaMobile } from '../lib/cardWrappers'; -import type { DCRContainerPalette, DCRFrontCard } from '../types/front'; -import { LI } from './Card/components/LI'; -import { UL } from './Card/components/UL'; -import type { Loading } from './CardPicture'; - -type Props = { - trails: DCRFrontCard[]; - imageLoading: Loading; - containerPalette?: DCRContainerPalette; - showAge?: boolean; - serverTime?: number; -}; - -export const FixedSmallSlowVHalf = ({ - trails, - containerPalette, - showAge, - serverTime, - imageLoading, -}: Props) => { - const firstSlice50 = trails.slice(0, 1); - const remaining = trails.slice(1, 5); - - return ( -
    - {firstSlice50.map((trail) => { - return ( -
  • - -
  • - ); - })} -
  • -
      - {remaining.map((trail) => { - return ( -
    • - -
    • - ); - })} -
    -
  • -
- ); -}; diff --git a/dotcom-rendering/src/components/FixedSmallSlowVMPU.stories.tsx b/dotcom-rendering/src/components/FixedSmallSlowVMPU.stories.tsx deleted file mode 100644 index 8d5c28ba012..00000000000 --- a/dotcom-rendering/src/components/FixedSmallSlowVMPU.stories.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import { breakpoints } from '@guardian/source/foundations'; -import { discussionApiUrl } from '../../fixtures/manual/discussionApiUrl'; -import { trails } from '../../fixtures/manual/trails'; -import { FixedSmallSlowVMPU } from './FixedSmallSlowVMPU'; -import { FrontSection } from './FrontSection'; - -export default { - component: FixedSmallSlowVMPU, - title: 'Front Containers/Deprecated Containers/FixedSmallSlowVMPU', - parameters: { - chromatic: { - viewports: [ - breakpoints.mobile, - breakpoints.tablet, - breakpoints.wide, - ], - }, - }, -}; - -export const FourCards = () => ( - - - -); - -FourCards.storyName = 'With 4 cards'; - -export const ThreeCards = () => ( - - - -); - -ThreeCards.storyName = 'With 3 cards'; - -export const TwoCards = () => ( - - - -); - -TwoCards.storyName = 'With 2 cards'; - -export const OneCard = () => ( - - - -); - -OneCard.storyName = 'With 1 card'; diff --git a/dotcom-rendering/src/components/FixedSmallSlowVMPU.tsx b/dotcom-rendering/src/components/FixedSmallSlowVMPU.tsx deleted file mode 100644 index 8af6450cdde..00000000000 --- a/dotcom-rendering/src/components/FixedSmallSlowVMPU.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { Card25Media25SmallHeadline } from '../lib/cardWrappers'; -import type { DCRContainerPalette, DCRFrontCard } from '../types/front'; -import { LI } from './Card/components/LI'; -import { UL } from './Card/components/UL'; -import type { Loading } from './CardPicture'; - -type Props = { - trails: DCRFrontCard[]; - imageLoading: Loading; - containerPalette?: DCRContainerPalette; - showAge?: boolean; - serverTime?: number; -}; - -/** - * @deprecated Adverts are no longer shown within containers. We no - * longer use containers that reserve a space for an MPU advert. - */ -export const FixedSmallSlowVMPU = ({ - trails, - containerPalette, - showAge, - serverTime, - imageLoading, -}: Props) => ( -
    - {trails.slice(0, 4).map((card, cardIndex) => { - return ( -
  • 0} - key={card.url} - > - -
  • - ); - })} -
-); diff --git a/dotcom-rendering/src/components/FixedSmallSlowVThird.stories.tsx b/dotcom-rendering/src/components/FixedSmallSlowVThird.stories.tsx deleted file mode 100644 index 81e5c656877..00000000000 --- a/dotcom-rendering/src/components/FixedSmallSlowVThird.stories.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { breakpoints } from '@guardian/source/foundations'; -import { discussionApiUrl } from '../../fixtures/manual/discussionApiUrl'; -import { trails } from '../../fixtures/manual/trails'; -import { FixedSmallSlowVThird } from './FixedSmallSlowVThird'; -import { FrontSection } from './FrontSection'; - -export default { - component: FixedSmallSlowVThird, - title: 'Front Containers/Deprecated Containers/FixedSmallSlowVThird', - parameters: { - chromatic: { - viewports: [ - breakpoints.mobile, - breakpoints.tablet, - breakpoints.wide, - ], - }, - }, -}; - -export const Default = () => ( - - - -); -Default.storyName = 'FixedSmallSlowVThird'; diff --git a/dotcom-rendering/src/components/FixedSmallSlowVThird.tsx b/dotcom-rendering/src/components/FixedSmallSlowVThird.tsx deleted file mode 100644 index 61b36ce02ff..00000000000 --- a/dotcom-rendering/src/components/FixedSmallSlowVThird.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { Card25Media25 } from '../lib/cardWrappers'; -import type { DCRContainerPalette, DCRFrontCard } from '../types/front'; -import { LI } from './Card/components/LI'; -import { UL } from './Card/components/UL'; -import type { Loading } from './CardPicture'; -import { FrontCard } from './FrontCard'; - -type Props = { - trails: DCRFrontCard[]; - imageLoading: Loading; - containerPalette?: DCRContainerPalette; - showAge?: boolean; - serverTime?: number; -}; - -export const FixedSmallSlowVThird = ({ - trails, - containerPalette, - showAge, - serverTime, - imageLoading, -}: Props) => { - const firstSlice25 = trails.slice(0, 2); - const remaining = trails.slice(2, 5); - - return ( -
    - {firstSlice25.map((trail, index) => { - return ( -
  • 0} - percentage="25%" - > - -
  • - ); - })} -
  • -
      - {remaining.map((trail) => { - return ( -
    • - -
    • - ); - })} -
    -
  • -
- ); -}; diff --git a/dotcom-rendering/src/frontend/feFront.ts b/dotcom-rendering/src/frontend/feFront.ts index a9fcb040380..070e19ccf06 100644 --- a/dotcom-rendering/src/frontend/feFront.ts +++ b/dotcom-rendering/src/frontend/feFront.ts @@ -40,19 +40,6 @@ interface FEPressedPage { /* This list of containers supported in DCR must be kept up to date with frontend **manually**. * @see https://github.com/guardian/frontend/blob/167dce23a8453ed13a97fbd23c7fc45ecb06e3fe/facia/app/services/dotcomrendering/FaciaPicker.scala#L21-L45 */ export type FEContainer = - | 'fixed/large/slow-XIV' - | 'fixed/medium/fast-XI' - | 'fixed/medium/fast-XII' - | 'fixed/medium/slow-VI' - | 'fixed/medium/slow-VII' - | 'fixed/medium/slow-XII-mpu' - | 'fixed/small/fast-VIII' - | 'fixed/small/slow-I' - | 'fixed/small/slow-III' - | 'fixed/small/slow-IV' - | 'fixed/small/slow-V-half' - | 'fixed/small/slow-V-mpu' - | 'fixed/small/slow-V-third' | 'fixed/thrasher' | 'nav/list' | 'nav/media-list' diff --git a/dotcom-rendering/src/frontend/schemas/feFront.json b/dotcom-rendering/src/frontend/schemas/feFront.json index e55d29ae127..a268d690f82 100644 --- a/dotcom-rendering/src/frontend/schemas/feFront.json +++ b/dotcom-rendering/src/frontend/schemas/feFront.json @@ -3393,19 +3393,6 @@ }, "FEContainer": { "enum": [ - "fixed/large/slow-XIV", - "fixed/medium/fast-XI", - "fixed/medium/fast-XII", - "fixed/medium/slow-VI", - "fixed/medium/slow-VII", - "fixed/medium/slow-XII-mpu", - "fixed/small/fast-VIII", - "fixed/small/slow-I", - "fixed/small/slow-III", - "fixed/small/slow-IV", - "fixed/small/slow-V-half", - "fixed/small/slow-V-mpu", - "fixed/small/slow-V-third", "fixed/thrasher", "flexible/general", "flexible/special", diff --git a/dotcom-rendering/src/lib/cardWrappers.tsx b/dotcom-rendering/src/lib/cardWrappers.tsx index 7a70efebecf..6d7d0c893e2 100644 --- a/dotcom-rendering/src/lib/cardWrappers.tsx +++ b/dotcom-rendering/src/lib/cardWrappers.tsx @@ -143,381 +143,21 @@ export const Card100Media75 = ({ ); }; -/** - * ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - * ┃▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒┃ - * ┃▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒┃ - * ┃▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒┃ - * ┃ ┃ - * ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - * Card designed to take up 100% of the container, with media that takes up 75% - * - * Options: - * - Huge headline (large on mobile) - * - Jumbo image on the top (top on mobile) - * - No trail text - * - Up to 4 supporting content items, always aligned horizontal - */ -export const Card100Media100 = ({ - trail, - showAge, - containerPalette, - imageLoading, - isTagPage, - serverTime, - aspectRatio, -}: TrailProps) => { - return ( - - ); -}; - -/** - * ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - * ┃▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒┃ - * ┃▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒┃ - * ┃▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒┃ - * ┃ ┃ - * ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - * Card designed to take up 100% of the container, with media that takes up 75% - * - * Options: - * - Medium headline (large on mobile) - * - Jumbo image on the top (top on mobile) - * - Trail text - * - Up to 2 supporting content items, always aligned vertically - */ - -export const Card100Media100Tall = ({ - trail, - showAge, - containerPalette, - imageLoading, - isTagPage, - serverTime, - aspectRatio, -}: TrailProps) => { - return ( - - ); -}; - -/** - * ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┱┈┈┈┈┈┈┈┈┈┐ - * ┃ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒┃ 25% ┊ - * ┃ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒┃Remaining┊ - * ┃ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒┃ ┊ - * ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━┹┈┈┈┈┈┈┈┈┈┘ - * Card designed to take up 75% of the container, with media that takes up 50% - * - * Options: - * - Large headline (large on mobile) - * - Large image on the right (top on mobile) - * - Trail text - * - Up to 3 supporting content items, 1-2 aligned vertical, 3 aligned horizontal - */ -export const Card75Media50Right = ({ - trail, - showAge, - containerPalette, - imageLoading, - isTagPage, - serverTime, - aspectRatio, -}: TrailProps) => { - return ( - 2 - ? 'horizontal' - : 'vertical' - } - mediaPositionOnDesktop="right" - mediaSize="large" - mediaPositionOnMobile="top" - imageLoading={imageLoading} - isTagPage={isTagPage} - headlineSizes={{ desktop: 'small', tablet: 'xsmall' }} - aspectRatio={aspectRatio} - /> - ); -}; - -/** - * ┌┈┈┈┈┈┈┈┈┈┲━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - * ┊ 25% ┃▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ┃ - * ┊Remaining┃▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ┃ - * ┊ ┃▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ┃ - * └┈┈┈┈┈┈┈┈┈┺━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - * Card designed to take up 75% of the container, with media that takes up 50% - * - * Options: - * - Large headline (large on mobile) - * - Large image on the left (top on mobile) - * - Trail text - * - Up to 3 supporting content items, 1-2 aligned vertical, 3 aligned horizontal - */ -export const Card75Media50Left = ({ - trail, - showAge, - containerPalette, - imageLoading, - isTagPage, - serverTime, - aspectRatio, -}: TrailProps) => { - return ( - 2 - ? 'horizontal' - : 'vertical' - } - mediaPositionOnDesktop="left" - mediaPositionOnMobile="top" - mediaSize="large" - imageLoading={imageLoading} - isTagPage={isTagPage} - headlineSizes={{ desktop: 'small', tablet: 'xsmall' }} - aspectRatio={aspectRatio} - /> - ); -}; - -/** - * ┏━━━━━━━━━┱┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┐ - * ┃▒▒▒▒▒▒▒▒▒┃ 75% ┊ - * ┃▒▒▒▒▒▒▒▒▒┃ Remaining ┊ - * ┃ ┃ ┊ - * ┗━━━━━━━━━┹┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┘ - * Card designed to take up 25% of the container, with media that takes up 25% - * - * Options: - * - Medium headline (medium on mobile) - * - Small image on the top (left on mobile) - * - No trail text - * - Up to 2 supporting content items, always aligned vertical - */ -export const Card25Media25 = ({ - trail, - showAge, - containerPalette, - imageLoading, - isTagPage, - serverTime, - aspectRatio, -}: TrailProps) => { - return ( - - ); -}; - -/** - * ┏━━━━━━━━━┱┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┐ - * ┃▒▒▒▒▒▒▒▒▒┃ 75% ┊ - * ┃▒▒▒▒▒▒▒▒▒┃ Remaining ┊ - * ┃ ┃ ┊ - * ┗━━━━━━━━━┹┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┘ - * Card designed to take up 25% of the container, with media that takes up 25% - * - * Options: - * - Medium headline (medium on mobile) - * - Small image on the top (left on mobile) - * - No trail text - * - Up to 2 supporting content items, always aligned vertical - */ -export const Card25Media25SmallHeadline = ({ - trail, - showAge, - containerPalette, - imageLoading, - isTagPage, - serverTime, - aspectRatio, -}: TrailProps) => { - return ( - - ); -}; - -/** - * ┏━━━━━━━━━┱┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┐ - * ┃▒▒▒▒▒▒▒▒▒┃ ┊ - * ┃▒▒▒▒▒▒▒▒▒┃ 75% ┊ - * ┃ ┃ Remaining ┊ - * ┃ ┃ ┊ - * ┗━━━━━━━━━┹┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┘ - * Card designed to take up 25% of the container, with media that takes up 25% - * - * Options: - * - Medium headline (medium on mobile) - * - Small image on the top (left on mobile) - * - Trail text when there is no supporting content - * - Up to 2 supporting content items, always aligned vertical - */ -export const Card25Media25Tall = ({ - trail, - showAge, - containerPalette, - imageLoading, - isTagPage, - serverTime, - aspectRatio, -}: TrailProps) => { - return ( - - ); -}; - -/** - * ┏━━━━━━━━━┱┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┐ - * ┃▒▒▒▒▒▒▒▒▒┃ ┊ - * ┃▒▒▒▒▒▒▒▒▒┃ 75% ┊ - * ┃ ┃ Remaining ┊ - * ┃ ┃ ┊ - * ┗━━━━━━━━━┹┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┘ - * Card designed to take up 25% of the container, with media that takes up 25% - * - * Options: - * - Medium headline (medium on mobile) - * - Small image on the top (left on mobile) - * - Up to 2 supporting content items, always aligned vertical - */ -export const Card25Media25TallNoTrail = ({ - trail, - showAge, - containerPalette, - imageLoading, - isTagPage, - serverTime, - aspectRatio, -}: TrailProps) => { - return ( - - ); -}; - /** * ┏━━━━━━━━━┱┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┐ - * ┃▒▒▒▒▒▒▒▒▒┃ ┊ * ┃▒▒▒▒▒▒▒▒▒┃ 75% ┊ - * ┃ ┃ Remaining ┊ + * ┃▒▒▒▒▒▒▒▒▒┃ Remaining ┊ * ┃ ┃ ┊ * ┗━━━━━━━━━┹┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┘ * Card designed to take up 25% of the container, with media that takes up 25% * * Options: - * - Small headline (medium on mobile) + * - Medium headline (medium on mobile) * - Small image on the top (left on mobile) + * - No trail text * - Up to 2 supporting content items, always aligned vertical */ -export const Card25Media25TallSmallHeadline = ({ +export const Card25Media25 = ({ trail, showAge, containerPalette, @@ -529,6 +169,8 @@ export const Card25Media25TallSmallHeadline = ({ return ( @@ -587,91 +228,6 @@ export const Card50Media50 = ({ ); }; -/** - * ┏━━━━━━━━━━━━━━━━━━┱┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┐ - * ┃▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒┃ ┊ - * ┃▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒┃ 50% ┊ - * ┃ ┃ Remaining ┊ - * ┃ ┃ ┊ - * ┗━━━━━━━━━━━━━━━━━━┹┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┘ - * Card designed to take up 50% of the container, with media that takes up 50% - * - * Options: - * - Large headline (large on mobile) - * - Medium image on the top (top on mobile) - * - Trail text - * - Up to 3 supporting content items, always aligned horizontal - */ -export const Card50Media50Tall = ({ - trail, - showAge, - containerPalette, - imageLoading, - isTagPage, - serverTime, - aspectRatio, -}: TrailProps) => { - return ( - - ); -}; - -/** - * ┏━━━━━━━━━━━━━━━━━━━━━━━━┱┈┈┈┈┈┈┈┈┈┈┈┈┐ - * ┃▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒┃ 33% ┊ - * ┃▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒┃ Remaining ┊ - * ┃ ┃ ┊ - * ┗━━━━━━━━━━━━━━━━━━━━━━━━┹┈┈┈┈┈┈┈┈┈┈┈┈┘ - * Card designed to take up 66% of the container, with media that takes up 66% - * - * Options: - * - Medium headline (medium on mobile) - * - Large image on the top (top on mobile) - * - Trail text - * - No supporting content - */ -export const Card66Media66 = ({ - trail, - showAge, - containerPalette, - imageLoading, - isTagPage, - serverTime, - aspectRatio, -}: TrailProps) => { - return ( - - ); -}; - /** * ┏━━━━━━━━━━━━┱┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┐ * ┃▒▒▒▒▒▒▒▒▒▒▒▒┃ 66% ┊ @@ -712,90 +268,6 @@ export const Card33Media33 = ({ /> ); }; -/** - * ┏━━━━━━━━━━━━┱┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┐ - * ┃▒▒▒▒▒▒▒▒▒▒▒▒┃ 66% ┊ - * ┃▒▒▒▒▒▒▒▒▒▒▒▒┃ Remaining ┊ - * ┃ ┃ ┊ - * ┗━━━━━━━━━━━━┹┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┘ - * Card designed to take up 33% of the container, with media that takes up 33% - * - * Options: - * - Medium headline (medium on mobile) - * - Medium image on the top (left on mobile) - * - No trail text - * - Up to 2 supporting content items, always aligned vertical - */ -export const Card33Media33Tall = ({ - trail, - showAge, - containerPalette, - imageLoading, - isTagPage, - serverTime, - aspectRatio, -}: TrailProps) => { - return ( - - ); -}; - -/** - * ┏━━━━━━━━━━━━┱┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┐ - * ┃▒▒▒▒▒▒▒▒▒▒▒▒┃ 66% ┊ - * ┃▒▒▒▒▒▒▒▒▒▒▒▒┃ Remaining ┊ - * ┃ ┃ ┊ - * ┗━━━━━━━━━━━━┹┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┘ - * Card designed to take up 33% of the container, with media that takes up 33% - * - * Options: - * - Medium headline (large on mobile) - * - Medium image at the top, including on mobile - * - Trail text - * - Up to 2 supporting content items, always aligned vertical - */ -export const Card33Media33MobileTopTall = ({ - trail, - showAge, - containerPalette, - imageLoading, - isTagPage, - serverTime, - aspectRatio, -}: TrailProps) => { - return ( - - ); -}; /** * ┏━━━━━━━━━━━━━━━━━━┱┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┐ @@ -833,81 +305,3 @@ export const CardDefault = ({ /> ); }; - -/** - * ┏━━━━━━━━━━━━━━━━━━┱┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┐ - * ┃ ▒▒▒▒ ┃ Any% Remaining ┊ - * ┗━━━━━━━━━━━━━━━━━━┹┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┘ - * Card designed to take up any width of container, with small left media - * - * Options: - * - Small headline (small on mobile) - * - Small image on the left (none on mobile) - * - No trail text - * - No supporting content - */ -export const CardDefaultMedia = ({ - trail, - showAge, - containerPalette, - imageLoading, - isTagPage, - serverTime, - aspectRatio, -}: TrailProps) => { - return ( - - ); -}; - -/** - * ┏━━━━━━━━━━━━━━━━━━┱┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┐ - * ┃ ▒▒▒▒ ┃ Any% Remaining ┊ - * ┗━━━━━━━━━━━━━━━━━━┹┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┘ - * Card designed to take up any width of container, with small left media - * - * Options: - * - Small headline (small on mobile) - * - Small image on the left (left on mobile) - * - No trail text - * - No supporting content - */ -export const CardDefaultMediaMobile = ({ - trail, - showAge, - containerPalette, - imageLoading, - isTagPage, - serverTime, - aspectRatio, -}: TrailProps) => { - return ( - - ); -}; diff --git a/dotcom-rendering/src/lib/dynamicSlices.tsx b/dotcom-rendering/src/lib/dynamicSlices.tsx index c64f1b4ade5..a7dbea201b9 100644 --- a/dotcom-rendering/src/lib/dynamicSlices.tsx +++ b/dotcom-rendering/src/lib/dynamicSlices.tsx @@ -1,64 +1,3 @@ -import { LI } from '../components/Card/components/LI'; -import { UL } from '../components/Card/components/UL'; -import type { Loading } from '../components/CardPicture'; -import type { DCRContainerPalette, DCRFrontCard } from '../types/front'; -import { Card25Media25Tall, Card50Media50 } from './cardWrappers'; - -/* ._________________.________.________. - * |#################|########|########| - * | | | | - * |_________________|________|________| - */ -export const Card50_Card25_Card25 = ({ - cards, - containerPalette, - showAge, - serverTime, - imageLoading, -}: { - cards: DCRFrontCard[]; - imageLoading: Loading; - containerPalette?: DCRContainerPalette; - showAge?: boolean; - serverTime?: number; -}) => { - const card50 = cards.slice(0, 1); - const cards25 = cards.slice(1, 3); - - return ( -
    - {card50.map((trail) => ( -
  • - -
  • - ))} - - {cards25.map((trail) => ( -
  • - -
  • - ))} -
- ); -}; - /** * Abstraction to decide whether to show padding on wrapped rows of cards. * diff --git a/dotcom-rendering/src/lib/getFrontsAdPositions.test.ts b/dotcom-rendering/src/lib/getFrontsAdPositions.test.ts index 0eb6f82b616..fb07bbb378e 100644 --- a/dotcom-rendering/src/lib/getFrontsAdPositions.test.ts +++ b/dotcom-rendering/src/lib/getFrontsAdPositions.test.ts @@ -17,7 +17,7 @@ import { } from './getFrontsAdPositions'; const testCollection: AdCandidate = { - collectionType: 'fixed/large/slow-XIV', + collectionType: 'flexible/general', displayName: 'Test Collection', containerLevel: 'Primary', containerPalette: 'EventPalette', @@ -73,18 +73,18 @@ describe('Mobile Ads', () => { // We used https://www.theguardian.com/uk/commentisfree as a blueprint it('Non-network front, with more than 4 collections, without thrashers', () => { const testCollections: AdCandidate[] = [ - { ...testCollection, collectionType: 'fixed/large/slow-XIV' }, // Ad position (0) - { ...testCollection, collectionType: 'fixed/medium/slow-VI' }, - { ...testCollection, collectionType: 'fixed/small/slow-IV' }, // Ad position (2) - { ...testCollection, collectionType: 'fixed/small/slow-IV' }, - { ...testCollection, collectionType: 'fixed/small/slow-IV' }, // Ad position (4) + { ...testCollection, collectionType: 'flexible/general' }, // Ad position (0) + { ...testCollection, collectionType: 'flexible/general' }, + { ...testCollection, collectionType: 'static/medium/4' }, // Ad position (2) + { ...testCollection, collectionType: 'static/medium/4' }, + { ...testCollection, collectionType: 'static/medium/4' }, // Ad position (4) + { ...testCollection, collectionType: 'flexible/general' }, + { ...testCollection, collectionType: 'flexible/general' }, // Ad position (6) { ...testCollection, collectionType: 'flexible/general' }, - { ...testCollection, collectionType: 'fixed/small/slow-I' }, // Ad position (6) - { ...testCollection, collectionType: 'fixed/medium/slow-VI' }, - { ...testCollection, collectionType: 'fixed/small/slow-IV' }, // Ad position (8) - { ...testCollection, collectionType: 'fixed/small/slow-III' }, - { ...testCollection, collectionType: 'fixed/medium/fast-XII' }, // Ignored - is before merch high position - { ...testCollection, collectionType: 'fixed/small/fast-VIII' }, // Ignored - is merch high position + { ...testCollection, collectionType: 'static/medium/4' }, // Ad position (8) + { ...testCollection, collectionType: 'flexible/general' }, + { ...testCollection, collectionType: 'flexible/general' }, // Ignored - is before merch high position + { ...testCollection, collectionType: 'flexible/general' }, // Ignored - is merch high position { ...testCollection, collectionType: 'news/most-popular' }, // Ignored - is most viewed container ]; @@ -97,28 +97,28 @@ describe('Mobile Ads', () => { it('UK Network Front, with more than 4 collections, with thrashers at various places', () => { const testCollections: AdCandidate[] = [ { ...testCollection, collectionType: 'flexible/general' }, // Ad position (0) - { ...testCollection, collectionType: 'fixed/small/slow-IV' }, + { ...testCollection, collectionType: 'static/medium/4' }, { ...testCollection, collectionType: 'flexible/special' }, // Ad position (2) { ...testCollection, collectionType: 'flexible/special' }, - { ...testCollection, collectionType: 'fixed/small/slow-V-mpu' }, // Ad position (4) + { ...testCollection, collectionType: 'static/medium/4' }, // Ad position (4) { ...testCollection, collectionType: 'flexible/special' }, // Ignored - before thrasher { ...testCollection, collectionType: 'fixed/thrasher' }, - { ...testCollection, collectionType: 'fixed/small/slow-IV' }, // Ignored - before thrasher + { ...testCollection, collectionType: 'static/medium/4' }, // Ignored - before thrasher { ...testCollection, collectionType: 'fixed/thrasher' }, // Ad position (8) { ...testCollection, collectionType: 'flexible/general' }, { ...testCollection, collectionType: 'flexible/general' }, // Ignored - before thrasher { ...testCollection, collectionType: 'fixed/thrasher' }, // Ad position (11) { ...testCollection, collectionType: 'flexible/general' }, - { ...testCollection, collectionType: 'fixed/small/slow-IV' }, // Ignored - before thrasher + { ...testCollection, collectionType: 'static/medium/4' }, // Ignored - before thrasher { ...testCollection, collectionType: 'fixed/thrasher' }, // Ad position (14) { ...testCollection, collectionType: 'flexible/general' }, // Ignored - before thrasher { ...testCollection, collectionType: 'fixed/thrasher' }, { ...testCollection, collectionType: 'scrollable/feature' }, // Ad position (17) - { ...testCollection, collectionType: 'fixed/small/slow-IV' }, - { ...testCollection, collectionType: 'fixed/small/slow-IV' }, // Ad position (19) + { ...testCollection, collectionType: 'static/medium/4' }, + { ...testCollection, collectionType: 'static/medium/4' }, // Ad position (19) { ...testCollection, collectionType: 'flexible/general' }, - { ...testCollection, collectionType: 'fixed/small/slow-IV' }, // Ignored - is before merch high position - { ...testCollection, collectionType: 'fixed/medium/slow-VI' }, // Ignored - is merch high position + { ...testCollection, collectionType: 'static/medium/4' }, // Ignored - is before merch high position + { ...testCollection, collectionType: 'flexible/general' }, // Ignored - is merch high position { ...testCollection, collectionType: 'news/most-popular' }, // Ignored - is most viewed container ]; @@ -131,24 +131,24 @@ describe('Mobile Ads', () => { it('International Network Front, with more than 4 collections, with thrashers at various places', () => { const testCollections: AdCandidate[] = [ { ...testCollection, collectionType: 'flexible/general' }, // Ad position (0) - { ...testCollection, collectionType: 'fixed/small/slow-IV' }, + { ...testCollection, collectionType: 'static/medium/4' }, { ...testCollection, collectionType: 'flexible/special' }, // Ad position (2) { ...testCollection, collectionType: 'flexible/general' }, { ...testCollection, collectionType: 'flexible/special' }, // Ignored - before thrasher { ...testCollection, collectionType: 'fixed/thrasher' }, // Ad position (5) - { ...testCollection, collectionType: 'fixed/small/slow-IV' }, + { ...testCollection, collectionType: 'static/medium/4' }, { ...testCollection, collectionType: 'flexible/general' }, // Ad position (7) - { ...testCollection, collectionType: 'fixed/small/slow-IV' }, // Ignored - before thrasher + { ...testCollection, collectionType: 'static/medium/4' }, // Ignored - before thrasher { ...testCollection, collectionType: 'fixed/thrasher' }, { ...testCollection, collectionType: 'flexible/general' }, // Ignored - before thrasher { ...testCollection, collectionType: 'fixed/thrasher' }, // Ad position (11) { ...testCollection, collectionType: 'flexible/general' }, // Ignored - before thrasher { ...testCollection, collectionType: 'fixed/thrasher' }, { ...testCollection, collectionType: 'flexible/general' }, // Ad position (14) - { ...testCollection, collectionType: 'fixed/small/slow-IV' }, - { ...testCollection, collectionType: 'fixed/small/slow-IV' }, // Ad position (16) + { ...testCollection, collectionType: 'static/medium/4' }, + { ...testCollection, collectionType: 'static/medium/4' }, // Ad position (16) { ...testCollection, collectionType: 'scrollable/feature' }, - { ...testCollection, collectionType: 'fixed/medium/slow-VI' }, // Ignored - is merch high position + { ...testCollection, collectionType: 'flexible/general' }, // Ignored - is merch high position { ...testCollection, collectionType: 'news/most-popular' }, // Ignored - is most viewed container ]; @@ -161,24 +161,24 @@ describe('Mobile Ads', () => { it('US Network Front, with more than 4 collections, with thrashers at various places', () => { const testCollections: AdCandidate[] = [ { ...testCollection, collectionType: 'flexible/general' }, // Ad position (0) - { ...testCollection, collectionType: 'fixed/small/slow-IV' }, - { ...testCollection, collectionType: 'fixed/small/slow-IV' }, // Ad position (2) + { ...testCollection, collectionType: 'static/medium/4' }, + { ...testCollection, collectionType: 'static/medium/4' }, // Ad position (2) { ...testCollection, collectionType: 'flexible/general' }, // Ignored - before thrasher { ...testCollection, collectionType: 'fixed/thrasher' }, { ...testCollection, collectionType: 'flexible/special' }, // Ad position (5) { ...testCollection, collectionType: 'flexible/special' }, // Ignored - before thrasher { ...testCollection, collectionType: 'fixed/thrasher' }, - { ...testCollection, collectionType: 'fixed/small/slow-III' }, // Ignored - before thrasher + { ...testCollection, collectionType: 'flexible/general' }, // Ignored - before thrasher { ...testCollection, collectionType: 'fixed/thrasher' }, // Ad position (9) - { ...testCollection, collectionType: 'fixed/small/slow-IV' }, + { ...testCollection, collectionType: 'static/medium/4' }, { ...testCollection, collectionType: 'flexible/general' }, // Ignored - before thrasher { ...testCollection, collectionType: 'fixed/thrasher' }, // Ad position (12) - { ...testCollection, collectionType: 'fixed/medium/slow-VI' }, + { ...testCollection, collectionType: 'flexible/general' }, { ...testCollection, collectionType: 'flexible/general' }, // Ad position (14) { ...testCollection, collectionType: 'flexible/general' }, - { ...testCollection, collectionType: 'fixed/small/slow-V-mpu' }, // Ad position (16) + { ...testCollection, collectionType: 'static/medium/4' }, // Ad position (16) { ...testCollection, collectionType: 'scrollable/feature' }, - { ...testCollection, collectionType: 'fixed/small/slow-III' }, // Ignored - before thrasher + { ...testCollection, collectionType: 'flexible/general' }, // Ignored - before thrasher { ...testCollection, collectionType: 'fixed/thrasher' }, // Ignored - is merch high position { ...testCollection, collectionType: 'news/most-popular' }, // Ignored - is most viewed container ]; @@ -192,20 +192,20 @@ describe('Mobile Ads', () => { it('Lifeandstyle front, with more than 4 collections, with thrashers at various places', () => { const testCollections: AdCandidate[] = [ { ...testCollection, collectionType: 'flexible/special' }, // Ad position (0) - { ...testCollection, collectionType: 'fixed/medium/slow-VI' }, // Ignored - before thrasher + { ...testCollection, collectionType: 'flexible/general' }, // Ignored - before thrasher { ...testCollection, collectionType: 'fixed/thrasher' }, - { ...testCollection, collectionType: 'fixed/medium/slow-VI' }, // Ad position (3) - { ...testCollection, collectionType: 'fixed/small/slow-V-third' }, - { ...testCollection, collectionType: 'fixed/small/slow-IV' }, // Ignored - before thrasher + { ...testCollection, collectionType: 'flexible/general' }, // Ad position (3) + { ...testCollection, collectionType: 'flexible/general' }, + { ...testCollection, collectionType: 'static/medium/4' }, // Ignored - before thrasher { ...testCollection, collectionType: 'fixed/thrasher' }, // Ad position (6) - { ...testCollection, collectionType: 'fixed/small/slow-IV' }, // Ignored - before thrasher + { ...testCollection, collectionType: 'static/medium/4' }, // Ignored - before thrasher { ...testCollection, collectionType: 'fixed/thrasher' }, - { ...testCollection, collectionType: 'fixed/medium/slow-VI' }, // Ad position (9) - { ...testCollection, collectionType: 'fixed/medium/slow-XII-mpu' }, - { ...testCollection, collectionType: 'fixed/small/fast-VIII' }, // Ignored - before thrasher + { ...testCollection, collectionType: 'flexible/general' }, // Ad position (9) + { ...testCollection, collectionType: 'flexible/general' }, + { ...testCollection, collectionType: 'flexible/general' }, // Ignored - before thrasher { ...testCollection, collectionType: 'fixed/thrasher' }, // Ad position (12) - { ...testCollection, collectionType: 'fixed/small/slow-III' }, - { ...testCollection, collectionType: 'fixed/small/slow-I' }, // Ignored - is merch high position + { ...testCollection, collectionType: 'flexible/general' }, + { ...testCollection, collectionType: 'flexible/general' }, // Ignored - is merch high position { ...testCollection, collectionType: 'news/most-popular' }, // Ignored - is most viewed container ]; @@ -218,18 +218,18 @@ describe('Mobile Ads', () => { it('Recipes front, with more than 4 collections, with thrasher at the first position', () => { const testCollections: AdCandidate[] = [ { ...testCollection, collectionType: 'fixed/thrasher' }, // Ignored - is first container and thrasher - { ...testCollection, collectionType: 'fixed/medium/slow-VI' }, // Ad position (1) - { ...testCollection, collectionType: 'fixed/small/slow-V-third' }, - { ...testCollection, collectionType: 'fixed/medium/slow-XII-mpu' }, // Ad position (3) - { ...testCollection, collectionType: 'fixed/medium/fast-XII' }, - { ...testCollection, collectionType: 'fixed/small/slow-V-third' }, // Ad position (5) - { ...testCollection, collectionType: 'fixed/small/fast-VIII' }, - { ...testCollection, collectionType: 'fixed/medium/fast-XI' }, // Ad position (7) - { ...testCollection, collectionType: 'fixed/small/slow-III' }, - { ...testCollection, collectionType: 'fixed/small/slow-IV' }, // Ad position (9) - { ...testCollection, collectionType: 'fixed/small/slow-V-half' }, - { ...testCollection, collectionType: 'fixed/small/slow-V-third' }, // Ignored - is before merch high position - { ...testCollection, collectionType: 'fixed/small/fast-VIII' }, // Ignored - is merch high position + { ...testCollection, collectionType: 'flexible/general' }, // Ad position (1) + { ...testCollection, collectionType: 'flexible/general' }, + { ...testCollection, collectionType: 'flexible/general' }, // Ad position (3) + { ...testCollection, collectionType: 'flexible/general' }, + { ...testCollection, collectionType: 'flexible/general' }, // Ad position (5) + { ...testCollection, collectionType: 'flexible/general' }, + { ...testCollection, collectionType: 'flexible/general' }, // Ad position (7) + { ...testCollection, collectionType: 'flexible/general' }, + { ...testCollection, collectionType: 'static/medium/4' }, // Ad position (9) + { ...testCollection, collectionType: 'flexible/general' }, + { ...testCollection, collectionType: 'flexible/general' }, // Ignored - is before merch high position + { ...testCollection, collectionType: 'flexible/general' }, // Ignored - is merch high position { ...testCollection, collectionType: 'news/most-popular' }, // Ignored - is most viewed container ]; @@ -350,13 +350,13 @@ describe('Desktop Ads', () => { it('calculates ad positions correctly for an example of the UK network front', () => { const adPositions = getDesktopAdPositions(testCollectionsUk, 'uk'); - expect(adPositions).toEqual([3, 6, 8, 11, 14, 17, 20]); + expect(adPositions).toEqual([3, 6, 8, 14, 17]); }); it('calculates ad positions correctly for an example of the US network front', () => { const adPositions = getDesktopAdPositions(testCollectionsUs, 'us'); - expect(adPositions).toEqual([3, 6, 8, 11, 14, 18, 20, 22]); + expect(adPositions).toEqual([3, 6, 10, 12, 19]); }); it('does NOT insert ads above or below branded content', () => { diff --git a/dotcom-rendering/src/lib/getFrontsAdPositions.ts b/dotcom-rendering/src/lib/getFrontsAdPositions.ts index 40c123de1e1..e6fca6bc194 100644 --- a/dotcom-rendering/src/lib/getFrontsAdPositions.ts +++ b/dotcom-rendering/src/lib/getFrontsAdPositions.ts @@ -260,8 +260,6 @@ const getCollectionHeight = (collection: AdCandidate): number => { case 'fixed/thrasher': return 0.5; - case 'fixed/small/slow-IV': - case 'fixed/small/slow-V-mpu': case 'nav/list': case 'nav/media-list': case 'scrollable/small': @@ -269,25 +267,12 @@ const getCollectionHeight = (collection: AdCandidate): number => { case 'static/medium/4': return 1; - case 'fixed/small/slow-I': - case 'fixed/small/slow-III': - case 'fixed/small/slow-V-third': - case 'fixed/small/slow-V-half': - case 'fixed/small/fast-VIII': case 'scrollable/feature': return 1.5; - case 'fixed/medium/slow-VI': - case 'fixed/medium/slow-VII': - case 'fixed/medium/slow-XII-mpu': - case 'fixed/medium/fast-XI': - case 'fixed/medium/fast-XII': case 'static/feature/2': return 2; - case 'fixed/large/slow-XIV': - return 3; - case 'flexible/special': return getFlexibleSpecialHeight(grouped); From db229481f7c0b78b66a5a1d9c60b2de59ebe1069 Mon Sep 17 00:00:00 2001 From: Dominik Lander Date: Wed, 11 Mar 2026 15:48:07 +0000 Subject: [PATCH 19/19] Remove 'show more' functionality from containers --- .../fixtures/manual/show-more-trails.ts | 1741 ----------------- .../components/Accessibility.importable.tsx | 2 +- .../src/components/Card/Card.stories.tsx | 3 +- .../DecideContainerByTrails.stories.tsx | 118 +- .../src/components/ElementContainer.tsx | 4 - .../components/FlexibleGeneral.stories.tsx | 46 +- .../components/FlexibleSpecial.stories.tsx | 6 +- .../src/components/FrontSection.stories.tsx | 36 +- .../src/components/FrontSection.tsx | 53 - .../src/components/NavList.stories.tsx | 13 +- .../src/components/Palettes.stories.tsx | 2 - .../components/ScrollableFeature.stories.tsx | 11 +- .../components/ScrollableMedium.stories.tsx | 4 - .../components/ScrollableSmall.stories.tsx | 8 +- .../src/components/ShowMore.importable.tsx | 256 --- .../src/components/ShowMore.stories.tsx | 73 - .../components/StaticFeatureTwo.stories.tsx | 10 +- .../components/StaticMediumFour.stories.tsx | 3 - dotcom-rendering/src/frontend/feFront.ts | 4 +- .../src/frontend/schemas/feFront.json | 2 - dotcom-rendering/src/layouts/FrontLayout.tsx | 7 - .../src/layouts/TagPageLayout.tsx | 7 +- .../src/lib/dynamicSlices.test.ts | 113 -- dotcom-rendering/src/lib/dynamicSlices.tsx | 36 - .../src/model/enhanceCollections.ts | 7 +- dotcom-rendering/src/types/front.ts | 7 - 26 files changed, 48 insertions(+), 2524 deletions(-) delete mode 100644 dotcom-rendering/fixtures/manual/show-more-trails.ts delete mode 100644 dotcom-rendering/src/components/ShowMore.importable.tsx delete mode 100644 dotcom-rendering/src/components/ShowMore.stories.tsx delete mode 100644 dotcom-rendering/src/lib/dynamicSlices.test.ts delete mode 100644 dotcom-rendering/src/lib/dynamicSlices.tsx diff --git a/dotcom-rendering/fixtures/manual/show-more-trails.ts b/dotcom-rendering/fixtures/manual/show-more-trails.ts deleted file mode 100644 index 25c18a8840a..00000000000 --- a/dotcom-rendering/fixtures/manual/show-more-trails.ts +++ /dev/null @@ -1,1741 +0,0 @@ -/** - * This is the response as it's received from Frontend, and includes fields that - * the DCR types don't know about. - * Exporting this 'as' FEFrontCard because we know it should be of the right shape. - */ - -import type { FEFrontCard } from '../../src/frontend/feFront'; - -export const trails: [ - FEFrontCard, - FEFrontCard, - FEFrontCard, - FEFrontCard, - FEFrontCard, - FEFrontCard, -] = [ - { - properties: { - isBreaking: false, - mediaSelect: { - showMainVideo: false, - imageSlideshowReplace: false, - videoReplace: false, - }, - showKickerTag: false, - showByline: false, - maybeContent: { - trail: { - trailPicture: { - allImages: [ - { - index: 3, - fields: { - displayCredit: 'true', - isMaster: 'true', - altText: - 'Woman stressed about big ladder in her tights', - mediaId: - 'a85912b00046f8ba0314e9064a0851ea17ef76d2', - width: '2124', - source: 'Getty', - photographer: 'artursfoto', - height: '1274', - credit: 'Photograph: artursfoto/Getty', - }, - mediaType: 'Image', - url: 'https://media.guim.co.uk/a85912b00046f8ba0314e9064a0851ea17ef76d2/0_12_2124_1274/master/2124.jpg', - }, - ], - }, - byline: 'Chloe Mac Donnell', - thumbnailPath: - 'https://i.guim.co.uk/img/media/a85912b00046f8ba0314e9064a0851ea17ef76d2/0_12_2124_1274/500.jpg?quality=85&auto=format&fit=max&s=e6cf84c0603adefcde825d59df704d28', - webPublicationDate: 1666953304000, - }, - metadata: { - id: 'fashion/2022/oct/28/forever-tights-idea-really-have-legs-rip-resistant-fasion', - webTitle: - 'Forever tights? Now that’s an idea that could really have legs', - webUrl: 'https://www.theguardian.com/fashion/2022/oct/28/forever-tights-idea-really-have-legs-rip-resistant-fasion', - type: 'Article', - - sectionId: { - value: 'fashion', - }, - format: { - design: 'FeatureDesign', - theme: 'LifestylePillar', - display: 'StandardDisplay', - }, - }, - fields: { - main: '
Woman stressed about big ladder in her tights
The average woman spends £3,000 on tights in her lifetime. Photograph: artursfoto/Getty
', - body: '

Would you pay £62 for a pair of tights? What about if they were guaranteed not to rip, ladder or snag for at least three months? For anyone who frequently screams in frustration as they tear a pair while pulling them on, the world’s toughest tights – as they are being described – could prove tempting.

To tight or not to tight used to be a subject as divisive as the Conservatives’ economic policy. With energy bills rising, trying to emulate the year round bare-legged lifestyle of the 1% is no longer viable.

', - standfirst: - '

As the UK gets chillier, rip-resistant tights – embraced even by fashion brands – are coming into their own

', - }, - elements: { - mediaAtoms: [], - }, - tags: { - tags: [ - { - properties: { - id: 'fashion/tights', - url: '/fashion/tights', - tagType: 'Keyword', - sectionId: 'fashion', - sectionName: 'Fashion', - webTitle: 'Tights and socks', - webUrl: 'https://www.theguardian.com/fashion/tights', - }, - }, - { - properties: { - id: 'fashion/fashion', - url: '/fashion/fashion', - tagType: 'Keyword', - sectionId: 'fashion', - sectionName: 'Fashion', - webTitle: 'Fashion', - webUrl: 'https://www.theguardian.com/fashion/fashion', - }, - }, - { - properties: { - id: 'lifeandstyle/lifeandstyle', - url: '/lifeandstyle/lifeandstyle', - tagType: 'Keyword', - sectionId: 'lifeandstyle', - sectionName: 'Life and style', - webTitle: 'Life and style', - webUrl: 'https://www.theguardian.com/lifeandstyle/lifeandstyle', - }, - }, - { - properties: { - id: 'fashion/lingerie', - url: '/fashion/lingerie', - tagType: 'Keyword', - sectionId: 'fashion', - sectionName: 'Fashion', - webTitle: 'Lingerie', - webUrl: 'https://www.theguardian.com/fashion/lingerie', - }, - }, - { - properties: { - id: 'type/article', - url: '/type/article', - tagType: 'Type', - sectionId: 'global', - sectionName: 'global', - webTitle: 'Article', - webUrl: 'https://www.theguardian.com/articles', - }, - }, - { - properties: { - id: 'tone/news', - url: '/tone/news', - tagType: 'Tone', - sectionId: 'global', - sectionName: 'global', - webTitle: 'News', - webUrl: 'https://www.theguardian.com/tone/news', - }, - }, - { - properties: { - id: 'tone/features', - url: '/tone/features', - tagType: 'Tone', - sectionId: 'global', - sectionName: 'global', - webTitle: 'Features', - webUrl: 'https://www.theguardian.com/tone/features', - }, - }, - { - properties: { - id: 'profile/chloe-mac-donnell', - url: '/profile/chloe-mac-donnell', - tagType: 'Contributor', - sectionId: 'global', - sectionName: 'global', - webTitle: 'Chloe Mac Donnell', - webUrl: 'https://www.theguardian.com/profile/chloe-mac-donnell', - twitterHandle: 'tweetchloe', - bio: '

Chloe Mac Donnell is the Guardian\'s deputy fashion and lifestyle editor. Twitter @tweetchloe

', - }, - }, - { - properties: { - id: 'publication/theguardian', - url: '/publication/theguardian', - tagType: 'Publication', - sectionId: 'theguardian', - sectionName: 'From the Guardian', - webTitle: 'The Guardian', - webUrl: 'https://www.theguardian.com/theguardian/all', - }, - }, - { - properties: { - id: 'theguardian/mainsection', - url: '/theguardian/mainsection', - tagType: 'NewspaperBook', - sectionId: 'news', - sectionName: 'News', - webTitle: 'Main section', - webUrl: 'https://www.theguardian.com/theguardian/mainsection', - }, - }, - { - properties: { - id: 'theguardian/mainsection/uknews', - url: '/theguardian/mainsection/uknews', - tagType: 'NewspaperBookSection', - sectionId: 'uk-news', - sectionName: 'UK news', - webTitle: 'UK news', - webUrl: 'https://www.theguardian.com/theguardian/mainsection/uknews', - }, - }, - { - properties: { - id: 'tracking/commissioningdesk/uk-fashion', - url: '/tracking/commissioningdesk/uk-fashion', - tagType: 'Tracking', - sectionId: 'global', - sectionName: 'global', - webTitle: 'UK Fashion', - webUrl: 'https://www.theguardian.com/tracking/commissioningdesk/uk-fashion', - }, - }, - ], - }, - }, - maybeContentId: - 'fashion/2022/oct/28/forever-tights-idea-really-have-legs-rip-resistant-fasion', - isLiveBlog: false, - isCrossword: false, - byline: 'Chloe Mac Donnell', - webTitle: - 'Forever tights? Now that’s an idea that could really have legs', - linkText: - 'Forever tights? Now that’s an idea that could really have legs', - webUrl: 'https://www.theguardian.com/fashion/2022/oct/28/forever-tights-idea-really-have-legs-rip-resistant-fasion', - editionBrandings: [ - { - edition: { - id: 'UK', - }, - }, - { - edition: { - id: 'US', - }, - }, - { - edition: { - id: 'AU', - }, - }, - { - edition: { - id: 'INT', - }, - }, - ], - }, - header: { - isVideo: false, - isComment: false, - isGallery: false, - isAudio: false, - kicker: { - type: 'FreeHtmlKicker', - item: { - properties: { - kickerText: 'For ever tights? ', - }, - }, - }, - headline: 'Now that’s an idea that could really have legs', - url: '/fashion/2022/oct/28/forever-tights-idea-really-have-legs-rip-resistant-fasion', - hasMainVideoElement: false, - }, - card: { - id: 'fashion/2022/oct/28/forever-tights-idea-really-have-legs-rip-resistant-fasion', - cardStyle: { - type: 'Feature', - }, - webPublicationDateOption: 1666953304000, - lastModifiedOption: 1667012469000, - trailText: - 'As the UK gets chillier, rip-resistant tights – embraced even by fashion brands – are coming into their own', - shortUrlPath: '/p/mhet4', - shortUrl: 'https://www.theguardian.com/p/mhet4', - group: '0', - isLive: false, - }, - discussion: { - isCommentable: false, - isClosedForComments: true, - discussionId: '/p/mhet4', - }, - display: { - isBoosted: false, - boostLevel: 'default', - showBoostedHeadline: false, - showQuotedHeadline: false, - imageHide: false, - showLivePlayable: false, - }, - format: { - design: 'FeatureDesign', - theme: 'LifestylePillar', - display: 'StandardDisplay', - }, - enriched: {}, - supportingContent: [], - cardStyle: { - type: 'Feature', - }, - type: 'CuratedContent', - }, - { - properties: { - isBreaking: false, - mediaSelect: { - showMainVideo: false, - imageSlideshowReplace: false, - videoReplace: false, - }, - showKickerTag: false, - showByline: true, - maybeContent: { - trail: { - trailPicture: { - allImages: [ - { - index: 3, - fields: { - displayCredit: 'true', - isMaster: 'true', - altText: - 'USA. Harris Dickinson in a scene from (C)Neon new film : Triangle of Sadness (2022). Plot: A cruise for the super-rich sinks thus leaving survivors, including a fashion model celebrity couple, trapped on an island. Ref: LMK110-J8273-190822 Supplied by LMKMEDIA. Editorial Only. Landmark Media is not the copyright owner of these Film or TV stills but provides a service only for recognised Media outlets. pictures@lmkmedia.com
2JPPN2Y USA. Harris Dickinson in a scene from (C)Neon new film : Triangle of Sadness (2022). Plot: A cruise for the super-rich sinks thus leaving survivors, including a fashion model celebrity couple, trapped on an island. Ref: LMK110-J8273-190822 Supplied by LMKMEDIA. Editorial Only. Landmark Media is not the copyright owner of these Film or TV stills but provides a service only for recognised Media outlets. pictures@lmkmedia.com', - mediaId: - 'c669f6300e637c6eac5b26fd3c3a0eda4b475126', - width: '4454', - source: 'Alamy', - photographer: 'Landmark Media', - height: '2674', - credit: 'Photograph: Landmark Media/Alamy', - }, - mediaType: 'Image', - url: 'https://media.guim.co.uk/c669f6300e637c6eac5b26fd3c3a0eda4b475126/473_0_4454_2674/master/4454.jpg', - }, - ], - }, - byline: 'Morwenna Ferrier', - thumbnailPath: - 'https://i.guim.co.uk/img/media/c669f6300e637c6eac5b26fd3c3a0eda4b475126/473_0_4454_2674/500.jpg?quality=85&auto=format&fit=max&s=e8979943964b29a8c95f7eef77d67885', - webPublicationDate: 1666958427000, - }, - metadata: { - id: 'commentisfree/2022/oct/28/triangle-of-sadness-fashion-industry-ruben-ostlund-palm-dor', - webTitle: - 'Rolexes, influencers and H&M grins: Triangle of Sadness nails the fashion industry | Morwenna Ferrier', - webUrl: 'https://www.theguardian.com/commentisfree/2022/oct/28/triangle-of-sadness-fashion-industry-ruben-ostlund-palm-dor', - type: 'Article', - sectionId: { - value: 'commentisfree', - }, - format: { - design: 'CommentDesign', - theme: 'OpinionPillar', - display: 'StandardDisplay', - }, - }, - fields: { - main: '
Harris Dickinson (centre) as Carl in Triangle of Sadness.
Harris Dickinson (centre) as Carl in Triangle of Sadness. Photograph: Landmark Media/Alamy
', - body: '

The picture painted of the fashion world in Triangle of Sadness, Ruben Östlund’s Palme d’Or-winning film, is not a kind one. Its title refers to a pair of frown lines that sit between the eyebrows. It’s one of the most Botoxed areas of a person’s face, probably because (as the name suggests) they only appear when you’re unhappy, and no one wants that.

This particular triangle belongs to Carl (Harris Dickinson), an ageing (he’s 24) male model with soft strawberry hair and a gentle pout. We meet Carl at a casting for an unknown fashion brand; as soon as he’s out of earshot, the panel discuss whether his triangle needs Botox. Old, unhappy Carl is unable to smooth his out.

', - standfirst: - '

As a fashion journalist, I recognised the precarity and cruelty on show in Ruben Östlund’s Palm d’Or winner

', - }, - elements: { - mediaAtoms: [], - }, - tags: { - tags: [ - { - properties: { - id: 'commentisfree/commentisfree', - url: '/commentisfree/commentisfree', - tagType: 'Blog', - sectionId: 'commentisfree', - sectionName: 'Opinion', - webTitle: 'Opinion', - webUrl: 'https://www.theguardian.com/commentisfree/commentisfree', - }, - }, - { - properties: { - id: 'fashion/fashion-industry', - url: '/fashion/fashion-industry', - tagType: 'Keyword', - sectionId: 'fashion', - sectionName: 'Fashion', - webTitle: 'Fashion industry', - webUrl: 'https://www.theguardian.com/fashion/fashion-industry', - }, - }, - { - properties: { - id: 'fashion/fashion', - url: '/fashion/fashion', - tagType: 'Keyword', - sectionId: 'fashion', - sectionName: 'Fashion', - webTitle: 'Fashion', - webUrl: 'https://www.theguardian.com/fashion/fashion', - }, - }, - { - properties: { - id: 'lifeandstyle/lifeandstyle', - url: '/lifeandstyle/lifeandstyle', - tagType: 'Keyword', - sectionId: 'lifeandstyle', - sectionName: 'Life and style', - webTitle: 'Life and style', - webUrl: 'https://www.theguardian.com/lifeandstyle/lifeandstyle', - }, - }, - { - properties: { - id: 'film/film', - url: '/film/film', - tagType: 'Keyword', - sectionId: 'film', - sectionName: 'Film', - webTitle: 'Film', - webUrl: 'https://www.theguardian.com/film/film', - }, - }, - { - properties: { - id: 'culture/culture', - url: '/culture/culture', - tagType: 'Keyword', - sectionId: 'culture', - sectionName: 'Culture', - webTitle: 'Culture', - webUrl: 'https://www.theguardian.com/culture/culture', - }, - }, - { - properties: { - id: 'fashion/models', - url: '/fashion/models', - tagType: 'Keyword', - sectionId: 'fashion', - sectionName: 'Fashion', - webTitle: 'Models', - webUrl: 'https://www.theguardian.com/fashion/models', - }, - }, - { - properties: { - id: 'type/article', - url: '/type/article', - tagType: 'Type', - sectionId: 'global', - sectionName: 'global', - webTitle: 'Article', - webUrl: 'https://www.theguardian.com/articles', - }, - }, - { - properties: { - id: 'tone/comment', - url: '/tone/comment', - tagType: 'Tone', - sectionId: 'global', - sectionName: 'global', - webTitle: 'Comment', - webUrl: 'https://www.theguardian.com/tone/comment', - }, - }, - { - properties: { - id: 'profile/morwennaferrier', - url: '/profile/morwennaferrier', - tagType: 'Contributor', - sectionId: 'global', - sectionName: 'global', - webTitle: 'Morwenna Ferrier', - webUrl: 'https://www.theguardian.com/profile/morwennaferrier', - bio: '

Morwenna Ferrier is the Guardian’s fashion and lifestyle editor

', - contributorLargeImagePath: - 'https://uploads.guim.co.uk/2017/10/09/Morwenna-Ferrier,-L.png', - bylineImageUrl: - 'https://static.guim.co.uk/sys-images/Guardian/Pix/contributor/2014/10/15/1413394164362/Morwenna-Ferrier.jpg', - }, - }, - { - properties: { - id: 'publication/theguardian', - url: '/publication/theguardian', - tagType: 'Publication', - sectionId: 'theguardian', - sectionName: 'From the Guardian', - webTitle: 'The Guardian', - webUrl: 'https://www.theguardian.com/theguardian/all', - }, - }, - { - properties: { - id: 'theguardian/journal', - url: '/theguardian/journal', - tagType: 'NewspaperBook', - sectionId: 'theguardian', - sectionName: 'From the Guardian', - webTitle: 'Journal', - webUrl: 'https://www.theguardian.com/theguardian/journal', - }, - }, - { - properties: { - id: 'theguardian/journal/opinion', - url: '/theguardian/journal/opinion', - tagType: 'NewspaperBookSection', - sectionId: 'theguardian', - sectionName: 'From the Guardian', - webTitle: 'Opinion', - webUrl: 'https://www.theguardian.com/theguardian/journal/opinion', - }, - }, - { - properties: { - id: 'tracking/commissioningdesk/uk-opinion', - url: '/tracking/commissioningdesk/uk-opinion', - tagType: 'Tracking', - sectionId: 'global', - sectionName: 'global', - webTitle: 'UK Opinion', - webUrl: 'https://www.theguardian.com/tracking/commissioningdesk/uk-opinion', - }, - }, - ], - }, - }, - maybeContentId: - 'commentisfree/2022/oct/28/triangle-of-sadness-fashion-industry-ruben-ostlund-palm-dor', - isLiveBlog: false, - isCrossword: false, - byline: 'Morwenna Ferrier', - image: { - type: 'Cutout', - item: { - imageSrc: - 'https://uploads.guim.co.uk/2017/10/09/Morwenna-Ferrier,-L.png', - }, - }, - webTitle: - 'Rolexes, influencers and H&M grins: Triangle of Sadness nails the fashion industry | Morwenna Ferrier', - linkText: - 'Rolexes, influencers and H&M grins: Triangle of Sadness nails the fashion industry | Morwenna Ferrier', - webUrl: 'https://www.theguardian.com/commentisfree/2022/oct/28/triangle-of-sadness-fashion-industry-ruben-ostlund-palm-dor', - editionBrandings: [ - { - edition: { - id: 'UK', - }, - }, - { - edition: { - id: 'US', - }, - }, - { - edition: { - id: 'AU', - }, - }, - { - edition: { - id: 'INT', - }, - }, - ], - }, - header: { - isVideo: false, - isComment: true, - isGallery: false, - isAudio: false, - headline: - 'Rolexes, influencers and H&M grins: Triangle of Sadness nails the fashion industry', - url: '/commentisfree/2022/oct/28/triangle-of-sadness-fashion-industry-ruben-ostlund-palm-dor', - hasMainVideoElement: false, - }, - card: { - id: 'commentisfree/2022/oct/28/triangle-of-sadness-fashion-industry-ruben-ostlund-palm-dor', - cardStyle: { - type: 'Comment', - }, - webPublicationDateOption: 1666958427000, - lastModifiedOption: 1666990950000, - trailText: - 'As a fashion journalist, I recognised the cruelty on show in Ruben Östlund’s film, says Morwenna Ferrier, assistant editor on the Guardian’s Saturday magazine', - shortUrlPath: '/p/mgkpz', - shortUrl: 'https://www.theguardian.com/p/mgkpz', - group: '0', - isLive: false, - }, - discussion: { - isCommentable: true, - isClosedForComments: true, - discussionId: '/p/mgkpz', - }, - display: { - isBoosted: false, - boostLevel: 'default', - showBoostedHeadline: false, - showQuotedHeadline: true, - imageHide: false, - showLivePlayable: false, - }, - format: { - design: 'CommentDesign', - theme: 'OpinionPillar', - display: 'StandardDisplay', - }, - enriched: {}, - supportingContent: [], - cardStyle: { - type: 'Comment', - }, - type: 'CuratedContent', - }, - { - properties: { - isBreaking: false, - mediaSelect: { - showMainVideo: false, - imageSlideshowReplace: false, - videoReplace: false, - }, - showKickerTag: false, - showByline: false, - maybeContent: { - trail: { - trailPicture: { - allImages: [ - { - index: 12, - fields: { - displayCredit: 'true', - isMaster: 'true', - altText: 'Fire on the Beach - Dana Scruggs', - mediaId: - 'ae693a9bf972f379f725722a1aff5cee881cc574', - width: '2661', - source: 'Dana Scruggs', - photographer: 'Dana Scruggs', - height: '1597', - credit: 'Photograph: Dana Scruggs', - }, - mediaType: 'Image', - url: 'https://media.guim.co.uk/ae693a9bf972f379f725722a1aff5cee881cc574/80_651_2661_1597/master/2661.jpg', - }, - ], - }, - byline: '', - thumbnailPath: - 'https://i.guim.co.uk/img/media/ae693a9bf972f379f725722a1aff5cee881cc574/80_651_2661_1597/500.jpg?quality=85&auto=format&fit=max&s=fc54e408eafeadf5c0ed3d1b87902325', - webPublicationDate: 1667199650000, - }, - metadata: { - id: 'fashion/gallery/2022/oct/31/new-black-vanguard-photography-between-art-and-fashion-antwaun-sargent-in-pictures', - webTitle: - 'At the vanguard: a new aesthetic in Black portraiture – in pictures', - webUrl: 'https://www.theguardian.com/fashion/gallery/2022/oct/31/new-black-vanguard-photography-between-art-and-fashion-antwaun-sargent-in-pictures', - type: 'Gallery', - sectionId: { - value: 'fashion', - }, - format: { - design: 'GalleryDesign', - theme: 'LifestylePillar', - display: 'StandardDisplay', - }, - }, - fields: { - main: '
Fire on the Beach.
Fire on the Beach. Photograph: Dana Scruggs
', - body: '', - standfirst: - '

Curated by the US writer and critic Antwaun Sargent, an exhibition aims to explore the new aesthetic in portraiture being created by an emerging generation of black models, photographers and stylists.

The New Black Vanguard: Photography Between Art and Fashion is at the Saatchi Gallery in London until 22 January 2023

', - }, - elements: { - mediaAtoms: [], - }, - tags: { - tags: [ - { - properties: { - id: 'fashion/fashion-photography', - url: '/fashion/fashion-photography', - tagType: 'Keyword', - sectionId: 'fashion', - sectionName: 'Fashion', - webTitle: 'Fashion photography', - webUrl: 'https://www.theguardian.com/fashion/fashion-photography', - }, - }, - { - properties: { - id: 'artanddesign/photography', - url: '/artanddesign/photography', - tagType: 'Keyword', - sectionId: 'artanddesign', - sectionName: 'Art and design', - webTitle: 'Photography', - webUrl: 'https://www.theguardian.com/artanddesign/photography', - }, - }, - { - properties: { - id: 'artanddesign/exhibition', - url: '/artanddesign/exhibition', - tagType: 'Keyword', - sectionId: 'artanddesign', - sectionName: 'Art and design', - webTitle: 'Exhibitions', - webUrl: 'https://www.theguardian.com/artanddesign/exhibition', - }, - }, - { - properties: { - id: 'artanddesign/artanddesign', - url: '/artanddesign/artanddesign', - tagType: 'Keyword', - sectionId: 'artanddesign', - sectionName: 'Art and design', - webTitle: 'Art and design', - webUrl: 'https://www.theguardian.com/artanddesign/artanddesign', - }, - }, - { - properties: { - id: 'culture/culture', - url: '/culture/culture', - tagType: 'Keyword', - sectionId: 'culture', - sectionName: 'Culture', - webTitle: 'Culture', - webUrl: 'https://www.theguardian.com/culture/culture', - }, - }, - { - properties: { - id: 'artanddesign/saatchi-gallery', - url: '/artanddesign/saatchi-gallery', - tagType: 'Keyword', - sectionId: 'artanddesign', - sectionName: 'Art and design', - webTitle: 'Saatchi gallery', - webUrl: 'https://www.theguardian.com/artanddesign/saatchi-gallery', - }, - }, - { - properties: { - id: 'artanddesign/art', - url: '/artanddesign/art', - tagType: 'Keyword', - sectionId: 'artanddesign', - sectionName: 'Art and design', - webTitle: 'Art', - webUrl: 'https://www.theguardian.com/artanddesign/art', - }, - }, - { - properties: { - id: 'uk/london', - url: '/uk/london', - tagType: 'Keyword', - sectionId: 'uk-news', - sectionName: 'UK news', - webTitle: 'London', - webUrl: 'https://www.theguardian.com/uk/london', - }, - }, - { - properties: { - id: 'us-news/us-news', - url: '/us-news/us-news', - tagType: 'Keyword', - sectionId: 'us-news', - sectionName: 'US news', - webTitle: 'US news', - webUrl: 'https://www.theguardian.com/us-news/us-news', - }, - }, - { - properties: { - id: 'fashion/fashion', - url: '/fashion/fashion', - tagType: 'Keyword', - sectionId: 'fashion', - sectionName: 'Fashion', - webTitle: 'Fashion', - webUrl: 'https://www.theguardian.com/fashion/fashion', - }, - }, - { - properties: { - id: 'lifeandstyle/lifeandstyle', - url: '/lifeandstyle/lifeandstyle', - tagType: 'Keyword', - sectionId: 'lifeandstyle', - sectionName: 'Life and style', - webTitle: 'Life and style', - webUrl: 'https://www.theguardian.com/lifeandstyle/lifeandstyle', - }, - }, - { - properties: { - id: 'uk/uk', - url: '/uk/uk', - tagType: 'Keyword', - sectionId: 'uk-news', - sectionName: 'UK news', - webTitle: 'UK news', - webUrl: 'https://www.theguardian.com/uk/uk', - }, - }, - { - properties: { - id: 'world/world', - url: '/world/world', - tagType: 'Keyword', - sectionId: 'world', - sectionName: 'World news', - webTitle: 'World news', - webUrl: 'https://www.theguardian.com/world/world', - }, - }, - { - properties: { - id: 'world/race', - url: '/world/race', - tagType: 'Keyword', - sectionId: 'world', - sectionName: 'World news', - webTitle: 'Race', - webUrl: 'https://www.theguardian.com/world/race', - }, - }, - { - properties: { - id: 'type/gallery', - url: '/type/gallery', - tagType: 'Type', - sectionId: 'global', - sectionName: 'global', - webTitle: 'Gallery', - webUrl: 'https://www.theguardian.com/inpictures', - }, - }, - { - properties: { - id: 'tone/features', - url: '/tone/features', - tagType: 'Tone', - sectionId: 'global', - sectionName: 'global', - webTitle: 'Features', - webUrl: 'https://www.theguardian.com/tone/features', - }, - }, - { - properties: { - id: 'tracking/commissioningdesk/uk-pictures-guardian-news', - url: '/tracking/commissioningdesk/uk-pictures-guardian-news', - tagType: 'Tracking', - sectionId: 'global', - sectionName: 'global', - webTitle: 'UK Pictures Guardian News', - webUrl: 'https://www.theguardian.com/tracking/commissioningdesk/uk-pictures-guardian-news', - }, - }, - ], - }, - }, - maybeContentId: - 'fashion/gallery/2022/oct/31/new-black-vanguard-photography-between-art-and-fashion-antwaun-sargent-in-pictures', - isLiveBlog: false, - isCrossword: false, - byline: '', - webTitle: - 'At the vanguard: a new aesthetic in Black portraiture – in pictures', - linkText: - 'At the vanguard: a new aesthetic in Black portraiture – in pictures', - webUrl: 'https://www.theguardian.com/fashion/gallery/2022/oct/31/new-black-vanguard-photography-between-art-and-fashion-antwaun-sargent-in-pictures', - editionBrandings: [ - { - edition: { - id: 'UK', - }, - }, - { - edition: { - id: 'US', - }, - }, - { - edition: { - id: 'AU', - }, - }, - { - edition: { - id: 'INT', - }, - }, - ], - }, - header: { - isVideo: false, - isComment: false, - isGallery: true, - isAudio: false, - headline: - 'At the vanguard: a new aesthetic in Black portraiture – in pictures', - url: '/fashion/gallery/2022/oct/31/new-black-vanguard-photography-between-art-and-fashion-antwaun-sargent-in-pictures', - hasMainVideoElement: false, - }, - card: { - id: 'fashion/gallery/2022/oct/31/new-black-vanguard-photography-between-art-and-fashion-antwaun-sargent-in-pictures', - cardStyle: { - type: 'Media', - }, - webPublicationDateOption: 1667199650000, - lastModifiedOption: 1667199650000, - trailText: - 'Curated by the US writer and critic Antwaun Sargent, a new exhibition aims to explore the new aesthetic in portraiture being created by an emerging generation of black models, photographers and stylists', - shortUrlPath: '/p/mhe94', - shortUrl: 'https://www.theguardian.com/p/mhe94', - group: '0', - isLive: false, - galleryCount: 11, - }, - discussion: { - isCommentable: false, - isClosedForComments: true, - discussionId: '/p/mhe94', - }, - display: { - isBoosted: false, - boostLevel: 'default', - showBoostedHeadline: false, - showQuotedHeadline: false, - imageHide: false, - showLivePlayable: false, - }, - format: { - design: 'GalleryDesign', - theme: 'LifestylePillar', - display: 'StandardDisplay', - }, - enriched: {}, - supportingContent: [], - cardStyle: { - type: 'Media', - }, - type: 'CuratedContent', - }, - { - properties: { - isBreaking: false, - mediaSelect: { - showMainVideo: false, - imageSlideshowReplace: false, - videoReplace: false, - }, - showKickerTag: false, - showByline: false, - maybeContent: { - trail: { - trailPicture: { - allImages: [ - { - index: 4, - fields: { - displayCredit: 'true', - isMaster: 'true', - altText: - 'Fan Special Screening of "Morbius" at Cinemark Playa Vista and XD in Playa Vista
Cast member Jared Leto attends a Fan Special Screening of "Morbius" at Cinemark Playa Vista and XD in Playa Vista, California, U.S., March 30, 2022. REUTERS/Aude Guerrucci', - mediaId: - 'db676607592f20362cbe6c6dcb9e2416723d7af1', - width: '3500', - source: 'Reuters', - photographer: 'Aude Guerrucci', - height: '2100', - credit: 'Photograph: Aude Guerrucci/Reuters', - }, - mediaType: 'Image', - url: 'https://media.guim.co.uk/db676607592f20362cbe6c6dcb9e2416723d7af1/0_25_3500_2100/master/3500.jpg', - }, - ], - }, - byline: 'Alaina Demopoulos in New York', - thumbnailPath: - 'https://i.guim.co.uk/img/media/db676607592f20362cbe6c6dcb9e2416723d7af1/0_25_3500_2100/500.jpg?quality=85&auto=format&fit=max&s=97c0c4a69484caad6f9da36484795910', - webPublicationDate: 1666933219000, - }, - metadata: { - id: 'fashion/2022/oct/27/jared-leto-skincare-celebrity-men-brad-pitt', - webTitle: - 'Jared Leto says he’s not interested in skincare – while selling $97 eye cream', - webUrl: 'https://www.theguardian.com/fashion/2022/oct/27/jared-leto-skincare-celebrity-men-brad-pitt', - type: 'Article', - sectionId: { - value: 'fashion', - }, - format: { - design: 'FeatureDesign', - theme: 'LifestylePillar', - display: 'StandardDisplay', - }, - }, - fields: { - main: '
leto flashes peace sign
Jared Leto is launching a line of skin, body and hair care products. Photograph: Aude Guerrucci/Reuters
', - body: '

In 2022, it seems you’re more likely to see a celebrity announce a new beauty line than promote their next project. This year alone, Hailey Bieber, Gwen Stefani, Halsey, Ciara and Winnie Harlow all dropped new brands. But it’s not just a woman’s game – now men want a piece of the $5bn market, too.

Last week, Jared Leto announced Twentynine Palms, “an 11-piece range of gender-neutral skin care, body care and hair care products”, per Vogue. Though the 50-year-old told the glossy he’s “never been really interested in beauty products”, he unveiled items like $47 hand wash and $97 eye cream. Each product is made from “desert botanicals” like prickly pear extract.

', - standfirst: - '

The actor has joined Brad Pitt – another self-described newbie – in hawking pricey products. Will anyone shell out?

', - }, - elements: { - mediaAtoms: [], - }, - tags: { - tags: [ - { - properties: { - id: 'fashion/fashion', - url: '/fashion/fashion', - tagType: 'Keyword', - sectionId: 'fashion', - sectionName: 'Fashion', - webTitle: 'Fashion', - webUrl: 'https://www.theguardian.com/fashion/fashion', - }, - }, - { - properties: { - id: 'film/jared-leto', - url: '/film/jared-leto', - tagType: 'Keyword', - sectionId: 'film', - sectionName: 'Film', - webTitle: 'Jared Leto', - webUrl: 'https://www.theguardian.com/film/jared-leto', - }, - }, - { - properties: { - id: 'film/film', - url: '/film/film', - tagType: 'Keyword', - sectionId: 'film', - sectionName: 'Film', - webTitle: 'Film', - webUrl: 'https://www.theguardian.com/film/film', - }, - }, - { - properties: { - id: 'lifeandstyle/lifeandstyle', - url: '/lifeandstyle/lifeandstyle', - tagType: 'Keyword', - sectionId: 'lifeandstyle', - sectionName: 'Life and style', - webTitle: 'Life and style', - webUrl: 'https://www.theguardian.com/lifeandstyle/lifeandstyle', - }, - }, - { - properties: { - id: 'culture/culture', - url: '/culture/culture', - tagType: 'Keyword', - sectionId: 'culture', - sectionName: 'Culture', - webTitle: 'Culture', - webUrl: 'https://www.theguardian.com/culture/culture', - }, - }, - { - properties: { - id: 'fashion/skincare', - url: '/fashion/skincare', - tagType: 'Keyword', - sectionId: 'fashion', - sectionName: 'Fashion', - webTitle: 'Skincare', - webUrl: 'https://www.theguardian.com/fashion/skincare', - }, - }, - { - properties: { - id: 'film/bradpitt', - url: '/film/bradpitt', - tagType: 'Keyword', - sectionId: 'film', - sectionName: 'Film', - webTitle: 'Brad Pitt', - webUrl: 'https://www.theguardian.com/film/bradpitt', - }, - }, - { - properties: { - id: 'fashion/beauty', - url: '/fashion/beauty', - tagType: 'Keyword', - sectionId: 'fashion', - sectionName: 'Fashion', - webTitle: 'Beauty', - webUrl: 'https://www.theguardian.com/fashion/beauty', - }, - }, - { - properties: { - id: 'type/article', - url: '/type/article', - tagType: 'Type', - sectionId: 'global', - sectionName: 'global', - webTitle: 'Article', - webUrl: 'https://www.theguardian.com/articles', - }, - }, - { - properties: { - id: 'tone/features', - url: '/tone/features', - tagType: 'Tone', - sectionId: 'global', - sectionName: 'global', - webTitle: 'Features', - webUrl: 'https://www.theguardian.com/tone/features', - }, - }, - { - properties: { - id: 'profile/alaina-demopoulos', - url: '/profile/alaina-demopoulos', - tagType: 'Contributor', - sectionId: 'global', - sectionName: 'global', - webTitle: 'Alaina Demopoulos', - webUrl: 'https://www.theguardian.com/profile/alaina-demopoulos', - bio: '

Alaina Demopoulos is a daily features writer for the Guardian

', - }, - }, - { - properties: { - id: 'tracking/commissioningdesk/east-coast-features', - url: '/tracking/commissioningdesk/east-coast-features', - tagType: 'Tracking', - sectionId: 'global', - sectionName: 'global', - webTitle: 'East coast features', - webUrl: 'https://www.theguardian.com/tracking/commissioningdesk/east-coast-features', - }, - }, - { - properties: { - id: 'tracking/commissioningdesk/us-features', - url: '/tracking/commissioningdesk/us-features', - tagType: 'Tracking', - sectionId: 'global', - sectionName: 'global', - webTitle: 'US Features', - webUrl: 'https://www.theguardian.com/tracking/commissioningdesk/us-features', - }, - }, - ], - }, - }, - maybeContentId: - 'fashion/2022/oct/27/jared-leto-skincare-celebrity-men-brad-pitt', - isLiveBlog: false, - isCrossword: false, - byline: 'Alaina Demopoulos in New York', - webTitle: - 'Jared Leto says he’s not interested in skincare – while selling $97 eye cream', - linkText: - 'Jared Leto says he’s not interested in skincare – while selling $97 eye cream', - webUrl: 'https://www.theguardian.com/fashion/2022/oct/27/jared-leto-skincare-celebrity-men-brad-pitt', - editionBrandings: [ - { - edition: { - id: 'UK', - }, - }, - { - edition: { - id: 'US', - }, - }, - { - edition: { - id: 'AU', - }, - }, - { - edition: { - id: 'INT', - }, - }, - ], - }, - header: { - isVideo: false, - isComment: false, - isGallery: false, - isAudio: false, - kicker: { - type: 'FreeHtmlKicker', - item: { - properties: { - kickerText: 'Jared Leto', - }, - }, - }, - headline: - 'Actor says he’s not interested in skincare – while selling $97 eye cream', - url: '/fashion/2022/oct/27/jared-leto-skincare-celebrity-men-brad-pitt', - hasMainVideoElement: false, - }, - card: { - id: 'fashion/2022/oct/27/jared-leto-skincare-celebrity-men-brad-pitt', - cardStyle: { - type: 'Feature', - }, - webPublicationDateOption: 1666933219000, - lastModifiedOption: 1666966902000, - trailText: - 'The actor has joined Brad Pitt – another self-described newbie – in hawking pricey products. Will anyone shell out?', - shortUrlPath: '/p/mh28e', - shortUrl: 'https://www.theguardian.com/p/mh28e', - group: '0', - isLive: false, - }, - discussion: { - isCommentable: false, - isClosedForComments: true, - discussionId: '/p/mh28e', - }, - display: { - isBoosted: false, - boostLevel: 'default', - showBoostedHeadline: false, - showQuotedHeadline: false, - imageHide: false, - showLivePlayable: false, - }, - format: { - design: 'FeatureDesign', - theme: 'LifestylePillar', - display: 'StandardDisplay', - }, - enriched: {}, - supportingContent: [], - cardStyle: { - type: 'Feature', - }, - type: 'CuratedContent', - }, - { - properties: { - isBreaking: false, - mediaSelect: { - showMainVideo: false, - imageSlideshowReplace: false, - videoReplace: false, - }, - showKickerTag: false, - showByline: false, - maybeContent: { - trail: { - trailPicture: { - allImages: [ - { - index: 20, - fields: { - displayCredit: 'true', - source: 'REX/Shutterstock', - isMaster: 'true', - altText: 'Winter coats', - mediaId: - '11f672598753a0604016f1d79e9ea57d22773e44', - width: '2560', - height: '1536', - credit: 'Composite: REX/Shutterstock', - }, - mediaType: 'Image', - url: 'https://media.guim.co.uk/11f672598753a0604016f1d79e9ea57d22773e44/0_0_2560_1536/master/2560.jpg', - }, - ], - }, - byline: 'by Jo Jones', - thumbnailPath: - 'https://i.guim.co.uk/img/media/11f672598753a0604016f1d79e9ea57d22773e44/0_0_2560_1536/500.jpg?quality=85&auto=format&fit=max&s=18a365bbe36ec46533d68addc35b39e4', - webPublicationDate: 1667083509000, - }, - metadata: { - id: 'fashion/gallery/2022/oct/29/20-of-the-best-winter-coats-to-wear-now-in-pictures', - webTitle: - ' 20 of the best winter coats to wear now - in pictures ', - webUrl: 'https://www.theguardian.com/fashion/gallery/2022/oct/29/20-of-the-best-winter-coats-to-wear-now-in-pictures', - type: 'Gallery', - sectionId: { - value: 'fashion', - }, - format: { - design: 'GalleryDesign', - theme: 'LifestylePillar', - display: 'StandardDisplay', - }, - }, - fields: { - main: '
L-R: Ghospell, Cos, Street Style PFW, La Redoute, Monki
L-R: Ghospell, Cos, Street Style PFW, La Redoute, Monki Composite: REX/Shutterstock
', - body: '', - standfirst: - '

A pop of colour, a classic camel or a cuddly teddy? Choose the coat that best suits your personality

', - }, - elements: { - mediaAtoms: [], - }, - tags: { - tags: [ - { - properties: { - id: 'fashion/fashion', - url: '/fashion/fashion', - tagType: 'Keyword', - sectionId: 'fashion', - sectionName: 'Fashion', - webTitle: 'Fashion', - webUrl: 'https://www.theguardian.com/fashion/fashion', - }, - }, - { - properties: { - id: 'fashion/womens-coats', - url: '/fashion/womens-coats', - tagType: 'Keyword', - sectionId: 'fashion', - sectionName: 'Fashion', - webTitle: "Women's coats and jackets", - webUrl: 'https://www.theguardian.com/fashion/womens-coats', - }, - }, - { - properties: { - id: 'fashion/winter-coats', - url: '/fashion/winter-coats', - tagType: 'Keyword', - sectionId: 'fashion', - sectionName: 'Fashion', - webTitle: 'Winter coats', - webUrl: 'https://www.theguardian.com/fashion/winter-coats', - }, - }, - { - properties: { - id: 'lifeandstyle/lifeandstyle', - url: '/lifeandstyle/lifeandstyle', - tagType: 'Keyword', - sectionId: 'lifeandstyle', - sectionName: 'Life and style', - webTitle: 'Life and style', - webUrl: 'https://www.theguardian.com/lifeandstyle/lifeandstyle', - }, - }, - { - properties: { - id: 'type/gallery', - url: '/type/gallery', - tagType: 'Type', - sectionId: 'global', - sectionName: 'global', - webTitle: 'Gallery', - webUrl: 'https://www.theguardian.com/inpictures', - }, - }, - { - properties: { - id: 'profile/jojones', - url: '/profile/jojones', - tagType: 'Contributor', - sectionId: 'global', - sectionName: 'global', - webTitle: 'Jo Jones', - webUrl: 'https://www.theguardian.com/profile/jojones', - bio: '

Jo Jones is fashion editor for the Observer

', - }, - }, - { - properties: { - id: 'tracking/commissioningdesk/observer-magazine', - url: '/tracking/commissioningdesk/observer-magazine', - tagType: 'Tracking', - sectionId: 'global', - sectionName: 'global', - webTitle: 'Observer Magazine', - webUrl: 'https://www.theguardian.com/tracking/commissioningdesk/observer-magazine', - }, - }, - ], - }, - }, - maybeContentId: - 'fashion/gallery/2022/oct/29/20-of-the-best-winter-coats-to-wear-now-in-pictures', - isLiveBlog: false, - isCrossword: false, - byline: 'by Jo Jones', - webTitle: ' 20 of the best winter coats to wear now - in pictures ', - linkText: ' 20 of the best winter coats to wear now - in pictures ', - webUrl: 'https://www.theguardian.com/fashion/gallery/2022/oct/29/20-of-the-best-winter-coats-to-wear-now-in-pictures', - editionBrandings: [ - { - edition: { - id: 'UK', - }, - }, - { - edition: { - id: 'US', - }, - }, - { - edition: { - id: 'AU', - }, - }, - { - edition: { - id: 'INT', - }, - }, - ], - }, - header: { - isVideo: false, - isComment: false, - isGallery: true, - isAudio: false, - kicker: { - type: 'FreeHtmlKicker', - item: { - properties: { - kickerText: 'Winter is coming', - }, - }, - }, - headline: ' 20 of the best winter coats to wear now', - url: '/fashion/gallery/2022/oct/29/20-of-the-best-winter-coats-to-wear-now-in-pictures', - hasMainVideoElement: false, - }, - card: { - id: 'fashion/gallery/2022/oct/29/20-of-the-best-winter-coats-to-wear-now-in-pictures', - cardStyle: { - type: 'Media', - }, - webPublicationDateOption: 1667083509000, - lastModifiedOption: 1667083509000, - trailText: - 'A pop of colour, a classic camel or a cuddly teddy? Choose the coat that best suits your personality', - shortUrlPath: '/p/mgmvx', - shortUrl: 'https://www.theguardian.com/p/mgmvx', - group: '0', - isLive: false, - galleryCount: 19, - }, - discussion: { - isCommentable: false, - isClosedForComments: true, - discussionId: '/p/mgmvx', - }, - display: { - isBoosted: false, - boostLevel: 'default', - showBoostedHeadline: false, - showQuotedHeadline: false, - imageHide: false, - showLivePlayable: false, - }, - format: { - design: 'GalleryDesign', - theme: 'LifestylePillar', - display: 'StandardDisplay', - }, - enriched: {}, - supportingContent: [], - cardStyle: { - type: 'Media', - }, - type: 'CuratedContent', - }, - { - properties: { - isBreaking: false, - mediaSelect: { - showMainVideo: false, - imageSlideshowReplace: false, - videoReplace: false, - }, - showKickerTag: false, - showByline: false, - maybeContent: { - trail: { - trailPicture: { - allImages: [ - { - index: 4, - fields: { - displayCredit: 'true', - source: 'TikTok', - isMaster: 'true', - altText: 'IMG 0740', - mediaId: - 'dd9d8bd4f8805a0ddd3dabbb1ddb5add71fb8cc9', - width: '1170', - height: '702', - credit: 'Photograph: TikTok', - }, - mediaType: 'Image', - url: 'https://media.guim.co.uk/dd9d8bd4f8805a0ddd3dabbb1ddb5add71fb8cc9/0_335_1170_702/master/1170.jpg', - }, - ], - }, - byline: 'Alaina Demopoulos', - thumbnailPath: - 'https://i.guim.co.uk/img/media/dd9d8bd4f8805a0ddd3dabbb1ddb5add71fb8cc9/0_335_1170_702/500.jpg?quality=85&auto=format&fit=max&s=d58b9663df81f862f6756209122cb7ef', - webPublicationDate: 1667192447000, - }, - metadata: { - id: 'lifeandstyle/2022/oct/30/sadness-is-a-trend-why-tiktok-loves-crying-makeup', - webTitle: - '‘Sadness is a trend’: why TikTok loves ‘crying makeup’', - webUrl: 'https://www.theguardian.com/lifeandstyle/2022/oct/30/sadness-is-a-trend-why-tiktok-loves-crying-makeup', - type: 'Article', - sectionId: { - value: 'lifeandstyle', - }, - format: { - design: 'FeatureDesign', - theme: 'LifestylePillar', - display: 'StandardDisplay', - }, - }, - fields: { - main: '
close up of Kenealy - she looks like she\'s been crying
Zoe Kim Kenealy on TikTok. Photograph: Screengrab/TikTok
', - body: '

Beauty magazines once taught readers how to use makeup to conceal a recent sobbing sesh. But now, one TikTok trend encourages us to embrace those misty eyes and rosy noses. “Crying makeup,” it seems, is in.

In a clip that has gained over 507,000 likes, the Boston-based content creator Zoe Kim Kenealy offers a tutorial “for the unstable girlies” to achieve the look of a fresh sob even “if you’re not in the mood to cry”.

', - standfirst: - '

From #sadgirlwalk to dazed stares, misery – or the performance of it – is all over the platform in what some see as a bid for belonging

', - }, - elements: { - mediaAtoms: [], - }, - tags: { - tags: [ - { - properties: { - id: 'lifeandstyle/lifeandstyle', - url: '/lifeandstyle/lifeandstyle', - tagType: 'Keyword', - sectionId: 'lifeandstyle', - sectionName: 'Life and style', - webTitle: 'Life and style', - webUrl: 'https://www.theguardian.com/lifeandstyle/lifeandstyle', - }, - }, - { - properties: { - id: 'us-news/us-news', - url: '/us-news/us-news', - tagType: 'Keyword', - sectionId: 'us-news', - sectionName: 'US news', - webTitle: 'US news', - webUrl: 'https://www.theguardian.com/us-news/us-news', - }, - }, - { - properties: { - id: 'technology/tiktok', - url: '/technology/tiktok', - tagType: 'Keyword', - sectionId: 'technology', - sectionName: 'Technology', - webTitle: 'TikTok', - webUrl: 'https://www.theguardian.com/technology/tiktok', - }, - }, - { - properties: { - id: 'fashion/fashion', - url: '/fashion/fashion', - tagType: 'Keyword', - sectionId: 'fashion', - sectionName: 'Fashion', - webTitle: 'Fashion', - webUrl: 'https://www.theguardian.com/fashion/fashion', - }, - }, - { - properties: { - id: 'type/article', - url: '/type/article', - tagType: 'Type', - sectionId: 'global', - sectionName: 'global', - webTitle: 'Article', - webUrl: 'https://www.theguardian.com/articles', - }, - }, - { - properties: { - id: 'tone/features', - url: '/tone/features', - tagType: 'Tone', - sectionId: 'global', - sectionName: 'global', - webTitle: 'Features', - webUrl: 'https://www.theguardian.com/tone/features', - }, - }, - { - properties: { - id: 'profile/alaina-demopoulos', - url: '/profile/alaina-demopoulos', - tagType: 'Contributor', - sectionId: 'global', - sectionName: 'global', - webTitle: 'Alaina Demopoulos', - webUrl: 'https://www.theguardian.com/profile/alaina-demopoulos', - bio: '

Alaina Demopoulos is a daily features writer for the Guardian

', - }, - }, - { - properties: { - id: 'tracking/commissioningdesk/east-coast-features', - url: '/tracking/commissioningdesk/east-coast-features', - tagType: 'Tracking', - sectionId: 'global', - sectionName: 'global', - webTitle: 'East coast features', - webUrl: 'https://www.theguardian.com/tracking/commissioningdesk/east-coast-features', - }, - }, - { - properties: { - id: 'tracking/commissioningdesk/us-features', - url: '/tracking/commissioningdesk/us-features', - tagType: 'Tracking', - sectionId: 'global', - sectionName: 'global', - webTitle: 'US Features', - webUrl: 'https://www.theguardian.com/tracking/commissioningdesk/us-features', - }, - }, - ], - }, - }, - maybeContentId: - 'lifeandstyle/2022/oct/30/sadness-is-a-trend-why-tiktok-loves-crying-makeup', - isLiveBlog: false, - isCrossword: false, - byline: 'Alaina Demopoulos', - webTitle: '‘Sadness is a trend’: why TikTok loves ‘crying makeup’', - linkText: '‘Sadness is a trend’: why TikTok loves ‘crying makeup’', - webUrl: 'https://www.theguardian.com/lifeandstyle/2022/oct/30/sadness-is-a-trend-why-tiktok-loves-crying-makeup', - editionBrandings: [ - { - edition: { - id: 'UK', - }, - }, - { - edition: { - id: 'US', - }, - }, - { - edition: { - id: 'AU', - }, - }, - { - edition: { - id: 'INT', - }, - }, - ], - }, - header: { - isVideo: false, - isComment: false, - isGallery: false, - isAudio: false, - headline: '‘Sadness is a trend’: why TikTok loves ‘crying makeup’', - url: '/lifeandstyle/2022/oct/30/sadness-is-a-trend-why-tiktok-loves-crying-makeup', - hasMainVideoElement: false, - }, - card: { - id: 'lifeandstyle/2022/oct/30/sadness-is-a-trend-why-tiktok-loves-crying-makeup', - cardStyle: { - type: 'Feature', - }, - webPublicationDateOption: 1667192447000, - lastModifiedOption: 1667228583000, - trailText: - 'From #sadgirlwalk to dazed stares, misery – or the performance of it – is all over the platform in what some see as a bid for belonging', - shortUrlPath: '/p/mhgkn', - shortUrl: 'https://www.theguardian.com/p/mhgkn', - group: '0', - isLive: false, - }, - discussion: { - isCommentable: false, - isClosedForComments: true, - discussionId: '/p/mhgkn', - }, - display: { - isBoosted: false, - boostLevel: 'default', - showBoostedHeadline: false, - showQuotedHeadline: false, - imageHide: false, - showLivePlayable: false, - }, - format: { - design: 'FeatureDesign', - theme: 'LifestylePillar', - display: 'StandardDisplay', - }, - enriched: {}, - supportingContent: [], - cardStyle: { - type: 'Feature', - }, - type: 'CuratedContent', - }, -]; diff --git a/dotcom-rendering/src/components/Accessibility.importable.tsx b/dotcom-rendering/src/components/Accessibility.importable.tsx index d83af9dc576..9cfd681f704 100644 --- a/dotcom-rendering/src/components/Accessibility.importable.tsx +++ b/dotcom-rendering/src/components/Accessibility.importable.tsx @@ -93,7 +93,7 @@ export const Accessibility = () => { }; return ( - +

diff --git a/dotcom-rendering/src/components/Card/Card.stories.tsx b/dotcom-rendering/src/components/Card/Card.stories.tsx index b837ddb33bd..92097d86ef5 100644 --- a/dotcom-rendering/src/components/Card/Card.stories.tsx +++ b/dotcom-rendering/src/components/Card/Card.stories.tsx @@ -1555,8 +1555,7 @@ export const WithSpecialPaletteVariations = () => { {containerPalettes.map((containerPalette) => ( { return ( - + { return ( - + { return ( - + { return ( - + { return ( - + { return ( - + { return ( - + { return ( - + { return ( - + { return ( - + { return ( - + { return ( - + { return ( - + { return ( - + { return ( - + { return ( - + { return ( - + { return ( - + { return ( { return ( - + ( ( - + ( - + ( - + ( - + ( - + {containerPalettes.map((containerPalette) => ( ( - + ( {containerPalettes.map((containerPalette) => ( , url: '/', @@ -171,49 +169,29 @@ export const MultipleStory = { - + - + - +

Insert call to action here

- + diff --git a/dotcom-rendering/src/components/FrontSection.tsx b/dotcom-rendering/src/components/FrontSection.tsx index 90418e44c74..0fc1347e3d8 100644 --- a/dotcom-rendering/src/components/FrontSection.tsx +++ b/dotcom-rendering/src/components/FrontSection.tsx @@ -3,7 +3,6 @@ import { isString } from '@guardian/libs'; import { between, from, space, until } from '@guardian/source/foundations'; import { pageSkinContainer } from '../layouts/lib/pageSkin'; import { type EditionId, isNetworkFront } from '../lib/edition'; -import { hideAge } from '../lib/hideAge'; import { getOphanComponents } from '../lib/labs'; import { palette as schemePalette } from '../palette'; import type { CollectionBranding } from '../types/branding'; @@ -26,7 +25,6 @@ import { Island } from './Island'; import { LabsSectionHeader } from './LabsSectionHeader'; import { MostPopularFrontRight } from './MostPopularFrontRight'; import { ShowHideButton } from './ShowHideButton'; -import { ShowMore } from './ShowMore.importable'; import { Treats } from './Treats'; type Props = { @@ -38,7 +36,6 @@ type Props = { url?: string; /** The html `id` property of the element */ sectionId?: string; - collectionId?: string; pageId?: string; /** Defaults to `true`. If we should render the top border */ showTopBorder?: boolean; @@ -75,9 +72,6 @@ type Props = { editionId: EditionId; /** A list of related links that appear in the bottom of the left column on fronts */ treats?: TreatType[]; - /** Enable the "Show More" button on this container to allow readers to load more cards */ - canShowMore?: boolean; - ajaxUrl?: string; /** Puts pagination at the bottom of the container allowing the user to navigate to other pages, * usually used on the last container on a page */ pagination?: TagPagePagination; @@ -99,7 +93,6 @@ type Props = { */ slimifySectionForSlimHomepageAbTest?: boolean; showRightContentForSlimHomepageAbTest?: boolean; - discussionApiUrl: string; collectionBranding?: CollectionBranding; isTagPage?: boolean; hasNavigationButtons?: boolean; @@ -553,8 +546,6 @@ const sponsoredContentLabelWrapper = css` * in the centre. Extra elements can be passed to `leftContent`, which will * automatically fall into the left column on larger breakpoints. * - * Defaults to an HTML `section`, but the specific tag can be set. - * * @example * * from `mobile` (320) to `phablet` (660) @@ -564,9 +555,6 @@ const sponsoredContentLabelWrapper = css` * ├───────┤ * │▒▒▒▒▒▒▒│ * │▒▒▒▒▒▒▒│ - * ├───────┤ - * │Show │ - * |More │ * └───────┘ * * from `tablet` (740) to `desktop` (980) @@ -578,8 +566,6 @@ const sponsoredContentLabelWrapper = css` * ├───────────────────────┤ * │▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│ * │▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│ - * ├───────────────────────┤ - * │Show More │ * └───────────────────────┘ * * on `leftCol` (1140) if component is toggleable @@ -592,8 +578,6 @@ const sponsoredContentLabelWrapper = css` * │ │▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│ * │Tre│▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│ * │ats│▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│ - * ├───┼─────────────────────┤ - * │ │Show More │ * └───┴─────────────────────┘ * * on `leftCol` (1140) if component is not toggleable @@ -606,8 +590,6 @@ const sponsoredContentLabelWrapper = css` * │ │▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│ * │Tre│▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│ * │ats│▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│ - * ├───┼──────────────────────┤ - * │ │Show More │ * └───┴──────────────────────┘ * * on `wide` (1300) @@ -620,8 +602,6 @@ const sponsoredContentLabelWrapper = css` * │ │▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ │ * │ │▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ │ * │Treat│▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ │ - * ├─────┼─────────────────────────┤ - * │ │Show More │ * └─────┴─────────────────────────┘ * */ @@ -638,22 +618,18 @@ export const FrontSection = ({ ophanComponentLink, ophanComponentName, sectionId = '', - collectionId, pageId, showDateHeader = false, showTopBorder = true, toggleable = false, treats, url, - canShowMore, - ajaxUrl, pagination, isOnPaidContentFront, targetedTerritory, hasPageSkin = false, slimifySectionForSlimHomepageAbTest = false, showRightContentForSlimHomepageAbTest = false, - discussionApiUrl, collectionBranding, isTagPage = false, hasNavigationButtons = false, @@ -666,14 +642,6 @@ export const FrontSection = ({ const isToggleable = toggleable && !!sectionId; const showVerticalRule = !hasPageSkin; const isBetaContainer = !!containerLevel; - const showMore = - canShowMore && - !!title && - !!sectionId && - !!collectionId && - !!pageId && - !!ajaxUrl && - !isBetaContainer; // These are for beta containers only const useLargeSpacingMobile = !!isNextCollectionPrimary || isAboveMobileAd; @@ -689,10 +657,6 @@ export const FrontSection = ({ }) : undefined; - /** - * id is being used to set the containerId in @see {ShowMore.importable.tsx} - * this id pre-existed showMore so is probably also being used for something else. - */ return (
- ) : showMore ? ( - - - ) : null} {isLabs && diff --git a/dotcom-rendering/src/components/NavList.stories.tsx b/dotcom-rendering/src/components/NavList.stories.tsx index f02b7c440be..b96b88ec682 100644 --- a/dotcom-rendering/src/components/NavList.stories.tsx +++ b/dotcom-rendering/src/components/NavList.stories.tsx @@ -1,5 +1,4 @@ import { breakpoints } from '@guardian/source/foundations'; -import { discussionApiUrl } from '../../fixtures/manual/discussionApiUrl'; import { trails } from '../../fixtures/manual/trails-nav'; import { FrontSection } from './FrontSection'; import { NavList } from './NavList'; @@ -19,22 +18,14 @@ export default { }; export const Default = () => ( - + ); Default.storyName = 'NavList'; export const DefaultWithImages = () => ( - + ); diff --git a/dotcom-rendering/src/components/Palettes.stories.tsx b/dotcom-rendering/src/components/Palettes.stories.tsx index 854058d6e1c..7a0f023565a 100644 --- a/dotcom-rendering/src/components/Palettes.stories.tsx +++ b/dotcom-rendering/src/components/Palettes.stories.tsx @@ -1,7 +1,6 @@ import type { Meta, StoryObj } from '@storybook/react-webpack5'; import type { ComponentProps } from 'react'; import { allModes } from '../../.storybook/modes'; -import { discussionApiUrl } from '../../fixtures/manual/discussionApiUrl'; import { trails } from '../../fixtures/manual/trails'; import type { DCRGroupedTrails } from '../types/front'; import { FlexibleGeneral } from './FlexibleGeneral'; @@ -54,7 +53,6 @@ export const EventPalette: Story = { containerPalette: 'EventPalette', showDateHeader: true, editionId: 'UK', - discussionApiUrl, }, flexibleGeneral: { groupedTrails, diff --git a/dotcom-rendering/src/components/ScrollableFeature.stories.tsx b/dotcom-rendering/src/components/ScrollableFeature.stories.tsx index 40325300303..0124aefba07 100644 --- a/dotcom-rendering/src/components/ScrollableFeature.stories.tsx +++ b/dotcom-rendering/src/components/ScrollableFeature.stories.tsx @@ -1,6 +1,5 @@ import { breakpoints } from '@guardian/source/foundations'; import type { Meta, StoryObj } from '@storybook/react-webpack5'; -import { discussionApiUrl } from '../../fixtures/manual/discussionApiUrl'; import { audioTrails, galleryTrails, @@ -86,7 +85,6 @@ const meta = { render: (args) => ( ( - + ); @@ -203,7 +196,6 @@ export const WithPrimaryContainer = { render: (args) => ( @@ -235,7 +227,6 @@ export const WithSpecialPaletteVariations = { {containerPalettes.map((containerPalette) => ( ( ( @@ -132,7 +129,6 @@ export const WithSpecialPaletteVariations = { {containerPalettes.map((containerPalette) => ( ( @@ -87,8 +85,7 @@ export const WithPrimaryContainer = { render: (args) => ( @@ -119,7 +116,6 @@ export const WithSpecialPaletteVariations = { {containerPalettes.map((containerPalette) => ( { - if (isOpen && loading) return <>Loading; - if (isOpen) { - return ( - <> - Less{' '} - - {/* The context of what we're hiding is likely more useful for screen-reader users */} - {title} - - - ); - } - return <>More {title}; -}; - -type Props = { - title: string; - pageId: string; - /** - * `collectionId` is the id of the collection as it figures in the fronts - * config. It is used to generate the URL for the show-more API endpoint. - */ - collectionId: string; - /** - * The value of the `id` attribute on the container element that this 'show more' - * button sits beneath. The main container is server-side rendered, so this show more - * button needs to access its contents on the client side so that we can check whether - * any of the stories received from the `show-more` endpoint are already being displayed - * in the main container. (This can happen due to a lag between when the page is SSRd - * and when the user clicks the 'show more' button.) - */ - sectionId: string; - showAge: boolean; - ajaxUrl: string; - editionId: EditionId; - containerPalette?: DCRContainerPalette; - discussionApiUrl: string; -}; - -export const ShowMore = ({ - title, - pageId, - sectionId, - collectionId, - showAge, - ajaxUrl, - editionId, - containerPalette, - discussionApiUrl, -}: Props) => { - const [existingCardLinks, setExistingCardLinks] = useState([]); - const [isOpen, setIsOpen] = useState(false); - - /** - * Store the URLs of the cards in the main container for this button, to - * allow us to filter out duplicated stories when we load them from the - * 'show-more' endpoint. - */ - useEffect(() => { - const container = document.getElementById(`container-${sectionId}`); - const containerLinks = Array.from( - container?.querySelectorAll('a') ?? [], - ) - .map((element) => element.attributes.getNamedItem('href')?.value) - .filter(isNonNullable); - - setExistingCardLinks(containerLinks); - }, [sectionId]); - - /** We only pass an actual URL to SWR when 'showMore' is true. - * Toggling 'isOpen' will trigger a re-render - * @see https://swr.vercel.app/docs/conditional-fetching#conditional - */ - const url = isOpen - ? `${ajaxUrl}/${pageId}/show-more/${collectionId}.json?dcr=true` - : undefined; - const { data, error, loading } = useApi(url); - - const cards = - data && - enhanceCards(data, { - cardInTagPage: false, - editionId, - discussionApiUrl, - }).filter((card) => !existingCardLinks.includes(card.url)); - - const showMoreContainerId = `show-more-${collectionId}`; - - useEffect(() => { - /** - * Focus the first of the new cards when they're loaded in. - * There's no need to check `isOpen` here because if `isOpen` is - * `false` then `filteredData` will be `undefined`. - * */ - - const [card] = cards ?? []; - if (card) { - const maybeFirstCard = document.querySelector( - `#${showMoreContainerId} [data-link-name="${card.dataLinkName}"]`, - ); - if (maybeFirstCard instanceof HTMLElement) { - maybeFirstCard.focus(); - } - } - }, [cards, showMoreContainerId]); - - return ( - <> -
- {cards && ( -
-
    - {cards.map((card, cardIndex) => { - const columns = 3; - return ( -
  • - -
  • - ); - })} -
-
- )} -
-
- - - Loads more stories and moves focus to first new story. - - {error && ( - - Sorry, failed to load more stories. Retrying in a few - seconds. - - )} -
- - ); -}; diff --git a/dotcom-rendering/src/components/ShowMore.stories.tsx b/dotcom-rendering/src/components/ShowMore.stories.tsx deleted file mode 100644 index c82fed71609..00000000000 --- a/dotcom-rendering/src/components/ShowMore.stories.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { userEvent, within } from 'storybook/test'; -import { discussionApiUrl } from '../../fixtures/manual/discussionApiUrl'; -import { trails } from '../../fixtures/manual/show-more-trails'; -import { customMockFetch } from '../lib/mockRESTCalls'; -import { ShowMore } from './ShowMore.importable'; - -/** - * Clicks the 'show more' button so that Chromatic can capture it the component - * in its 'open' state. - */ -const play = async ({ canvasElement }: { canvasElement: HTMLElement }) => { - const canvas = within(canvasElement); - await userEvent.click(canvas.getByRole('button')); -}; - -const title = 'Opinion'; -const pageId = 'uk/lifestyle'; -const collectionId = '5011-3940-8793-33a9'; -const ajaxUrl = 'https://api.nextgen.guardianapps.co.uk'; -const sectionId = 'container-id'; -const editionId = 'UK'; - -const defaultProps = { - title, - ajaxUrl, - pageId, - collectionId, - sectionId, - showAge: false, - discussionApiUrl, - editionId, -} satisfies Parameters[0]; - -export default { - component: ShowMore, - title: 'Components/ShowMore', -}; - -const mockShowMoreSuccessRequestFetch = customMockFetch([ - { - mockedMethod: 'GET', - mockedUrl: `${ajaxUrl}/${pageId}/show-more/${collectionId}.json?dcr=true`, - mockedStatus: 200, - mockedBody: trails.slice(0, 6), - }, -]); - -const mockShowMoreErrorRequestFetch = customMockFetch([ - { - mockedMethod: 'GET', - mockedUrl: `${ajaxUrl}/${pageId}/show-more/${collectionId}.json?dcr=true`, - mockedStatus: 400, - mockedBody: undefined, - }, -]); - -export const ShowMoreSuccess = () => { - global.fetch = mockShowMoreSuccessRequestFetch; - - return ShowMore(defaultProps); -}; - -ShowMoreSuccess.play = play; -ShowMoreSuccess.storyName = 'ShowMore button, success'; - -export const ShowMoreError = () => { - global.fetch = mockShowMoreErrorRequestFetch; - - return ShowMore(defaultProps); -}; - -ShowMoreError.play = play; -ShowMoreError.storyName = 'ShowMore button, error'; diff --git a/dotcom-rendering/src/components/StaticFeatureTwo.stories.tsx b/dotcom-rendering/src/components/StaticFeatureTwo.stories.tsx index af1c2e6cf3f..899c4e06139 100644 --- a/dotcom-rendering/src/components/StaticFeatureTwo.stories.tsx +++ b/dotcom-rendering/src/components/StaticFeatureTwo.stories.tsx @@ -1,6 +1,5 @@ import { breakpoints } from '@guardian/source/foundations'; import type { Meta, StoryObj } from '@storybook/react-webpack5'; -import { discussionApiUrl } from '../../fixtures/manual/discussionApiUrl'; import { audioTrails, galleryTrails, @@ -36,7 +35,6 @@ const meta = { render: (args) => ( @@ -97,12 +95,7 @@ export const SelfHostedVideo = { title: string; videos: DCRFrontCard[]; }) => ( - + ); @@ -175,7 +168,6 @@ export const WithSpecialPaletteVariations = { {containerPalettes.map((containerPalette) => ( ( ( { treats={collection.treats} data-print-layout="hide" hasPageSkin={hasPageSkin} - discussionApiUrl={ - front.config.discussionApiUrl - } > { collection, )} sectionId={ophanName} - collectionId={collection.id} pageId={front.pressedPage.id} showDateHeader={ collection.config.showDateHeader } editionId={front.editionId} treats={collection.treats} - canShowMore={collection.canShowMore} - ajaxUrl={front.config.ajaxUrl} isOnPaidContentFront={isPaidContent} targetedTerritory={collection.targetedTerritory} hasPageSkin={hasPageSkin} - discussionApiUrl={front.config.discussionApiUrl} collectionBranding={ collection.collectionBranding } diff --git a/dotcom-rendering/src/layouts/TagPageLayout.tsx b/dotcom-rendering/src/layouts/TagPageLayout.tsx index d04caa4d218..54134266100 100644 --- a/dotcom-rendering/src/layouts/TagPageLayout.tsx +++ b/dotcom-rendering/src/layouts/TagPageLayout.tsx @@ -193,19 +193,14 @@ export const TagPageLayout = ({ tagPage, NAV }: Props) => { toggleable={false} pageId={tagPage.pageId} editionId={tagPage.editionId} - canShowMore={false} - ajaxUrl={tagPage.config.ajaxUrl} pagination={tagPagePagination} - discussionApiUrl={ - tagPage.config.discussionApiUrl - } > {insertStorylinesSection && diff --git a/dotcom-rendering/src/lib/dynamicSlices.test.ts b/dotcom-rendering/src/lib/dynamicSlices.test.ts deleted file mode 100644 index 8b6fa51e064..00000000000 --- a/dotcom-rendering/src/lib/dynamicSlices.test.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { shouldPadWrappableRows } from './dynamicSlices'; - -describe('shouldPadWrappableRows', () => { - describe('Three columns', () => { - const columns = 3; - describe('Five cards', () => { - const cards = 5; - // true: 0 1 2 - // false: 3 4 - it.each([ - [0, true], - [1, true], - [2, true], - [3, false], - [4, false], - ])('card number %s should return %s', (index, expected) => { - expect(shouldPadWrappableRows(index, cards, columns)).toBe( - expected, - ); - }); - }); - - describe('Six cards', () => { - const cards = 6; - // true: 0 1 2 - // false: 3 4 5 - it.each([ - [0, true], - [1, true], - [2, true], - [3, false], - [4, false], - [5, false], - ])('card number %s should return %s', (index, expected) => { - expect(shouldPadWrappableRows(index, cards, columns)).toBe( - expected, - ); - }); - }); - - describe('Seven cards', () => { - const cards = 7; - // true: 0 1 2 - // true: 3 4 5 - // false: 6 - it.each([ - [0, true], - [1, true], - [2, true], - [3, true], - [4, true], - [5, true], - [6, false], - ])('card number %s should return %s', (index, expected) => { - expect(shouldPadWrappableRows(index, cards, columns)).toBe( - expected, - ); - }); - }); - }); - - describe('Two columns', () => { - const columns = 2; - describe('Three cards', () => { - const cards = 3; - // true: 0 1 - // false: 2 - it.each([ - [0, true], - [1, true], - [2, false], - ])('card number %s should return %s', (index, expected) => { - expect(shouldPadWrappableRows(index, cards, columns)).toBe( - expected, - ); - }); - }); - - describe('Four cards', () => { - const cards = 4; - // true: 0 1 - // false: 2 3 - it.each([ - [0, true], - [1, true], - [2, false], - [3, false], - ])('card number %s should return %s', (index, expected) => { - expect(shouldPadWrappableRows(index, cards, columns)).toBe( - expected, - ); - }); - }); - - describe('Five cards', () => { - const cards = 5; - // true: 0 1 - // true: 2 3 - // false: 4 - it.each([ - [0, true], - [1, true], - [2, true], - [3, true], - [4, false], - ])('card number %s should return %s', (index, expected) => { - expect(shouldPadWrappableRows(index, cards, columns)).toBe( - expected, - ); - }); - }); - }); -}); diff --git a/dotcom-rendering/src/lib/dynamicSlices.tsx b/dotcom-rendering/src/lib/dynamicSlices.tsx deleted file mode 100644 index a7dbea201b9..00000000000 --- a/dotcom-rendering/src/lib/dynamicSlices.tsx +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Abstraction to decide whether to show padding on wrapped rows of cards. - * - * For three columns, We have different results with 5 or 9 cards - * - * @example - All but last 2 - * ``` - * ┌───┐ ┌───┐ ┌───┐ - * │Pad│ │Pad│ │Pad│ - * └───┘ └───┘ └───┘ - * ┌───┐ ┌───┐ - * │No!│ │No!│ - * └───┘ └───┘ - * ``` - * - All but last 3 - * ``` - * ┌───┐ ┌───┐ ┌───┐ - * │Pad│ │Pad│ │Pad│ - * └───┘ └───┘ └───┘ - * ┌───┐ ┌───┐ ┌───┐ - * │Pad│ │Pad│ │Pad│ - * └───┘ └───┘ └───┘ - * ┌───┐ ┌───┐ ┌───┐ - * │No!│ │No!│ │No!│ - * └───┘ └───┘ └───┘ - * ``` - * - * @param index - Index of the current card - * @param totalCards - Total number of cards being shown - * @param cardsPerRow - No. of cards in each row (if full) - */ -export const shouldPadWrappableRows = ( - index: number, - totalCards: number, - cardsPerRow: number, -): boolean => index < totalCards - (totalCards % cardsPerRow || cardsPerRow); diff --git a/dotcom-rendering/src/model/enhanceCollections.ts b/dotcom-rendering/src/model/enhanceCollections.ts index 7e6c62464ba..53d8c2ac088 100644 --- a/dotcom-rendering/src/model/enhanceCollections.ts +++ b/dotcom-rendering/src/model/enhanceCollections.ts @@ -78,7 +78,7 @@ export const enhanceCollections = ({ findCollectionSuitableForFrontBranding(collections); return collections.filter(isSupported).map((collection, index) => { - const { id, displayName, collectionType, hasMore, href, description } = + const { id, displayName, collectionType, href, description } = collection; const allCards = [...collection.curated, ...collection.backfill]; @@ -118,9 +118,6 @@ export const enhanceCollections = ({ const isNextCollectionPrimary = collections[index + 1]?.config.collectionLevel === 'Primary'; - const isBetaContainer = BETA_CONTAINERS.includes( - collection.collectionType, - ); return { id, @@ -164,8 +161,6 @@ export const enhanceCollections = ({ config: { showDateHeader: collection.config.showDateHeader, }, - canShowMore: - hasMore && !collection.config.hideShowMore && !isBetaContainer, targetedTerritory: collection.targetedTerritory, aspectRatio: collection.config.aspectRatio, }; diff --git a/dotcom-rendering/src/types/front.ts b/dotcom-rendering/src/types/front.ts index f3d8f87a6fd..a6e404741e5 100644 --- a/dotcom-rendering/src/types/front.ts +++ b/dotcom-rendering/src/types/front.ts @@ -135,13 +135,6 @@ export type DCRCollectionType = { config: { showDateHeader: boolean; }; - /** - * @property {?boolean} canShowMore - Whether the 'show more' button should be shown. - * nb. the value of this will typically reflect the `FECollectionType.hasMore` value we get from Frontend, - * except when `FECollectionType.config.hideShowMore` is set to `true`, in which case `DCRCollectionType.canShowMore` - * will always be `false`. - **/ - canShowMore?: boolean; collectionBranding?: CollectionBranding; targetedTerritory?: Territory; aspectRatio?: AspectRatio;