From 9c89bf9137001ad3b7f559b7d40cc827a80f2afe Mon Sep 17 00:00:00 2001 From: Diljohn Singh Date: Wed, 3 Jun 2026 10:30:38 +0200 Subject: [PATCH] feat: adding slides configuration --- .../carousel-web/src/Carousel.tsx | 42 ++++++++++++++----- .../carousel-web/src/Carousel.xml | 8 ++++ .../carousel-web/src/components/Carousel.tsx | 23 ++++++++-- .../components/__tests__/Carousel.spec.tsx | 7 ++++ .../carousel-web/typings/CarouselProps.d.ts | 4 ++ 5 files changed, 70 insertions(+), 14 deletions(-) diff --git a/packages/pluggableWidgets/carousel-web/src/Carousel.tsx b/packages/pluggableWidgets/carousel-web/src/Carousel.tsx index 8bd335a2bd..93784da793 100644 --- a/packages/pluggableWidgets/carousel-web/src/Carousel.tsx +++ b/packages/pluggableWidgets/carousel-web/src/Carousel.tsx @@ -1,18 +1,41 @@ import { executeAction } from "@mendix/widget-plugin-platform/framework/execute-action"; import classNames from "classnames"; import { GUID, ObjectItem, ValueStatus } from "mendix"; -import { ReactNode, useCallback, useId } from "react"; -import { CarouselContainerProps } from "../typings/CarouselProps"; +import { ReactNode, useCallback, useId, useMemo } from "react"; + +import { type CarouselContainerProps } from "../typings/CarouselProps"; import { Carousel as CarouselComponent } from "./components/Carousel"; -import "./ui/Carousel.scss"; + import loadingCircleSvg from "./ui/loading-circle.svg"; +import "./ui/Carousel.scss"; + export function Carousel(props: CarouselContainerProps): ReactNode { - const { showPagination, loop, tabIndex, navigation, animation, delay, autoplay } = props; + const { + showPagination, + loop, + tabIndex, + navigation, + animation, + delay, + autoplay, + slidesPerView, + slidesPerGroup, + dataSource, + content + } = props; const onClick = useCallback(() => executeAction(props.onClickAction), [props.onClickAction]); const id = useId(); + const carouselItems = useMemo( + () => + dataSource?.items?.map((item: ObjectItem) => ({ + id: item.id as GUID, + content: content?.get(item) + })) ?? [], + [dataSource] + ); - if (props.dataSource?.status !== ValueStatus.Available) { + if (dataSource?.status !== ValueStatus.Available) { return (
@@ -27,16 +50,13 @@ export function Carousel(props: CarouselContainerProps): ReactNode { tabIndex={tabIndex} pagination={showPagination} loop={loop} + slidesPerView={slidesPerView} + slidesPerGroup={slidesPerGroup} animation={animation} autoplay={autoplay} delay={delay} navigation={navigation} - items={ - props.dataSource?.items?.map((item: ObjectItem) => ({ - id: item.id as GUID, - content: props.content?.get(item) - })) ?? [] - } + items={carouselItems} onClick={onClick} /> ); diff --git a/packages/pluggableWidgets/carousel-web/src/Carousel.xml b/packages/pluggableWidgets/carousel-web/src/Carousel.xml index 2db73936e0..a7b5afc6c5 100644 --- a/packages/pluggableWidgets/carousel-web/src/Carousel.xml +++ b/packages/pluggableWidgets/carousel-web/src/Carousel.xml @@ -37,6 +37,14 @@ Infinite loop + + Slides per view + + + + Slides per group + + Animation diff --git a/packages/pluggableWidgets/carousel-web/src/components/Carousel.tsx b/packages/pluggableWidgets/carousel-web/src/components/Carousel.tsx index d9c3d10c40..2fede6df97 100644 --- a/packages/pluggableWidgets/carousel-web/src/components/Carousel.tsx +++ b/packages/pluggableWidgets/carousel-web/src/components/Carousel.tsx @@ -19,6 +19,8 @@ export interface CarouselProps { animation?: boolean; autoplay?: boolean; delay?: number; + slidesPerView?: number; + slidesPerGroup?: number; navigation: boolean; className: string; tabIndex?: number | undefined; @@ -27,7 +29,21 @@ export interface CarouselProps { } export function Carousel(props: CarouselProps): ReactElement { - const { items, pagination, loop, animation, autoplay, delay, navigation, className, tabIndex, id, onClick } = props; + const { + items, + pagination, + loop, + animation, + autoplay, + delay, + slidesPerView, + slidesPerGroup, + navigation, + className, + tabIndex, + id, + onClick + } = props; const [activeIndex, setActiveIndex] = useState(0); const getSlideId = useCallback( @@ -47,7 +63,8 @@ export function Carousel(props: CarouselProps): ReactElement { }; const options: SwiperOptions = { - slidesPerView: 1, + slidesPerView, + slidesPerGroup, centeredSlides: true, loop, navigation, @@ -76,9 +93,9 @@ export function Carousel(props: CarouselProps): ReactElement { {items?.map((item, index) => ( diff --git a/packages/pluggableWidgets/carousel-web/src/components/__tests__/Carousel.spec.tsx b/packages/pluggableWidgets/carousel-web/src/components/__tests__/Carousel.spec.tsx index e831171997..7ecda07879 100644 --- a/packages/pluggableWidgets/carousel-web/src/components/__tests__/Carousel.spec.tsx +++ b/packages/pluggableWidgets/carousel-web/src/components/__tests__/Carousel.spec.tsx @@ -156,6 +156,7 @@ describe("Carousel", () => { jest.resetAllMocks(); jest.spyOn(Math, "random").mockReturnValue(0.123456789); }); + const defaultCarouselProps: CarouselProps = { id: "Carousel", className: "", @@ -163,6 +164,8 @@ describe("Carousel", () => { { id: "1" as GUID, content:
test1
}, { id: "2" as GUID, content:
test2
} ], + slidesPerView: 1, + slidesPerGroup: 1, pagination: true, animation: true, autoplay: true, @@ -177,21 +180,25 @@ describe("Carousel", () => { expect(asFragment()).toMatchSnapshot(); }); + it("renders correctly without pagination", () => { const { asFragment } = render(); expect(asFragment()).toMatchSnapshot(); }); + it("renders correctly without navigation", () => { const { asFragment } = render(); expect(asFragment()).toMatchSnapshot(); }); + it("renders correctly with minimal setup", () => { const { asFragment } = render(); expect(asFragment()).toMatchSnapshot(); }); + afterEach(() => { jest.restoreAllMocks(); }); diff --git a/packages/pluggableWidgets/carousel-web/typings/CarouselProps.d.ts b/packages/pluggableWidgets/carousel-web/typings/CarouselProps.d.ts index 87e017a5c8..ac896d0379 100644 --- a/packages/pluggableWidgets/carousel-web/typings/CarouselProps.d.ts +++ b/packages/pluggableWidgets/carousel-web/typings/CarouselProps.d.ts @@ -18,6 +18,8 @@ export interface CarouselContainerProps { autoplay: boolean; delay: number; loop: boolean; + slidesPerView: number; + slidesPerGroup: number; animation: boolean; onClickAction?: ActionValue; } @@ -40,6 +42,8 @@ export interface CarouselPreviewProps { autoplay: boolean; delay: number | null; loop: boolean; + slidesPerView: number | null; + slidesPerGroup: number | null; animation: boolean; onClickAction: {} | null; }