From 9bbddfe8ff75dbe6151e77c77a580f185d8a3a5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Parram=C3=B3n=20Teixid=C3=B3?= Date: Wed, 15 Apr 2026 11:13:31 +0800 Subject: [PATCH 01/13] [DCUBESDQLR-2356][MPT] add file info bar with fileName and fileSize support - Add fileName and fileSize props to FullscreenCarouselItemProps - Top bar shows file name and size when at least one item has file info, otherwise reverts to floating absolute-positioned action buttons - File name falls back to '-' when only fileSize is provided - Action buttons (magnifier, delete, close) moved into the top bar - Fix ImageGalleryContainer height to use flex:1/min-height:0 to prevent vertical scroll when top bar is present - Add WithFileInfo story, update props-table and MDX docs - Add tests covering all new file info bar behaviour --- .../fullscreen-image-carousel.style.tsx | 97 +++++++++++++--- .../fullscreen-image-carousel.tsx | 109 +++++++++++------- src/fullscreen-image-carousel/types.ts | 4 + .../fullscreen-image-carousel.mdx | 4 + .../fullscreen-image-carousel.stories.tsx | 44 +++++++ .../fullscreen-image-carousel/props-table.tsx | 12 ++ .../fullscreen-image-carousel.spec.tsx | 98 ++++++++++++++++ 7 files changed, 311 insertions(+), 57 deletions(-) diff --git a/src/fullscreen-image-carousel/fullscreen-image-carousel.style.tsx b/src/fullscreen-image-carousel/fullscreen-image-carousel.style.tsx index a3f985847..293bea684 100644 --- a/src/fullscreen-image-carousel/fullscreen-image-carousel.style.tsx +++ b/src/fullscreen-image-carousel/fullscreen-image-carousel.style.tsx @@ -17,6 +17,10 @@ interface ThumbnailItemStyleProps { $active?: boolean; } +interface TopActionButtonsStyleProps extends InsetStyleProps { + $hasFileInfo?: boolean | undefined; +} + // ============================================================================= // STYLING // ============================================================================= @@ -44,23 +48,60 @@ const IconButton = styled(ClickableIcon)` } `; -export const TopActionButtons = styled.div` - position: absolute; - top: ${(props) => - css`calc(${Spacing["spacing-48"]} + ${props.$insetTop || 0}px)`}; - right: ${(props) => - css`calc(${Spacing["spacing-48"]} + ${props.$insetRight || 0}px)`}; - z-index: 5; +export const TopActionButtons = styled.div` display: flex; align-items: center; + justify-content: flex-end; gap: ${Spacing["spacing-16"]}; - ${MediaQuery.MaxWidth.sm} { - top: ${(props) => - css`calc(${Spacing["spacing-20"]} + ${props.$insetTop || 0}px)`}; - right: ${(props) => - css`calc(${Spacing["spacing-20"]} + ${props.$insetRight || 0}px)`}; - } + ${(props) => + props.$hasFileInfo + ? css` + flex-shrink: 0; + background-color: ${Colour["bg-inverse"]}; + padding-top: calc( + ${Spacing["spacing-24"]} + ${props.$insetTop || 0}px + ); + padding-bottom: ${Spacing["spacing-24"]}; + padding-left: calc( + ${Spacing["spacing-32"]} + ${props.$insetLeft || 0}px + ); + padding-right: calc( + ${Spacing["spacing-32"]} + ${props.$insetRight || 0}px + ); + + ${MediaQuery.MaxWidth.sm} { + padding-top: calc( + ${Spacing["spacing-16"]} + ${props.$insetTop || 0}px + ); + padding-bottom: ${Spacing["spacing-16"]}; + padding-left: calc( + ${Spacing["spacing-20"]} + ${props.$insetLeft || 0}px + ); + padding-right: calc( + ${Spacing["spacing-20"]} + ${props.$insetRight || 0}px + ); + } + ` + : css` + position: absolute; + top: calc( + ${Spacing["spacing-48"]} + ${props.$insetTop || 0}px + ); + right: calc( + ${Spacing["spacing-48"]} + ${props.$insetRight || 0}px + ); + z-index: 5; + + ${MediaQuery.MaxWidth.sm} { + top: calc( + ${Spacing["spacing-20"]} + ${props.$insetTop || 0}px + ); + right: calc( + ${Spacing["spacing-20"]} + ${props.$insetRight || 0}px + ); + } + `} `; export const CloseButton = styled(IconButton)``; @@ -114,7 +155,8 @@ export const ImageGalleryContainer = styled.div` display: flex; flex-direction: column; width: 100%; - height: 100%; + flex: 1; + min-height: 0; `; export const ImageGalleryWrapper = styled.div` @@ -312,3 +354,30 @@ export const ThumbnailImage = styled(StatefulImage)` height: 100%; width: 100%; `; + +// ----------------------------------------------------------------------------- +// FILE INFO BAR STYLING +// ----------------------------------------------------------------------------- + +export const FileInfoTextWrapper = styled.div` + flex: 1; + display: flex; + flex-direction: column; + gap: ${Spacing["spacing-8"]}; + overflow: hidden; + min-width: 0; + min-height: calc(26px + ${Spacing["spacing-8"]} + 24px); +`; + +export const FileInfoFileName = styled(Typography.BodyBL)` + color: ${Colour["text-inverse"]}; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + min-width: 0; +`; + +export const FileInfoFileSize = styled(Typography.BodyMD)` + color: ${Colour["text-inverse"]}; + letter-spacing: 0.14px; +`; diff --git a/src/fullscreen-image-carousel/fullscreen-image-carousel.tsx b/src/fullscreen-image-carousel/fullscreen-image-carousel.tsx index e721400c0..73c57bd8f 100644 --- a/src/fullscreen-image-carousel/fullscreen-image-carousel.tsx +++ b/src/fullscreen-image-carousel/fullscreen-image-carousel.tsx @@ -31,6 +31,9 @@ import { Chip, CloseButton, DeleteButton, + FileInfoFileName, + FileInfoFileSize, + FileInfoTextWrapper, FocusableImageRegion, ImageGalleryContainer, ImageGallerySlide, @@ -98,6 +101,7 @@ export const Component = ( (item) => isCustomItem(item) && !!item.itemLabel?.trim() ); const carouselItemNoun = hasAnyItemLabel ? "item" : "image"; + const hasFileInfo = items.some((item) => item.fileName || item.fileSize); const getItemAriaLabel = useCallback( (index: number) => { @@ -348,6 +352,22 @@ export const Component = ( ); }; + const renderFileInfo = () => { + const { fileName, fileSize } = currentItem ?? {}; + const displayName = fileName || (fileSize ? "-" : undefined); + + return ( + + {displayName && ( + + {displayName} + + )} + {fileSize && {fileSize}} + + ); + }; + const renderThumbnails = () => { return ( + + {hasFileInfo && renderFileInfo()} + {!hideMagnifier && !isCustomItem(currentItem) && ( + + {zoom === 1 ? ( + + ) : ( + + )} + + )} + + {onDelete && ( + + + + )} + + + + + - - - {!hideMagnifier && !isCustomItem(currentItem) && ( - - {zoom === 1 ? ( - - ) : ( - - )} - - )} - - {onDelete && ( - - - - )} - - - - - ); diff --git a/src/fullscreen-image-carousel/types.ts b/src/fullscreen-image-carousel/types.ts index 3cb19d334..1cbb5e9eb 100644 --- a/src/fullscreen-image-carousel/types.ts +++ b/src/fullscreen-image-carousel/types.ts @@ -33,6 +33,8 @@ export interface FullscreenImageCarouselImageItemProps { alt?: string | undefined; thumbnailSrc?: string | undefined; renderContent?: never; + fileName?: string | undefined; + fileSize?: string | undefined; } /** @deprecated Use FullscreenImageCarouselImageItemProps instead */ @@ -46,6 +48,8 @@ export interface FullscreenImageCarouselCustomItemProps { itemLabel?: string | undefined; /** Render prop for the full slide area. Consumer is responsible for the entire slide content (e.g. an iframe, embed, or custom viewer). */ renderContent: () => React.ReactNode; + fileName?: string | undefined; + fileSize?: string | undefined; } export type FullscreenImageCarouselItemProps = diff --git a/stories/fullscreen-image-carousel/fullscreen-image-carousel.mdx b/stories/fullscreen-image-carousel/fullscreen-image-carousel.mdx index 723cd4fb6..80f642e4b 100644 --- a/stories/fullscreen-image-carousel/fullscreen-image-carousel.mdx +++ b/stories/fullscreen-image-carousel/fullscreen-image-carousel.mdx @@ -38,6 +38,10 @@ Slides can render arbitrary content via `renderContent`. If the content is not a +## With file info + + + ## Component API diff --git a/stories/fullscreen-image-carousel/fullscreen-image-carousel.stories.tsx b/stories/fullscreen-image-carousel/fullscreen-image-carousel.stories.tsx index 014d8db83..fee33e59e 100644 --- a/stories/fullscreen-image-carousel/fullscreen-image-carousel.stories.tsx +++ b/stories/fullscreen-image-carousel/fullscreen-image-carousel.stories.tsx @@ -195,3 +195,47 @@ export const WithCustomContent: StoryObj = { ); }, }; + +export const WithFileInfo: StoryObj = { + render: (_args) => { + const [show, setShow] = useState(false); + return ( + <> + { + setShow((old) => !old); + }} + > + Show carousel + + setShow(false)} + /> + + ); + }, +}; diff --git a/stories/fullscreen-image-carousel/props-table.tsx b/stories/fullscreen-image-carousel/props-table.tsx index f51824caa..da939619d 100644 --- a/stories/fullscreen-image-carousel/props-table.tsx +++ b/stories/fullscreen-image-carousel/props-table.tsx @@ -158,6 +158,18 @@ const DATA: ApiTableSectionProps[] = [ ), propTypes: ["string"], }, + { + name: "fileName", + description: + "The file name to display in the file info bar at the top", + propTypes: ["string"], + }, + { + name: "fileSize", + description: + 'The pre-formatted file size string to display in the file info bar at the top (e.g. "2.4 MB")', + propTypes: ["string"], + }, ], }, { diff --git a/tests/fullscreen-image-carousel/fullscreen-image-carousel.spec.tsx b/tests/fullscreen-image-carousel/fullscreen-image-carousel.spec.tsx index 5411abdb0..e9e8e09e3 100644 --- a/tests/fullscreen-image-carousel/fullscreen-image-carousel.spec.tsx +++ b/tests/fullscreen-image-carousel/fullscreen-image-carousel.spec.tsx @@ -254,6 +254,86 @@ describe("Fullscreen Image Carousel", () => { expect(screen.getByLabelText("Delete image")).toBeInTheDocument(); }); }); + + describe("File info bar", () => { + it("should render fileName and fileSize when provided on the current item", () => { + render( + + ); + + expect(screen.getByText("photo-a.jpg")).toBeInTheDocument(); + expect(screen.getByText("1.2 MB")).toBeInTheDocument(); + }); + + it("should not render the file info bar when no item has fileName or fileSize", () => { + render(); + + expect(screen.queryByText(/\.jpg|MB|KB/)).not.toBeInTheDocument(); + }); + + it("should not render file info text for a slide that has no fileName or fileSize", () => { + render( + + ); + + // Slide 3 has no file info; bar is present but no text + expect(screen.queryByText("photo-a.jpg")).not.toBeInTheDocument(); + expect(screen.queryByText("photo-b.jpg")).not.toBeInTheDocument(); + }); + + it("should update the file info bar when navigating to a different slide", () => { + render( + + ); + + expect(screen.getByText("photo-a.jpg")).toBeInTheDocument(); + + fireEvent.click(screen.getByTestId("forward-btn")); + + expect(screen.queryByText("photo-a.jpg")).not.toBeInTheDocument(); + expect(screen.getByText("photo-b.jpg")).toBeInTheDocument(); + expect(screen.getByText("840 KB")).toBeInTheDocument(); + }); + + it("should display '-' as the file name when only fileSize is provided", () => { + render( + + ); + + expect(screen.getByText("-")).toBeInTheDocument(); + expect(screen.getByText("500 KB")).toBeInTheDocument(); + }); + + it("should render fileName only when fileSize is not provided", () => { + render( + + ); + + expect(screen.getByText("only-name.jpg")).toBeInTheDocument(); + }); + }); }); // ============================================================================= @@ -295,3 +375,21 @@ const IMAGES_WITHOUT_THUMBNAIL = [ thumbnailSrc: "https://picsum.photos/id/163/100/100", }, ]; + +const IMAGES_WITH_FILE_INFO = [ + { + src: "https://picsum.photos/id/157/1600/900", + fileName: "photo-a.jpg", + fileSize: "1.2 MB", + }, + { + src: "https://picsum.photos/id/163/900/300", + fileName: "photo-b.jpg", + fileSize: "840 KB", + }, + { + src: "https://picsum.photos/id/369/1000/1000", + fileSize: "500 KB", + }, + { src: "https://picsum.photos/id/445/300/300" }, +]; From b7d4e043cd1e37a4784f676e2a16d4db26b792ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Parram=C3=B3n=20Teixid=C3=B3?= Date: Wed, 15 Apr 2026 13:50:49 +0800 Subject: [PATCH 02/13] [DCUBESDQLR-2356][MPT] truncate fileSize with ellipsis; add long-name story item --- .../fullscreen-image-carousel.style.tsx | 4 ++++ .../fullscreen-image-carousel.stories.tsx | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/src/fullscreen-image-carousel/fullscreen-image-carousel.style.tsx b/src/fullscreen-image-carousel/fullscreen-image-carousel.style.tsx index 293bea684..9a047167e 100644 --- a/src/fullscreen-image-carousel/fullscreen-image-carousel.style.tsx +++ b/src/fullscreen-image-carousel/fullscreen-image-carousel.style.tsx @@ -380,4 +380,8 @@ export const FileInfoFileName = styled(Typography.BodyBL)` export const FileInfoFileSize = styled(Typography.BodyMD)` color: ${Colour["text-inverse"]}; letter-spacing: 0.14px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + min-width: 0; `; diff --git a/stories/fullscreen-image-carousel/fullscreen-image-carousel.stories.tsx b/stories/fullscreen-image-carousel/fullscreen-image-carousel.stories.tsx index fee33e59e..9881f433b 100644 --- a/stories/fullscreen-image-carousel/fullscreen-image-carousel.stories.tsx +++ b/stories/fullscreen-image-carousel/fullscreen-image-carousel.stories.tsx @@ -220,6 +220,13 @@ export const WithFileInfo: StoryObj = { fileName: "panoramic-view.jpg", fileSize: "840 KB", }, + { + src: "https://picsum.photos/id/10/1600/900", + fileName: + "this-is-a-very-long-file-name-that-should-be-truncated-when-it-exceeds-the-available-width-in-the-bar.jpg", + fileSize: + "234,567,890.12 MB (123,456,789.99 MB compressed)", + }, { src: "https://picsum.photos/id/369/1000/1000", fileName: "square-image.jpg", From 89ceceb2c6e72faf8a01a6645cc95a08e699bc7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Parram=C3=B3n=20Teixid=C3=B3?= Date: Wed, 15 Apr 2026 14:05:51 +0800 Subject: [PATCH 03/13] [DCUBESDQLR-2356][MPT] remove redundant story item from WithFileInfo --- .../fullscreen-image-carousel.stories.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/stories/fullscreen-image-carousel/fullscreen-image-carousel.stories.tsx b/stories/fullscreen-image-carousel/fullscreen-image-carousel.stories.tsx index 9881f433b..ac9f8979a 100644 --- a/stories/fullscreen-image-carousel/fullscreen-image-carousel.stories.tsx +++ b/stories/fullscreen-image-carousel/fullscreen-image-carousel.stories.tsx @@ -215,11 +215,6 @@ export const WithFileInfo: StoryObj = { fileName: "landscape-photo.jpg", fileSize: "1.2 MB", }, - { - src: "https://picsum.photos/id/163/900/300", - fileName: "panoramic-view.jpg", - fileSize: "840 KB", - }, { src: "https://picsum.photos/id/10/1600/900", fileName: From 3a4ad994a4bb87508d3f58de27e712b12118afbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Parram=C3=B3n=20Teixid=C3=B3?= Date: Wed, 15 Apr 2026 14:15:19 +0800 Subject: [PATCH 04/13] [DCUBESDQLR-2356][MPT] address Copilot review comments - trim fileName before using in hasFileInfo and displayName - move aria-live/aria-atomic from TopActionButtons to FileInfoTextWrapper - rename misleading test name to match actual assertion --- .../fullscreen-image-carousel.tsx | 10 ++++++---- .../fullscreen-image-carousel.spec.tsx | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/fullscreen-image-carousel/fullscreen-image-carousel.tsx b/src/fullscreen-image-carousel/fullscreen-image-carousel.tsx index 73c57bd8f..ffd2fa58c 100644 --- a/src/fullscreen-image-carousel/fullscreen-image-carousel.tsx +++ b/src/fullscreen-image-carousel/fullscreen-image-carousel.tsx @@ -101,7 +101,9 @@ export const Component = ( (item) => isCustomItem(item) && !!item.itemLabel?.trim() ); const carouselItemNoun = hasAnyItemLabel ? "item" : "image"; - const hasFileInfo = items.some((item) => item.fileName || item.fileSize); + const hasFileInfo = items.some( + (item) => item.fileName?.trim() || item.fileSize + ); const getItemAriaLabel = useCallback( (index: number) => { @@ -354,10 +356,11 @@ export const Component = ( const renderFileInfo = () => { const { fileName, fileSize } = currentItem ?? {}; - const displayName = fileName || (fileSize ? "-" : undefined); + const trimmedName = fileName?.trim(); + const displayName = trimmedName || (fileSize ? "-" : undefined); return ( - + {displayName && ( {displayName} @@ -417,7 +420,6 @@ export const Component = ( > { expect(screen.getByText("1.2 MB")).toBeInTheDocument(); }); - it("should not render the file info bar when no item has fileName or fileSize", () => { + it("should not render file info text when no item has fileName or fileSize", () => { render(); expect(screen.queryByText(/\.jpg|MB|KB/)).not.toBeInTheDocument(); From 8d0ac8e49565e94fe112ef8f66cb37b02408e2f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Parram=C3=B3n=20Teixid=C3=B3?= Date: Wed, 15 Apr 2026 14:29:29 +0800 Subject: [PATCH 05/13] [DCUBESDQLR-2356][MPT] trim fileSize before rendering file info --- .../fullscreen-image-carousel.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/fullscreen-image-carousel/fullscreen-image-carousel.tsx b/src/fullscreen-image-carousel/fullscreen-image-carousel.tsx index ffd2fa58c..6876f1626 100644 --- a/src/fullscreen-image-carousel/fullscreen-image-carousel.tsx +++ b/src/fullscreen-image-carousel/fullscreen-image-carousel.tsx @@ -102,7 +102,7 @@ export const Component = ( ); const carouselItemNoun = hasAnyItemLabel ? "item" : "image"; const hasFileInfo = items.some( - (item) => item.fileName?.trim() || item.fileSize + (item) => item.fileName?.trim() || item.fileSize?.trim() ); const getItemAriaLabel = useCallback( @@ -357,7 +357,8 @@ export const Component = ( const renderFileInfo = () => { const { fileName, fileSize } = currentItem ?? {}; const trimmedName = fileName?.trim(); - const displayName = trimmedName || (fileSize ? "-" : undefined); + const trimmedSize = fileSize?.trim(); + const displayName = trimmedName || (trimmedSize ? "-" : undefined); return ( @@ -366,7 +367,9 @@ export const Component = ( {displayName} )} - {fileSize && {fileSize}} + {trimmedSize && ( + {trimmedSize} + )} ); }; From f81934d6a2bad9eaa190ca9dfbf121020057135a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Parram=C3=B3n=20Teixid=C3=B3?= Date: Wed, 15 Apr 2026 14:29:56 +0800 Subject: [PATCH 06/13] [DCUBESDQLR-2356][MPT] use font tokens for file info min-height --- .../fullscreen-image-carousel.style.tsx | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/fullscreen-image-carousel/fullscreen-image-carousel.style.tsx b/src/fullscreen-image-carousel/fullscreen-image-carousel.style.tsx index 9a047167e..b1b7e19a5 100644 --- a/src/fullscreen-image-carousel/fullscreen-image-carousel.style.tsx +++ b/src/fullscreen-image-carousel/fullscreen-image-carousel.style.tsx @@ -2,7 +2,15 @@ import styled, { css } from "styled-components"; import { ClickableIcon } from "../shared/clickable-icon"; import { ImagePlaceholder } from "../shared/image-placeholder"; import { InsetStyleProps } from "../shared/types"; -import { Border, Colour, MediaQuery, Radius, Shadow, Spacing } from "../theme"; +import { + Border, + Colour, + Font, + MediaQuery, + Radius, + Shadow, + Spacing, +} from "../theme"; import { Typography } from "../typography"; import { StatefulImage } from "./stateful-image"; @@ -366,7 +374,10 @@ export const FileInfoTextWrapper = styled.div` gap: ${Spacing["spacing-8"]}; overflow: hidden; min-width: 0; - min-height: calc(26px + ${Spacing["spacing-8"]} + 24px); + min-height: calc( + ${Font.Spec["body-lh-baseline"]} + ${Spacing["spacing-8"]} + + ${Font.Spec["body-lh-md"]} + ); `; export const FileInfoFileName = styled(Typography.BodyBL)` From ab0f06917378a65700e2024f7a1aefccebf46d00 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Apr 2026 08:17:51 +0000 Subject: [PATCH 07/13] [DCUBESDQLR-2356][MPT] memoize hasFileInfo with useMemo to avoid repeated O(n) computation on every render --- .../fullscreen-image-carousel.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/fullscreen-image-carousel/fullscreen-image-carousel.tsx b/src/fullscreen-image-carousel/fullscreen-image-carousel.tsx index 6876f1626..6fc52f02c 100644 --- a/src/fullscreen-image-carousel/fullscreen-image-carousel.tsx +++ b/src/fullscreen-image-carousel/fullscreen-image-carousel.tsx @@ -12,6 +12,7 @@ import { useCallback, useEffect, useImperativeHandle, + useMemo, useRef, useState, } from "react"; @@ -101,8 +102,12 @@ export const Component = ( (item) => isCustomItem(item) && !!item.itemLabel?.trim() ); const carouselItemNoun = hasAnyItemLabel ? "item" : "image"; - const hasFileInfo = items.some( - (item) => item.fileName?.trim() || item.fileSize?.trim() + const hasFileInfo = useMemo( + () => + items.some( + (item) => item.fileName?.trim() || item.fileSize?.trim() + ), + [items] ); const getItemAriaLabel = useCallback( From c9d305815a5117a82f2ce4209da54416b4d3ce16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Parram=C3=B3n=20Teixid=C3=B3?= Date: Thu, 16 Apr 2026 15:08:46 +0800 Subject: [PATCH 08/13] [DCUBESDQLR-2356][MPT] Address qroll PR comments --- .../fullscreen-image-carousel.style.tsx | 2 +- .../fullscreen-image-carousel.tsx | 110 ++++++++++-------- .../fullscreen-image-carousel.spec.tsx | 57 ++++++--- 3 files changed, 101 insertions(+), 68 deletions(-) diff --git a/src/fullscreen-image-carousel/fullscreen-image-carousel.style.tsx b/src/fullscreen-image-carousel/fullscreen-image-carousel.style.tsx index b1b7e19a5..1a6835faa 100644 --- a/src/fullscreen-image-carousel/fullscreen-image-carousel.style.tsx +++ b/src/fullscreen-image-carousel/fullscreen-image-carousel.style.tsx @@ -57,6 +57,7 @@ const IconButton = styled(ClickableIcon)` `; export const TopActionButtons = styled.div` + order: -1; display: flex; align-items: center; justify-content: flex-end; @@ -390,7 +391,6 @@ export const FileInfoFileName = styled(Typography.BodyBL)` export const FileInfoFileSize = styled(Typography.BodyMD)` color: ${Colour["text-inverse"]}; - letter-spacing: 0.14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; diff --git a/src/fullscreen-image-carousel/fullscreen-image-carousel.tsx b/src/fullscreen-image-carousel/fullscreen-image-carousel.tsx index 6fc52f02c..5712f251f 100644 --- a/src/fullscreen-image-carousel/fullscreen-image-carousel.tsx +++ b/src/fullscreen-image-carousel/fullscreen-image-carousel.tsx @@ -363,17 +363,25 @@ export const Component = ( const { fileName, fileSize } = currentItem ?? {}; const trimmedName = fileName?.trim(); const trimmedSize = fileSize?.trim(); - const displayName = trimmedName || (trimmedSize ? "-" : undefined); return ( - - {displayName && ( - - {displayName} + + {trimmedName && ( + + {trimmedName} )} {trimmedSize && ( - {trimmedSize} + + {trimmedSize} + )} ); @@ -427,51 +435,6 @@ export const Component = ( disableInitialFocus > - - {hasFileInfo && renderFileInfo()} - {!hideMagnifier && !isCustomItem(currentItem) && ( - - {zoom === 1 ? ( - - ) : ( - - )} - - )} - - {onDelete && ( - - - - )} - - - - - + + {hasFileInfo && renderFileInfo()} + {!hideMagnifier && !isCustomItem(currentItem) && ( + + {zoom === 1 ? ( + + ) : ( + + )} + + )} + + {onDelete && ( + + + + )} + + + + + ); diff --git a/tests/fullscreen-image-carousel/fullscreen-image-carousel.spec.tsx b/tests/fullscreen-image-carousel/fullscreen-image-carousel.spec.tsx index f61c465c4..0480d7f04 100644 --- a/tests/fullscreen-image-carousel/fullscreen-image-carousel.spec.tsx +++ b/tests/fullscreen-image-carousel/fullscreen-image-carousel.spec.tsx @@ -264,17 +264,24 @@ describe("Fullscreen Image Carousel", () => { /> ); - expect(screen.getByText("photo-a.jpg")).toBeInTheDocument(); - expect(screen.getByText("1.2 MB")).toBeInTheDocument(); + expect(screen.getByTestId("file-info-bar")).toBeInTheDocument(); + expect(screen.getByTestId("file-info-name")).toHaveTextContent( + "photo-a.jpg" + ); + expect(screen.getByTestId("file-info-size")).toHaveTextContent( + "1.2 MB" + ); }); - it("should not render file info text when no item has fileName or fileSize", () => { + it("should not render file info bar when no item has fileName or fileSize", () => { render(); - expect(screen.queryByText(/\.jpg|MB|KB/)).not.toBeInTheDocument(); + expect( + screen.queryByTestId("file-info-bar") + ).not.toBeInTheDocument(); }); - it("should not render file info text for a slide that has no fileName or fileSize", () => { + it("should render file info bar but no text for a slide that has no fileName or fileSize", () => { render( { /> ); - // Slide 3 has no file info; bar is present but no text - expect(screen.queryByText("photo-a.jpg")).not.toBeInTheDocument(); - expect(screen.queryByText("photo-b.jpg")).not.toBeInTheDocument(); + expect(screen.getByTestId("file-info-bar")).toBeInTheDocument(); + expect( + screen.queryByTestId("file-info-name") + ).not.toBeInTheDocument(); + expect( + screen.queryByTestId("file-info-size") + ).not.toBeInTheDocument(); }); it("should update the file info bar when navigating to a different slide", () => { @@ -296,16 +307,21 @@ describe("Fullscreen Image Carousel", () => { /> ); - expect(screen.getByText("photo-a.jpg")).toBeInTheDocument(); + expect(screen.getByTestId("file-info-name")).toHaveTextContent( + "photo-a.jpg" + ); fireEvent.click(screen.getByTestId("forward-btn")); - expect(screen.queryByText("photo-a.jpg")).not.toBeInTheDocument(); - expect(screen.getByText("photo-b.jpg")).toBeInTheDocument(); - expect(screen.getByText("840 KB")).toBeInTheDocument(); + expect(screen.getByTestId("file-info-name")).toHaveTextContent( + "photo-b.jpg" + ); + expect(screen.getByTestId("file-info-size")).toHaveTextContent( + "840 KB" + ); }); - it("should display '-' as the file name when only fileSize is provided", () => { + it("should not render fileName when only fileSize is provided", () => { render( { /> ); - expect(screen.getByText("-")).toBeInTheDocument(); - expect(screen.getByText("500 KB")).toBeInTheDocument(); + expect( + screen.queryByTestId("file-info-name") + ).not.toBeInTheDocument(); + expect(screen.getByTestId("file-info-size")).toHaveTextContent( + "500 KB" + ); }); it("should render fileName only when fileSize is not provided", () => { @@ -331,7 +351,12 @@ describe("Fullscreen Image Carousel", () => { /> ); - expect(screen.getByText("only-name.jpg")).toBeInTheDocument(); + expect(screen.getByTestId("file-info-name")).toHaveTextContent( + "only-name.jpg" + ); + expect( + screen.queryByTestId("file-info-size") + ).not.toBeInTheDocument(); }); }); }); From b56d04caad502d43c2afc0c0f341cdf5e8600c87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Parram=C3=B3n=20Teixid=C3=B3?= Date: Thu, 16 Apr 2026 16:15:40 +0800 Subject: [PATCH 09/13] [DCUBESDQLR-2356][MPT] enforce fileName/fileSize type constraints and center filename vertically - disallow fileSize without fileName via discriminated union - enforce all-or-none fileName across items via union of arrays - vertically center filename in file info bar when fileSize is absent - update stories and tests for new type constraints --- .../fullscreen-image-carousel.style.tsx | 11 ++++++- .../fullscreen-image-carousel.tsx | 1 + src/fullscreen-image-carousel/types.ts | 30 +++++++++++++----- .../doc-elements.tsx | 4 +-- .../fullscreen-image-carousel.stories.tsx | 2 ++ .../fullscreen-image-carousel.spec.tsx | 31 ++++++------------- 6 files changed, 46 insertions(+), 33 deletions(-) diff --git a/src/fullscreen-image-carousel/fullscreen-image-carousel.style.tsx b/src/fullscreen-image-carousel/fullscreen-image-carousel.style.tsx index 1a6835faa..fc0dfbc72 100644 --- a/src/fullscreen-image-carousel/fullscreen-image-carousel.style.tsx +++ b/src/fullscreen-image-carousel/fullscreen-image-carousel.style.tsx @@ -29,6 +29,10 @@ interface TopActionButtonsStyleProps extends InsetStyleProps { $hasFileInfo?: boolean | undefined; } +interface FileInfoTextWrapperStyleProps { + $centerContent?: boolean | undefined; +} + // ============================================================================= // STYLING // ============================================================================= @@ -368,7 +372,7 @@ export const ThumbnailImage = styled(StatefulImage)` // FILE INFO BAR STYLING // ----------------------------------------------------------------------------- -export const FileInfoTextWrapper = styled.div` +export const FileInfoTextWrapper = styled.div` flex: 1; display: flex; flex-direction: column; @@ -379,6 +383,11 @@ export const FileInfoTextWrapper = styled.div` ${Font.Spec["body-lh-baseline"]} + ${Spacing["spacing-8"]} + ${Font.Spec["body-lh-md"]} ); + ${(props) => + props.$centerContent && + css` + justify-content: center; + `} `; export const FileInfoFileName = styled(Typography.BodyBL)` diff --git a/src/fullscreen-image-carousel/fullscreen-image-carousel.tsx b/src/fullscreen-image-carousel/fullscreen-image-carousel.tsx index 5712f251f..c4e12a41a 100644 --- a/src/fullscreen-image-carousel/fullscreen-image-carousel.tsx +++ b/src/fullscreen-image-carousel/fullscreen-image-carousel.tsx @@ -366,6 +366,7 @@ export const Component = ( return ( void; } -export interface FullscreenImageCarouselProps +export interface FullscreenImageCarouselBaseProps extends Pick< ModalProps, "show" | "rootComponentId" | "animationFrom" | "zIndex" > { - items: FullscreenImageCarouselItemProps[]; /** The index of the visible item, starts from 0 */ initialActiveItemIndex?: number | undefined; hideThumbnail?: boolean | undefined; @@ -27,14 +26,22 @@ export interface FullscreenImageCarouselProps insets?: Insets | undefined; } +type FullscreenCarouselItemBase = + | FullscreenImageCarouselImageItemProps + | FullscreenImageCarouselCustomItemProps; + +export type FullscreenImageCarouselProps = FullscreenImageCarouselBaseProps & { + items: + | (FullscreenCarouselItemBase & FullscreenCarouselItemWithFileName)[] + | (FullscreenCarouselItemBase & FullscreenCarouselItemWithoutFileName)[]; +}; + export interface FullscreenImageCarouselImageItemProps { type?: "image" | undefined; src: string; alt?: string | undefined; thumbnailSrc?: string | undefined; renderContent?: never; - fileName?: string | undefined; - fileSize?: string | undefined; } /** @deprecated Use FullscreenImageCarouselImageItemProps instead */ @@ -48,13 +55,20 @@ export interface FullscreenImageCarouselCustomItemProps { itemLabel?: string | undefined; /** Render prop for the full slide area. Consumer is responsible for the entire slide content (e.g. an iframe, embed, or custom viewer). */ renderContent: () => React.ReactNode; - fileName?: string | undefined; +} + +export interface FullscreenCarouselItemWithFileName { + fileName: string; fileSize?: string | undefined; } -export type FullscreenImageCarouselItemProps = - | FullscreenImageCarouselImageItemProps - | FullscreenImageCarouselCustomItemProps; +export interface FullscreenCarouselItemWithoutFileName { + fileName?: undefined; + fileSize?: undefined; +} + +export type FullscreenImageCarouselItemProps = FullscreenCarouselItemBase & + (FullscreenCarouselItemWithFileName | FullscreenCarouselItemWithoutFileName); export interface ImageDimension { width: number; diff --git a/stories/fullscreen-image-carousel/doc-elements.tsx b/stories/fullscreen-image-carousel/doc-elements.tsx index ef21ed440..44af9d215 100644 --- a/stories/fullscreen-image-carousel/doc-elements.tsx +++ b/stories/fullscreen-image-carousel/doc-elements.tsx @@ -1,4 +1,4 @@ -import { FullscreenCarouselItemProps } from "src/fullscreen-image-carousel"; +import { FullscreenImageCarouselImageItemProps } from "src/fullscreen-image-carousel"; const RESOLUTIONS = [ [1600, 900], @@ -11,7 +11,7 @@ const RESOLUTIONS = [ ]; export const getImages = (size: number) => { - const images: FullscreenCarouselItemProps[] = []; + const images: FullscreenImageCarouselImageItemProps[] = []; for (let i = 0; i < size; i++) { const [width, height] = RESOLUTIONS[i % RESOLUTIONS.length]; images.push({ diff --git a/stories/fullscreen-image-carousel/fullscreen-image-carousel.stories.tsx b/stories/fullscreen-image-carousel/fullscreen-image-carousel.stories.tsx index ac9f8979a..e23ec6b18 100644 --- a/stories/fullscreen-image-carousel/fullscreen-image-carousel.stories.tsx +++ b/stories/fullscreen-image-carousel/fullscreen-image-carousel.stories.tsx @@ -228,10 +228,12 @@ export const WithFileInfo: StoryObj = { }, { src: "https://picsum.photos/id/445/300/300", + fileName: "small-photo.jpg", fileSize: "320 KB", }, { src: "https://picsum.photos/id/237/800/600", + fileName: "dog-photo.jpg", }, ]} show={show} diff --git a/tests/fullscreen-image-carousel/fullscreen-image-carousel.spec.tsx b/tests/fullscreen-image-carousel/fullscreen-image-carousel.spec.tsx index 0480d7f04..07e1de574 100644 --- a/tests/fullscreen-image-carousel/fullscreen-image-carousel.spec.tsx +++ b/tests/fullscreen-image-carousel/fullscreen-image-carousel.spec.tsx @@ -281,7 +281,7 @@ describe("Fullscreen Image Carousel", () => { ).not.toBeInTheDocument(); }); - it("should render file info bar but no text for a slide that has no fileName or fileSize", () => { + it("should render fileName without fileSize for a slide that has no fileSize", () => { render( { ); expect(screen.getByTestId("file-info-bar")).toBeInTheDocument(); - expect( - screen.queryByTestId("file-info-name") - ).not.toBeInTheDocument(); + expect(screen.getByTestId("file-info-name")).toHaveTextContent( + "photo-d.jpg" + ); expect( screen.queryByTestId("file-info-size") ).not.toBeInTheDocument(); @@ -321,23 +321,6 @@ describe("Fullscreen Image Carousel", () => { ); }); - it("should not render fileName when only fileSize is provided", () => { - render( - - ); - - expect( - screen.queryByTestId("file-info-name") - ).not.toBeInTheDocument(); - expect(screen.getByTestId("file-info-size")).toHaveTextContent( - "500 KB" - ); - }); - it("should render fileName only when fileSize is not provided", () => { render( Date: Fri, 17 Apr 2026 13:28:50 +0800 Subject: [PATCH 10/13] [DCUBESDQLR-2356][MPT] change order of stories to make more sense semantically --- .../fullscreen-image-carousel.mdx | 8 +- .../fullscreen-image-carousel.stories.tsx | 96 +++++++++---------- 2 files changed, 52 insertions(+), 52 deletions(-) diff --git a/stories/fullscreen-image-carousel/fullscreen-image-carousel.mdx b/stories/fullscreen-image-carousel/fullscreen-image-carousel.mdx index 80f642e4b..2730654a0 100644 --- a/stories/fullscreen-image-carousel/fullscreen-image-carousel.mdx +++ b/stories/fullscreen-image-carousel/fullscreen-image-carousel.mdx @@ -32,16 +32,16 @@ import { FullscreenImageCarousel } from "@lifesg/react-design-system/fullscreen- +## With file info + + + ## With custom content Slides can render arbitrary content via `renderContent`. If the content is not an image, you are recommended to set `itemLabel` so that the type of content can be described accurately to screen readers. -## With file info - - - ## Component API diff --git a/stories/fullscreen-image-carousel/fullscreen-image-carousel.stories.tsx b/stories/fullscreen-image-carousel/fullscreen-image-carousel.stories.tsx index e23ec6b18..7f00623a6 100644 --- a/stories/fullscreen-image-carousel/fullscreen-image-carousel.stories.tsx +++ b/stories/fullscreen-image-carousel/fullscreen-image-carousel.stories.tsx @@ -136,6 +136,54 @@ export const Configurable: StoryObj = { }, }; +export const WithFileInfo: StoryObj = { + render: (_args) => { + const [show, setShow] = useState(false); + return ( + <> + { + setShow((old) => !old); + }} + > + Show carousel + + setShow(false)} + /> + + ); + }, +}; + export const WithCustomContent: StoryObj = { render: (_args) => { const [show, setShow] = useState(false); @@ -195,51 +243,3 @@ export const WithCustomContent: StoryObj = { ); }, }; - -export const WithFileInfo: StoryObj = { - render: (_args) => { - const [show, setShow] = useState(false); - return ( - <> - { - setShow((old) => !old); - }} - > - Show carousel - - setShow(false)} - /> - - ); - }, -}; From 9f20cc3fd67741e50642f5d580d2adecf99fd045 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Parram=C3=B3n=20Teixid=C3=B3?= Date: Fri, 17 Apr 2026 13:29:27 +0800 Subject: [PATCH 11/13] [DCUBESDQLR-2356][MPT] remove unneeded test --- .../fullscreen-image-carousel.spec.tsx | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/tests/fullscreen-image-carousel/fullscreen-image-carousel.spec.tsx b/tests/fullscreen-image-carousel/fullscreen-image-carousel.spec.tsx index 07e1de574..ba120373d 100644 --- a/tests/fullscreen-image-carousel/fullscreen-image-carousel.spec.tsx +++ b/tests/fullscreen-image-carousel/fullscreen-image-carousel.spec.tsx @@ -320,27 +320,6 @@ describe("Fullscreen Image Carousel", () => { "840 KB" ); }); - - it("should render fileName only when fileSize is not provided", () => { - render( - - ); - - expect(screen.getByTestId("file-info-name")).toHaveTextContent( - "only-name.jpg" - ); - expect( - screen.queryByTestId("file-info-size") - ).not.toBeInTheDocument(); - }); }); }); From 2dfece07b78278c402a39aeb1bfb136e8613b702 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Parram=C3=B3n=20Teixid=C3=B3?= Date: Fri, 17 Apr 2026 16:51:06 +0800 Subject: [PATCH 12/13] [DCUBESDQLR-2356][MPT] allow for a carousel having items with and without filename --- src/fullscreen-image-carousel/types.ts | 30 +++++-------------- .../fullscreen-image-carousel.stories.tsx | 10 ------- .../fullscreen-image-carousel/props-table.tsx | 24 +++++++-------- .../fullscreen-image-carousel.spec.tsx | 28 +++++++++++++---- 4 files changed, 42 insertions(+), 50 deletions(-) diff --git a/src/fullscreen-image-carousel/types.ts b/src/fullscreen-image-carousel/types.ts index b01a5a040..dfeb9af94 100644 --- a/src/fullscreen-image-carousel/types.ts +++ b/src/fullscreen-image-carousel/types.ts @@ -8,11 +8,12 @@ export interface FullscreenImageCarouselRef { goToNextItem: () => void; } -export interface FullscreenImageCarouselBaseProps +export interface FullscreenImageCarouselProps extends Pick< ModalProps, "show" | "rootComponentId" | "animationFrom" | "zIndex" > { + items: FullscreenImageCarouselItemProps[]; /** The index of the visible item, starts from 0 */ initialActiveItemIndex?: number | undefined; hideThumbnail?: boolean | undefined; @@ -26,16 +27,6 @@ export interface FullscreenImageCarouselBaseProps insets?: Insets | undefined; } -type FullscreenCarouselItemBase = - | FullscreenImageCarouselImageItemProps - | FullscreenImageCarouselCustomItemProps; - -export type FullscreenImageCarouselProps = FullscreenImageCarouselBaseProps & { - items: - | (FullscreenCarouselItemBase & FullscreenCarouselItemWithFileName)[] - | (FullscreenCarouselItemBase & FullscreenCarouselItemWithoutFileName)[]; -}; - export interface FullscreenImageCarouselImageItemProps { type?: "image" | undefined; src: string; @@ -57,18 +48,13 @@ export interface FullscreenImageCarouselCustomItemProps { renderContent: () => React.ReactNode; } -export interface FullscreenCarouselItemWithFileName { - fileName: string; +export type FullscreenImageCarouselItemProps = ( + | FullscreenImageCarouselImageItemProps + | FullscreenImageCarouselCustomItemProps +) & { + fileName?: string | undefined; fileSize?: string | undefined; -} - -export interface FullscreenCarouselItemWithoutFileName { - fileName?: undefined; - fileSize?: undefined; -} - -export type FullscreenImageCarouselItemProps = FullscreenCarouselItemBase & - (FullscreenCarouselItemWithFileName | FullscreenCarouselItemWithoutFileName); +}; export interface ImageDimension { width: number; diff --git a/stories/fullscreen-image-carousel/fullscreen-image-carousel.stories.tsx b/stories/fullscreen-image-carousel/fullscreen-image-carousel.stories.tsx index 7f00623a6..66321442e 100644 --- a/stories/fullscreen-image-carousel/fullscreen-image-carousel.stories.tsx +++ b/stories/fullscreen-image-carousel/fullscreen-image-carousel.stories.tsx @@ -164,16 +164,6 @@ export const WithFileInfo: StoryObj = { }, { src: "https://picsum.photos/id/369/1000/1000", - fileName: "square-image.jpg", - }, - { - src: "https://picsum.photos/id/445/300/300", - fileName: "small-photo.jpg", - fileSize: "320 KB", - }, - { - src: "https://picsum.photos/id/237/800/600", - fileName: "dog-photo.jpg", }, ]} show={show} diff --git a/stories/fullscreen-image-carousel/props-table.tsx b/stories/fullscreen-image-carousel/props-table.tsx index da939619d..868acee5b 100644 --- a/stories/fullscreen-image-carousel/props-table.tsx +++ b/stories/fullscreen-image-carousel/props-table.tsx @@ -121,6 +121,18 @@ const DATA: ApiTableSectionProps[] = [ ), }, + { + name: "fileName", + description: + "The file name to display in the file info bar at the top", + propTypes: ["string"], + }, + { + name: "fileSize", + description: + 'The pre-formatted file size string to display in the file info bar at the top (e.g. "2.4 MB")', + propTypes: ["string"], + }, ], }, { @@ -158,18 +170,6 @@ const DATA: ApiTableSectionProps[] = [ ), propTypes: ["string"], }, - { - name: "fileName", - description: - "The file name to display in the file info bar at the top", - propTypes: ["string"], - }, - { - name: "fileSize", - description: - 'The pre-formatted file size string to display in the file info bar at the top (e.g. "2.4 MB")', - propTypes: ["string"], - }, ], }, { diff --git a/tests/fullscreen-image-carousel/fullscreen-image-carousel.spec.tsx b/tests/fullscreen-image-carousel/fullscreen-image-carousel.spec.tsx index ba120373d..da2054e41 100644 --- a/tests/fullscreen-image-carousel/fullscreen-image-carousel.spec.tsx +++ b/tests/fullscreen-image-carousel/fullscreen-image-carousel.spec.tsx @@ -286,19 +286,37 @@ describe("Fullscreen Image Carousel", () => { ); expect(screen.getByTestId("file-info-bar")).toBeInTheDocument(); expect(screen.getByTestId("file-info-name")).toHaveTextContent( - "photo-d.jpg" + "photo-c.jpg" ); expect( screen.queryByTestId("file-info-size") ).not.toBeInTheDocument(); }); + it("should not render fileName or fileSize for a slide that has neither", () => { + render( + + ); + + expect(screen.getByTestId("file-info-bar")).toBeInTheDocument(); + expect( + screen.queryByTestId("file-info-name") + ).not.toBeInTheDocument(); + expect( + screen.queryByTestId("file-info-size") + ).not.toBeInTheDocument(); + }); + it("should update the file info bar when navigating to a different slide", () => { render( Date: Fri, 17 Apr 2026 17:17:30 +0800 Subject: [PATCH 13/13] [DCUBESDQLR-2356][MPT] let consumers use the standalone fullscreen image carousel item types directly --- src/fullscreen-image-carousel/types.ts | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/fullscreen-image-carousel/types.ts b/src/fullscreen-image-carousel/types.ts index dfeb9af94..32e197df7 100644 --- a/src/fullscreen-image-carousel/types.ts +++ b/src/fullscreen-image-carousel/types.ts @@ -27,7 +27,13 @@ export interface FullscreenImageCarouselProps insets?: Insets | undefined; } -export interface FullscreenImageCarouselImageItemProps { +interface FullscreenImageCarouselBaseItemProps { + fileName?: string | undefined; + fileSize?: string | undefined; +} + +export interface FullscreenImageCarouselImageItemProps + extends FullscreenImageCarouselBaseItemProps { type?: "image" | undefined; src: string; alt?: string | undefined; @@ -38,7 +44,8 @@ export interface FullscreenImageCarouselImageItemProps { /** @deprecated Use FullscreenImageCarouselImageItemProps instead */ export type FullscreenCarouselItemProps = FullscreenImageCarouselImageItemProps; -export interface FullscreenImageCarouselCustomItemProps { +export interface FullscreenImageCarouselCustomItemProps + extends FullscreenImageCarouselBaseItemProps { type: "custom"; /** The thumbnail image src. If omitted, a placeholder is shown in the thumbnail strip. */ thumbnailSrc?: string | undefined; @@ -48,13 +55,9 @@ export interface FullscreenImageCarouselCustomItemProps { renderContent: () => React.ReactNode; } -export type FullscreenImageCarouselItemProps = ( +export type FullscreenImageCarouselItemProps = | FullscreenImageCarouselImageItemProps - | FullscreenImageCarouselCustomItemProps -) & { - fileName?: string | undefined; - fileSize?: string | undefined; -}; + | FullscreenImageCarouselCustomItemProps; export interface ImageDimension { width: number;