From 74d18bf766ed041febe23267326c5294c3f5d55e Mon Sep 17 00:00:00 2001 From: Frederick O'Brien Date: Thu, 11 Jun 2026 17:54:02 +0100 Subject: [PATCH 1/4] Move AudioLayout to grid --- .../src/layouts/AudioLayout.stories.tsx | 6 +- dotcom-rendering/src/layouts/AudioLayout.tsx | 735 ------------------ dotcom-rendering/src/layouts/DecideLayout.tsx | 5 +- .../src/layouts/StandardLayoutArticleGrid.tsx | 78 +- 4 files changed, 52 insertions(+), 772 deletions(-) delete mode 100644 dotcom-rendering/src/layouts/AudioLayout.tsx diff --git a/dotcom-rendering/src/layouts/AudioLayout.stories.tsx b/dotcom-rendering/src/layouts/AudioLayout.stories.tsx index 9227a2e43d9..9b13f7271c8 100644 --- a/dotcom-rendering/src/layouts/AudioLayout.stories.tsx +++ b/dotcom-rendering/src/layouts/AudioLayout.stories.tsx @@ -5,12 +5,12 @@ import { ArticleDesign } from '../lib/articleFormat'; import { getCurrentPillar } from '../lib/layoutHelpers'; import { extractNAV } from '../model/extract-nav'; import { enhanceArticleType } from '../types/article'; -import { AudioLayout } from './AudioLayout'; +import { StandardLayout } from './StandardLayout'; const meta = { title: 'Layouts/Audio', - component: AudioLayout, -} satisfies Meta; + component: StandardLayout, +} satisfies Meta; export default meta; diff --git a/dotcom-rendering/src/layouts/AudioLayout.tsx b/dotcom-rendering/src/layouts/AudioLayout.tsx deleted file mode 100644 index cb848515995..00000000000 --- a/dotcom-rendering/src/layouts/AudioLayout.tsx +++ /dev/null @@ -1,735 +0,0 @@ -import { css } from '@emotion/react'; -import { - from, - palette as sourcePalette, - space, - until, -} from '@guardian/source/foundations'; -import { StraightLines } from '@guardian/source-development-kitchen/react-components'; -import { AdPortals } from '../components/AdPortals.island'; -import { AdSlot, MobileStickyContainer } from '../components/AdSlot.web'; -import { AffiliateDisclaimer } from '../components/AffiliateDisclaimer'; -import { AppsFooter } from '../components/AppsFooter.island'; -import { ArticleBody } from '../components/ArticleBody'; -import { ArticleContainer } from '../components/ArticleContainer'; -import { ArticleHeadline } from '../components/ArticleHeadline'; -import { ArticleMetaApps } from '../components/ArticleMeta.apps'; -import { ArticleMeta } from '../components/ArticleMeta.web'; -import { ArticleTitle } from '../components/ArticleTitle'; -import { AudioPlayer } from '../components/AudioPlayer/AudioPlayer'; -import { Border } from '../components/Border'; -import { Carousel } from '../components/Carousel.island'; -import { DirectoryPageNavIsland } from '../components/DirectoryPageNavIsland'; -import { DiscussionLayout } from '../components/DiscussionLayout'; -import { Footer } from '../components/Footer'; -import { GridItem } from '../components/GridItem'; -import { HeaderAdSlot } from '../components/HeaderAdSlot'; -import { Island } from '../components/Island'; -import { LabsHeader } from '../components/LabsHeader'; -import { Masthead } from '../components/Masthead/Masthead'; -import { MostViewedFooterData } from '../components/MostViewedFooterData.island'; -import { MostViewedFooterLayout } from '../components/MostViewedFooterLayout'; -import { MostViewedRightWithAd } from '../components/MostViewedRightWithAd.island'; -import { OnwardsUpper } from '../components/OnwardsUpper.island'; -import { RightColumn } from '../components/RightColumn'; -import { Section } from '../components/Section'; -import { SlotBodyEnd } from '../components/SlotBodyEnd.island'; -import { Standfirst } from '../components/Standfirst'; -import { StickyBottomBanner } from '../components/StickyBottomBanner.island'; -import { SubMeta } from '../components/SubMeta'; -import { SubNav } from '../components/SubNav.island'; -import { type ArticleFormat, ArticleSpecial } from '../lib/articleFormat'; -import { getAudioData } from '../lib/audio-data'; -import { canRenderAds } from '../lib/canRenderAds'; -import { getContributionsServiceUrl } from '../lib/contributions'; -import { decideStoryPackageTrails } from '../lib/decideTrail'; -import { parse } from '../lib/slot-machine-flags'; -import { worldCupTagId } from '../lib/worldCup2026'; -import type { NavType } from '../model/extract-nav'; -import { palette as themePalette } from '../palette'; -import type { ArticleDeprecated } from '../types/article'; -import type { RenderingTarget } from '../types/renderingTarget'; -import { BannerWrapper, Stuck } from './lib/stickiness'; - -const AudioGrid = ({ children }: { children: React.ReactNode }) => ( -
- {children} -
-); - -const maxWidth = css` - ${from.desktop} { - max-width: 620px; - } -`; - -interface Props { - article: ArticleDeprecated; - format: ArticleFormat; - renderingTarget: RenderingTarget; - serverTime?: number; -} - -interface WebProps extends Props { - NAV: NavType; - renderingTarget: 'Web'; -} - -interface AppProps extends Props { - renderingTarget: 'Apps'; -} - -export const AudioLayout = (props: WebProps | AppProps) => { - const { article, format, renderingTarget, serverTime } = props; - const isWeb = renderingTarget === 'Web'; - const isApps = renderingTarget === 'Apps'; - const audioData = getAudioData(article.mainMediaElements); - - const { - config: { isPaidContent, host, hasSurveyAd }, - editionId, - } = article; - - const showBodyEndSlot = parse(article.slotMachineFlags ?? '').showBodyEnd; - - const showComments = article.isCommentable && !isPaidContent; - - const { branding } = article.commercialProperties[article.editionId]; - - const contributionsServiceUrl = getContributionsServiceUrl(article); - - const isLabs = format.theme === ArticleSpecial.Labs; - - const isWorldCup2026 = article.tags.some((tag) => tag.id === worldCupTagId); - - const renderAds = canRenderAds(article); - - return ( - <> - {isWeb && ( -
- {renderAds && ( - -
- -
-
- )} - tag.id)} - contentType={article.contentType} - /> -
- )} - - {isWeb && format.theme === ArticleSpecial.Labs && ( - -
- -
-
- )} - - {isWeb && renderAds && hasSurveyAd && ( - - )} - -
- {isApps && renderAds && ( - - - - )} - - -
- - - - - - {format.theme === ArticleSpecial.Labs ? ( - <> - ) : ( - - )} - - -
- -
-
- -
- {renderingTarget === 'Web' ? ( - - ) : ( - - )} - {!!article.affiliateLinksDisclaimer && ( - - )} -
-
- - {audioData && ( - - - - )} - - - - - - - - - {isWeb && showBodyEndSlot && ( - - - - )} - - - - - - {isWeb && ( -
- - - - - -
- )} -
-
-
- - {isWeb && renderAds && !isLabs && ( -
- -
- )} - - {article.storyPackage && ( -
- - - -
- )} - - - - - {isWeb && showComments && ( -
- -
- )} - - {!isPaidContent && ( -
- - - - - -
- )} - - {isWeb && renderAds && !isLabs && ( -
- -
- )} -
- - {isWeb && ( - <> - {props.NAV.subNavSections && ( -
- - - -
- )} -
-
-
- - - - - - {renderAds && ( - - )} - - )} - {isApps && ( -
- - - -
- )} - - ); -}; diff --git a/dotcom-rendering/src/layouts/DecideLayout.tsx b/dotcom-rendering/src/layouts/DecideLayout.tsx index b6dd7f4a27c..7724a3dbd64 100644 --- a/dotcom-rendering/src/layouts/DecideLayout.tsx +++ b/dotcom-rendering/src/layouts/DecideLayout.tsx @@ -2,7 +2,6 @@ import { ArticleDesign, ArticleDisplay } from '../lib/articleFormat'; import type { NavType } from '../model/extract-nav'; import type { Article } from '../types/article'; import type { RenderingTarget } from '../types/renderingTarget'; -import { AudioLayout } from './AudioLayout'; import { CommentLayout } from './CommentLayout'; import { CrosswordLayout } from './CrosswordLayout'; import { FullPageInteractiveLayout } from './FullPageInteractiveLayout'; @@ -191,7 +190,7 @@ const DecideLayoutApps = ({ article, renderingTarget }: AppProps) => { ); case ArticleDesign.Audio: return ( - { ); case ArticleDesign.Audio: return ( - - + {format.design === ArticleDesign.Audio && audioData ? ( + + ) : ( + + )} -
- {isWeb && - format.theme === ArticleSpecial.Labs && - format.design !== ArticleDesign.Video ? ( - - ) : ( - - )} -
+ {format.design !== ArticleDesign.Audio && ( +
+ {isWeb && + format.theme === ArticleSpecial.Labs && + format.design !== ArticleDesign.Video ? ( + + ) : ( + + )} +
+ )} {isApps ? ( <> @@ -279,7 +294,7 @@ export const StandardLayoutArticleGrid = ({ {/* Only show Listen to Article button on App landscape views */} {isApps && ( - {!isVideo && ( + {!isMedia && (
From 6493ff6a50b44158061aec1100289c31540e794a Mon Sep 17 00:00:00 2001 From: Frederick O'Brien Date: Tue, 23 Jun 2026 14:27:33 +0100 Subject: [PATCH 2/4] Create AudioPlayer component --- .../src/components/AudioPlayer.tsx | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 dotcom-rendering/src/components/AudioPlayer.tsx diff --git a/dotcom-rendering/src/components/AudioPlayer.tsx b/dotcom-rendering/src/components/AudioPlayer.tsx new file mode 100644 index 00000000000..ddb489b8f96 --- /dev/null +++ b/dotcom-rendering/src/components/AudioPlayer.tsx @@ -0,0 +1,52 @@ +import { css } from '@emotion/react'; +import { space } from '@guardian/source/foundations'; +import { StraightLines } from '@guardian/source-development-kitchen/react-components'; +import { palette } from '../palette'; +import { AppsAudioPlayer } from './AppsAudioPlayer.island'; +import { AudioPlayerWrapper } from './AudioPlayerWrapper.island'; +import { formatAudioDuration } from './ListenToArticle.island'; + +export const AudioPlayer = ({ + audioData, + isSensitive, + isAcastEnabled, + isApps, +}: { + audioData: { + audioDownloadUrl: string; + durationSeconds?: number; + mediaId: string; + }; + isSensitive: boolean; + isAcastEnabled: boolean; + isApps: boolean; +}) => { + return ( + <> + {isApps ? ( + + ) : ( + + )} + + + ); +}; From b7c92739fc1dd0cc071c75d74269a0ec93b2e7d4 Mon Sep 17 00:00:00 2001 From: Frederick O'Brien Date: Tue, 7 Jul 2026 14:52:47 +0100 Subject: [PATCH 3/4] Review edits --- .../src/components/AudioPlayer.tsx | 52 ------------------ .../AudioPlayer/DecideAudioPlayer.tsx | 55 +++++++++++++++++++ ...stories.tsx => StandardLayout.stories.tsx} | 50 ++++++++--------- 3 files changed, 80 insertions(+), 77 deletions(-) delete mode 100644 dotcom-rendering/src/components/AudioPlayer.tsx create mode 100644 dotcom-rendering/src/components/AudioPlayer/DecideAudioPlayer.tsx rename dotcom-rendering/src/layouts/{AudioLayout.stories.tsx => StandardLayout.stories.tsx} (50%) diff --git a/dotcom-rendering/src/components/AudioPlayer.tsx b/dotcom-rendering/src/components/AudioPlayer.tsx deleted file mode 100644 index ddb489b8f96..00000000000 --- a/dotcom-rendering/src/components/AudioPlayer.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { css } from '@emotion/react'; -import { space } from '@guardian/source/foundations'; -import { StraightLines } from '@guardian/source-development-kitchen/react-components'; -import { palette } from '../palette'; -import { AppsAudioPlayer } from './AppsAudioPlayer.island'; -import { AudioPlayerWrapper } from './AudioPlayerWrapper.island'; -import { formatAudioDuration } from './ListenToArticle.island'; - -export const AudioPlayer = ({ - audioData, - isSensitive, - isAcastEnabled, - isApps, -}: { - audioData: { - audioDownloadUrl: string; - durationSeconds?: number; - mediaId: string; - }; - isSensitive: boolean; - isAcastEnabled: boolean; - isApps: boolean; -}) => { - return ( - <> - {isApps ? ( - - ) : ( - - )} - - - ); -}; diff --git a/dotcom-rendering/src/components/AudioPlayer/DecideAudioPlayer.tsx b/dotcom-rendering/src/components/AudioPlayer/DecideAudioPlayer.tsx new file mode 100644 index 00000000000..4f202cce2f2 --- /dev/null +++ b/dotcom-rendering/src/components/AudioPlayer/DecideAudioPlayer.tsx @@ -0,0 +1,55 @@ +import { css } from '@emotion/react'; +import { space } from '@guardian/source/foundations'; +import { StraightLines } from '@guardian/source-development-kitchen/react-components'; +import { palette } from '../../palette'; +import { AppsAudioPlayer } from '../AppsAudioPlayer.island'; +import { AudioPlayerWrapper } from '../AudioPlayerWrapper.island'; +import { Island } from '../Island'; +import { formatAudioDuration } from '../ListenToArticle.island'; + +export const DecideAudioPlayer = ({ + audioData, + isSensitive, + isAcastEnabled, + isApps, +}: { + audioData: { + audioDownloadUrl: string; + durationSeconds?: number; + mediaId: string; + }; + isSensitive: boolean; + isAcastEnabled: boolean; + isApps: boolean; +}) => { + return ( + <> + + {isApps ? ( + + ) : ( + + )} + + + + ); +}; diff --git a/dotcom-rendering/src/layouts/AudioLayout.stories.tsx b/dotcom-rendering/src/layouts/StandardLayout.stories.tsx similarity index 50% rename from dotcom-rendering/src/layouts/AudioLayout.stories.tsx rename to dotcom-rendering/src/layouts/StandardLayout.stories.tsx index 9b13f7271c8..f4d94df458f 100644 --- a/dotcom-rendering/src/layouts/AudioLayout.stories.tsx +++ b/dotcom-rendering/src/layouts/StandardLayout.stories.tsx @@ -8,7 +8,7 @@ import { enhanceArticleType } from '../types/article'; import { StandardLayout } from './StandardLayout'; const meta = { - title: 'Layouts/Audio', + title: 'Layouts/Standard', component: StandardLayout, } satisfies Meta; @@ -16,30 +16,30 @@ export default meta; type Story = StoryObj; -const appsArticle = enhanceArticleType(AudioFixture, 'Apps'); +const appsAudioArticle = enhanceArticleType(AudioFixture, 'Apps'); -if (appsArticle.design !== ArticleDesign.Audio) { +if (appsAudioArticle.design !== ArticleDesign.Audio) { throw new Error( - `Expected ArticleDesign.Audio, got: ${String(appsArticle.design)}`, + `Expected ArticleDesign.Audio, got: ${String(appsAudioArticle.design)}`, ); } -export const Apps: Story = { +export const AppsAudio: Story = { args: { renderingTarget: 'Apps', - article: appsArticle.frontendData, + article: appsAudioArticle.frontendData, format: { - design: appsArticle.design, - display: appsArticle.display, - theme: appsArticle.theme, + design: appsAudioArticle.design, + display: appsAudioArticle.display, + theme: appsAudioArticle.theme, }, }, parameters: { formats: [ { - design: appsArticle.design, - display: appsArticle.display, - theme: appsArticle.theme, + design: appsAudioArticle.design, + display: appsAudioArticle.display, + theme: appsAudioArticle.theme, }, ], config: { @@ -54,34 +54,34 @@ export const Apps: Story = { }, }; -const webArticle = enhanceArticleType(AudioFixture, 'Web'); +const webAudioArticle = enhanceArticleType(AudioFixture, 'Web'); -if (webArticle.design !== ArticleDesign.Audio) { +if (webAudioArticle.design !== ArticleDesign.Audio) { throw new Error( - `Expected ArticleDesign.Audio, got: ${String(webArticle.design)}`, + `Expected ArticleDesign.Audio, got: ${String(webAudioArticle.design)}`, ); } -export const Web: Story = { +export const WebAudio: Story = { args: { renderingTarget: 'Web', NAV: { - ...extractNAV(webArticle.frontendData.nav), - selectedPillar: getCurrentPillar(webArticle.frontendData), + ...extractNAV(webAudioArticle.frontendData.nav), + selectedPillar: getCurrentPillar(webAudioArticle.frontendData), }, - article: webArticle.frontendData, + article: webAudioArticle.frontendData, format: { - design: webArticle.design, - display: webArticle.display, - theme: webArticle.theme, + design: webAudioArticle.design, + display: webAudioArticle.display, + theme: webAudioArticle.theme, }, }, parameters: { formats: [ { - design: webArticle.design, - display: webArticle.display, - theme: webArticle.theme, + design: webAudioArticle.design, + display: webAudioArticle.display, + theme: webAudioArticle.theme, }, ], chromatic: { From f2b046698d6bff78a9cdc6d1709255a1ed6d13b9 Mon Sep 17 00:00:00 2001 From: Frederick O'Brien Date: Tue, 7 Jul 2026 15:34:27 +0100 Subject: [PATCH 4/4] Tidy up audio player components --- .../components/AudioPlayer/AudioPlayer.tsx | 50 +++++++++++------ .../AudioPlayer/DecideAudioPlayer.tsx | 55 ------------------- 2 files changed, 32 insertions(+), 73 deletions(-) delete mode 100644 dotcom-rendering/src/components/AudioPlayer/DecideAudioPlayer.tsx diff --git a/dotcom-rendering/src/components/AudioPlayer/AudioPlayer.tsx b/dotcom-rendering/src/components/AudioPlayer/AudioPlayer.tsx index d5fc2805626..8c4130fa553 100644 --- a/dotcom-rendering/src/components/AudioPlayer/AudioPlayer.tsx +++ b/dotcom-rendering/src/components/AudioPlayer/AudioPlayer.tsx @@ -1,3 +1,7 @@ +import { css } from '@emotion/react'; +import { space } from '@guardian/source/foundations'; +import { StraightLines } from '@guardian/source-development-kitchen/react-components'; +import { palette } from '../../palette'; import { Island } from '../Island'; import { formatAudioDuration } from '../ListenToArticle.island'; import { AudioPlayerApps } from './AudioPlayerApps.island'; @@ -19,23 +23,33 @@ export const AudioPlayer = ({ isApps: boolean; }) => { return ( - - {isApps ? ( - - ) : ( - - )} - + <> + + {isApps ? ( + + ) : ( + + )} + + + ); }; diff --git a/dotcom-rendering/src/components/AudioPlayer/DecideAudioPlayer.tsx b/dotcom-rendering/src/components/AudioPlayer/DecideAudioPlayer.tsx deleted file mode 100644 index 4f202cce2f2..00000000000 --- a/dotcom-rendering/src/components/AudioPlayer/DecideAudioPlayer.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { css } from '@emotion/react'; -import { space } from '@guardian/source/foundations'; -import { StraightLines } from '@guardian/source-development-kitchen/react-components'; -import { palette } from '../../palette'; -import { AppsAudioPlayer } from '../AppsAudioPlayer.island'; -import { AudioPlayerWrapper } from '../AudioPlayerWrapper.island'; -import { Island } from '../Island'; -import { formatAudioDuration } from '../ListenToArticle.island'; - -export const DecideAudioPlayer = ({ - audioData, - isSensitive, - isAcastEnabled, - isApps, -}: { - audioData: { - audioDownloadUrl: string; - durationSeconds?: number; - mediaId: string; - }; - isSensitive: boolean; - isAcastEnabled: boolean; - isApps: boolean; -}) => { - return ( - <> - - {isApps ? ( - - ) : ( - - )} - - - - ); -};