From 15396abb2b7c7ee2f12bbc353c11a32b23ce4bd5 Mon Sep 17 00:00:00 2001 From: Wai Sing Yiu Date: Wed, 4 Feb 2026 17:40:24 +0000 Subject: [PATCH 1/9] Support slideshow --- .../components/MultiImageBlockComponent.tsx | 170 +++++++++++------- dotcom-rendering/src/model/enhance-images.ts | 2 +- 2 files changed, 104 insertions(+), 68 deletions(-) diff --git a/dotcom-rendering/src/components/MultiImageBlockComponent.tsx b/dotcom-rendering/src/components/MultiImageBlockComponent.tsx index c4c1b89927b..8167a341254 100644 --- a/dotcom-rendering/src/components/MultiImageBlockComponent.tsx +++ b/dotcom-rendering/src/components/MultiImageBlockComponent.tsx @@ -5,6 +5,9 @@ import type { ImageBlockElement } from '../types/content'; import { Caption } from './Caption'; import { GridItem } from './GridItem'; import { ImageComponent } from './ImageComponent'; +import { SlideshowCarousel } from './SlideshowCarousel.importable'; +import { getLargest, getMaster } from '../lib/image'; +import { Island } from './Island'; type Props = { images: ImageBlockElement[]; @@ -58,27 +61,27 @@ const SideBySideGrid = ({ children }: { children: React.ReactNode }) => ( ); -const OneAboveTwoGrid = ({ children }: { children: React.ReactNode }) => ( -
- {children} -
-); +// const OneAboveTwoGrid = ({ children }: { children: React.ReactNode }) => ( +//
+// {children} +//
+// ); const GridOfFour = ({ children }: { children: React.ReactNode }) => (
); -const ThreeImage = ({ - images, - format, - caption, -}: { - images: [ImageBlockElement, ImageBlockElement, ImageBlockElement]; - format: ArticleFormat; - caption?: string; -}) => ( -
- - - - - - - - - - - - {!!caption && ( - - )} -
-); +// const ThreeImage = ({ +// images, +// format, +// caption, +// }: { +// images: [ImageBlockElement, ImageBlockElement, ImageBlockElement]; +// format: ArticleFormat; +// caption?: string; +// }) => ( +//
+// +// +// +// +// +// +// +// +// +// +// +// {!!caption && ( +// +// )} +//
+// ); const FourImage = ({ images, @@ -271,6 +274,39 @@ const FourImage = ({
); +const Slideshow = ({ + images, + format, + caption, +}: { + images: ImageBlockElement[]; + format: ArticleFormat; + caption?: string; +}) => { + const imagesForSlideshow = images.map((element) => { + /** Legacy images do not have a master so we fallback to the largest available */ + const image = + getMaster(element.media.allImages) ?? + getLargest(element.media.allImages); + return { + imageSrc: image?.url ?? '', + imageCaption: element.data.caption, + }; + }); + return ( + + + + ); +}; + export const MultiImageBlockComponent = ({ images, format, @@ -290,7 +326,7 @@ export const MultiImageBlockComponent = ({ if (one && two && three) { return ( - Date: Thu, 5 Feb 2026 09:46:32 +0000 Subject: [PATCH 2/9] Show caption in slideshow format if it is provided --- .../components/MultiImageBlockComponent.tsx | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/dotcom-rendering/src/components/MultiImageBlockComponent.tsx b/dotcom-rendering/src/components/MultiImageBlockComponent.tsx index 8167a341254..4828f2ba1da 100644 --- a/dotcom-rendering/src/components/MultiImageBlockComponent.tsx +++ b/dotcom-rendering/src/components/MultiImageBlockComponent.tsx @@ -294,16 +294,25 @@ const Slideshow = ({ }; }); return ( - - - + <> + + + + {!!caption && ( + + )} + ); }; From 6cdfc874aece7b6e3051f508f48f9312302b06a2 Mon Sep 17 00:00:00 2001 From: Wai Sing Yiu Date: Thu, 5 Feb 2026 10:28:35 +0000 Subject: [PATCH 3/9] Add support for slideshow to multi-image element --- .../components/MultiImageBlockComponent.tsx | 207 +++++++++--------- .../src/frontend/schemas/feArticle.json | 7 + dotcom-rendering/src/lib/renderElement.tsx | 1 + dotcom-rendering/src/model/block-schema.json | 7 + dotcom-rendering/src/types/content.ts | 1 + 5 files changed, 122 insertions(+), 101 deletions(-) diff --git a/dotcom-rendering/src/components/MultiImageBlockComponent.tsx b/dotcom-rendering/src/components/MultiImageBlockComponent.tsx index 4828f2ba1da..83412e2b741 100644 --- a/dotcom-rendering/src/components/MultiImageBlockComponent.tsx +++ b/dotcom-rendering/src/components/MultiImageBlockComponent.tsx @@ -13,6 +13,7 @@ type Props = { images: ImageBlockElement[]; format: ArticleFormat; caption?: string; + presentation?: 'slideshow' | 'grid'; }; const ieFallback = css` @@ -61,27 +62,27 @@ const SideBySideGrid = ({ children }: { children: React.ReactNode }) => ( ); -// const OneAboveTwoGrid = ({ children }: { children: React.ReactNode }) => ( -//
-// {children} -//
-// ); +const OneAboveTwoGrid = ({ children }: { children: React.ReactNode }) => ( +
+ {children} +
+); const GridOfFour = ({ children }: { children: React.ReactNode }) => (
); -// const ThreeImage = ({ -// images, -// format, -// caption, -// }: { -// images: [ImageBlockElement, ImageBlockElement, ImageBlockElement]; -// format: ArticleFormat; -// caption?: string; -// }) => ( -//
-// -// -// -// -// -// -// -// -// -// -// -// {!!caption && ( -// -// )} -//
-// ); +const ThreeImage = ({ + images, + format, + caption, +}: { + images: [ImageBlockElement, ImageBlockElement, ImageBlockElement]; + format: ArticleFormat; + caption?: string; +}) => ( +
+ + + + + + + + + + + + {!!caption && ( + + )} +
+); const FourImage = ({ images, @@ -305,13 +306,6 @@ const Slideshow = ({ dataLinkName="slideshow-carousel-1" /> - {!!caption && ( - - )} ); }; @@ -320,38 +314,49 @@ export const MultiImageBlockComponent = ({ images, format, caption, + presentation, }: Props) => { - const [one, two, three, four] = images; + if (presentation && presentation === 'slideshow') { + return ; + } else { + const [one, two, three, four] = images; - if (one && two && three && four) { - return ( - - ); - } + if (one && two && three && four) { + return ( + + ); + } - if (one && two && three) { - return ( - - ); - } + if (one && two && three) { + return ( + + ); + } - if (one && two) { - return ( - - ); - } + if (one && two) { + return ( + + ); + } - if (one) { - return ; - } + if (one) { + return ( + + ); + } - return null; + return null; + } }; diff --git a/dotcom-rendering/src/frontend/schemas/feArticle.json b/dotcom-rendering/src/frontend/schemas/feArticle.json index ba05d3e81f5..eebdb866568 100644 --- a/dotcom-rendering/src/frontend/schemas/feArticle.json +++ b/dotcom-rendering/src/frontend/schemas/feArticle.json @@ -2935,6 +2935,13 @@ }, "role": { "$ref": "#/definitions/RoleType" + }, + "presentation": { + "enum": [ + "grid", + "slideshow" + ], + "type": "string" } }, "required": [ diff --git a/dotcom-rendering/src/lib/renderElement.tsx b/dotcom-rendering/src/lib/renderElement.tsx index a04b2026e0a..1d39554cce3 100644 --- a/dotcom-rendering/src/lib/renderElement.tsx +++ b/dotcom-rendering/src/lib/renderElement.tsx @@ -568,6 +568,7 @@ export const renderElement = ({ key={index} images={element.images} caption={element.caption} + presentation={element.presentation} /> ); case 'model.dotcomrendering.pageElements.NewsletterSignupBlockElement': diff --git a/dotcom-rendering/src/model/block-schema.json b/dotcom-rendering/src/model/block-schema.json index 97c8187254a..a91112f6d48 100644 --- a/dotcom-rendering/src/model/block-schema.json +++ b/dotcom-rendering/src/model/block-schema.json @@ -2423,6 +2423,13 @@ }, "role": { "$ref": "#/definitions/RoleType" + }, + "presentation": { + "enum": [ + "grid", + "slideshow" + ], + "type": "string" } }, "required": [ diff --git a/dotcom-rendering/src/types/content.ts b/dotcom-rendering/src/types/content.ts index 3fbd75dcc98..4587923a89d 100644 --- a/dotcom-rendering/src/types/content.ts +++ b/dotcom-rendering/src/types/content.ts @@ -464,6 +464,7 @@ export interface MultiImageBlockElement { images: ImageBlockElement[]; caption?: string; role?: RoleType; + presentation?: 'slideshow' | 'grid'; } export interface NewsletterSignupBlockElement { From 3ce603b641a8aa030d5afe1218169c914661a109 Mon Sep 17 00:00:00 2001 From: Wai Sing Yiu Date: Thu, 5 Feb 2026 10:51:04 +0000 Subject: [PATCH 4/9] Produce a caption for multi-image element from captions of individual images --- .../components/MultiImageBlockComponent.tsx | 295 +++++++++++------- 1 file changed, 175 insertions(+), 120 deletions(-) diff --git a/dotcom-rendering/src/components/MultiImageBlockComponent.tsx b/dotcom-rendering/src/components/MultiImageBlockComponent.tsx index 83412e2b741..e8d1e748fd1 100644 --- a/dotcom-rendering/src/components/MultiImageBlockComponent.tsx +++ b/dotcom-rendering/src/components/MultiImageBlockComponent.tsx @@ -106,6 +106,14 @@ const GridOfFour = ({ children }: { children: React.ReactNode }) => (
); +const removeLastFullStop = (text?: string) => { + if (!text) return text; + if (text.endsWith('.')) { + return text.slice(0, -1); + } + return text; +}; + const OneImage = ({ images, format, @@ -114,23 +122,26 @@ const OneImage = ({ images: [ImageBlockElement]; format: ArticleFormat; caption?: string; -}) => ( -
- - {!!caption && ( - { + const captionToUse = caption ?? removeLastFullStop(images[0].data.caption); + return ( +
+ - )} -
-); + {!!captionToUse && ( + + )} +
+ ); +}; const TwoImage = ({ images, @@ -140,35 +151,44 @@ const TwoImage = ({ images: [ImageBlockElement, ImageBlockElement]; format: ArticleFormat; caption?: string; -}) => ( -
- - - - - - { + const captionLeft = + images[0].data.caption && + `${removeLastFullStop(images[0].data.caption)} (above left). `; + const captionRight = + images[1].data.caption && + `${removeLastFullStop(images[1].data.caption)} (above right). `; + const captionToUse = caption ?? `${captionLeft ?? ''}${captionRight ?? ''}`; + return ( +
+ + + + + + + + + {!!captionToUse && ( + - - - {!!caption && ( - - )} -
-); + )} +
+ ); +}; const ThreeImage = ({ images, @@ -178,43 +198,59 @@ const ThreeImage = ({ images: [ImageBlockElement, ImageBlockElement, ImageBlockElement]; format: ArticleFormat; caption?: string; -}) => ( -
- - - - - - - - - { + const captionTop = + images[0].data.caption && + `${removeLastFullStop(images[0].data.caption)} (top). `; + const captionBottomLeft = + images[1].data.caption && + `${removeLastFullStop(images[1].data.caption)} (bottom left). `; + const captionBottomRight = + images[2].data.caption && + `${removeLastFullStop(images[2].data.caption)} (bottom right). `; + const captionToUse = + caption ?? + `${captionTop ?? ''}${captionBottomLeft ?? ''}${ + captionBottomRight ?? '' + }`; + return ( +
+ + + + + + + + + + + + {!!captionToUse && ( + - - - {!!caption && ( - - )} -
-); + )} +
+ ); +}; const FourImage = ({ images, @@ -229,51 +265,70 @@ const FourImage = ({ ]; format: ArticleFormat; caption?: string; -}) => ( -
- - - - - - - - - - - - { + const captionTopLeft = + images[0].data.caption && + `${removeLastFullStop(images[0].data.caption)} (top left). `; + const captionTopRight = + images[1].data.caption && + `${removeLastFullStop(images[1].data.caption)} (top right). `; + const captionBottomLeft = + images[2].data.caption && + `${removeLastFullStop(images[2].data.caption)} (bottom left). `; + const captionBottomRight = + images[3].data.caption && + `${removeLastFullStop(images[3].data.caption)} (bottom right). `; + const captionToUse = + caption ?? + `${captionTopLeft ?? ''}${captionTopRight ?? ''}${ + captionBottomLeft ?? '' + }${captionBottomRight ?? ''}`; + return ( +
+ + + + + + + + + + + + + + + {!!captionToUse && ( + - - - {!!caption && ( - - )} -
-); + )} +
+ ); +}; const Slideshow = ({ images, From 48fe16e381d3c1ef824fd244959125be89d385d8 Mon Sep 17 00:00:00 2001 From: Wai Sing Yiu Date: Thu, 5 Feb 2026 11:17:10 +0000 Subject: [PATCH 5/9] Change slideshow to use high resolution --- dotcom-rendering/src/components/MultiImageBlockComponent.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotcom-rendering/src/components/MultiImageBlockComponent.tsx b/dotcom-rendering/src/components/MultiImageBlockComponent.tsx index e8d1e748fd1..055a7b1fffe 100644 --- a/dotcom-rendering/src/components/MultiImageBlockComponent.tsx +++ b/dotcom-rendering/src/components/MultiImageBlockComponent.tsx @@ -354,7 +354,7 @@ const Slideshow = ({ Date: Thu, 5 Feb 2026 11:48:21 +0000 Subject: [PATCH 6/9] no connection reset for demo --- dotcom-rendering/src/lib/thrift/nativeConnection.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotcom-rendering/src/lib/thrift/nativeConnection.ts b/dotcom-rendering/src/lib/thrift/nativeConnection.ts index 3b1ab0659f5..97e112b9508 100644 --- a/dotcom-rendering/src/lib/thrift/nativeConnection.ts +++ b/dotcom-rendering/src/lib/thrift/nativeConnection.ts @@ -124,7 +124,7 @@ export class NativeConnection extends ThriftConnection { resolve: res, reject: rej, timeoutId: setTimeout(function () { - connection.reset(id); + // connection.reset(id); }, ACTION_TIMEOUT_MS), }); const message: NativeMessage = { From aa16b408020a0b4d876d8839295721cb7ae29713 Mon Sep 17 00:00:00 2001 From: Wai Sing Yiu Date: Thu, 5 Feb 2026 12:13:16 +0000 Subject: [PATCH 7/9] Align the set of values for presentation property with Composer --- dotcom-rendering/src/frontend/schemas/feArticle.json | 5 +++-- dotcom-rendering/src/model/block-schema.json | 5 +++-- dotcom-rendering/src/types/content.ts | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/dotcom-rendering/src/frontend/schemas/feArticle.json b/dotcom-rendering/src/frontend/schemas/feArticle.json index eebdb866568..10868ad50cb 100644 --- a/dotcom-rendering/src/frontend/schemas/feArticle.json +++ b/dotcom-rendering/src/frontend/schemas/feArticle.json @@ -2938,8 +2938,9 @@ }, "presentation": { "enum": [ - "grid", - "slideshow" + "side-by-side", + "slideshow", + "stacked" ], "type": "string" } diff --git a/dotcom-rendering/src/model/block-schema.json b/dotcom-rendering/src/model/block-schema.json index a91112f6d48..0dfe9cba990 100644 --- a/dotcom-rendering/src/model/block-schema.json +++ b/dotcom-rendering/src/model/block-schema.json @@ -2426,8 +2426,9 @@ }, "presentation": { "enum": [ - "grid", - "slideshow" + "side-by-side", + "slideshow", + "stacked" ], "type": "string" } diff --git a/dotcom-rendering/src/types/content.ts b/dotcom-rendering/src/types/content.ts index 4587923a89d..5fef2a8a838 100644 --- a/dotcom-rendering/src/types/content.ts +++ b/dotcom-rendering/src/types/content.ts @@ -464,7 +464,7 @@ export interface MultiImageBlockElement { images: ImageBlockElement[]; caption?: string; role?: RoleType; - presentation?: 'slideshow' | 'grid'; + presentation?: 'slideshow' | 'side-by-side' | 'stacked'; } export interface NewsletterSignupBlockElement { From 191e6c1cd6b03c2e8722f719b875c585217ff936 Mon Sep 17 00:00:00 2001 From: Wai Sing Yiu Date: Thu, 5 Feb 2026 12:13:33 +0000 Subject: [PATCH 8/9] Handle caption that is an empty string --- .../src/components/MultiImageBlockComponent.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dotcom-rendering/src/components/MultiImageBlockComponent.tsx b/dotcom-rendering/src/components/MultiImageBlockComponent.tsx index 055a7b1fffe..88de111dcef 100644 --- a/dotcom-rendering/src/components/MultiImageBlockComponent.tsx +++ b/dotcom-rendering/src/components/MultiImageBlockComponent.tsx @@ -13,7 +13,7 @@ type Props = { images: ImageBlockElement[]; format: ArticleFormat; caption?: string; - presentation?: 'slideshow' | 'grid'; + presentation?: 'slideshow' | 'side-by-side' | 'stacked'; }; const ieFallback = css` @@ -123,7 +123,7 @@ const OneImage = ({ format: ArticleFormat; caption?: string; }) => { - const captionToUse = caption ?? removeLastFullStop(images[0].data.caption); + const captionToUse = caption || removeLastFullStop(images[0].data.caption); return (
@@ -209,7 +209,7 @@ const ThreeImage = ({ images[2].data.caption && `${removeLastFullStop(images[2].data.caption)} (bottom right). `; const captionToUse = - caption ?? + caption || `${captionTop ?? ''}${captionBottomLeft ?? ''}${ captionBottomRight ?? '' }`; @@ -279,7 +279,7 @@ const FourImage = ({ images[3].data.caption && `${removeLastFullStop(images[3].data.caption)} (bottom right). `; const captionToUse = - caption ?? + caption || `${captionTopLeft ?? ''}${captionTopRight ?? ''}${ captionBottomLeft ?? '' }${captionBottomRight ?? ''}`; From 91baa3ae0125a71f4f75a64455c65d355ff30d67 Mon Sep 17 00:00:00 2001 From: Wai Sing Yiu Date: Thu, 5 Feb 2026 12:31:22 +0000 Subject: [PATCH 9/9] Add story --- .../MultiImageBlockComponent.stories.tsx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/dotcom-rendering/src/components/MultiImageBlockComponent.stories.tsx b/dotcom-rendering/src/components/MultiImageBlockComponent.stories.tsx index 5c068242595..b3d110d1785 100644 --- a/dotcom-rendering/src/components/MultiImageBlockComponent.stories.tsx +++ b/dotcom-rendering/src/components/MultiImageBlockComponent.stories.tsx @@ -144,3 +144,20 @@ export const GridOfFourWithCaption = () => { ); }; GridOfFourWithCaption.storyName = 'grid of four with caption'; + +export const Slideshow = () => { + return ( +
+ +
+ ); +}; +Slideshow.storyName = 'slideshow';