Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 31 additions & 11 deletions packages/pluggableWidgets/carousel-web/src/Carousel.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className={classNames(props.class, "widget-carousel")} tabIndex={tabIndex}>
<img src={loadingCircleSvg} className="widget-carousel-loading-spinner" alt="" aria-hidden />
Expand All @@ -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}
/>
);
Expand Down
8 changes: 8 additions & 0 deletions packages/pluggableWidgets/carousel-web/src/Carousel.xml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@
<caption>Infinite loop</caption>
<description />
</property>
<property key="slidesPerView" type="integer" defaultValue="1">
<caption>Slides per view</caption>
<description />
</property>
<property key="slidesPerGroup" type="integer" defaultValue="1">
<caption>Slides per group</caption>
<description />
</property>
<property key="animation" type="boolean" defaultValue="true">
<caption>Animation</caption>
<description />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ export interface CarouselProps {
animation?: boolean;
autoplay?: boolean;
delay?: number;
slidesPerView?: number;
slidesPerGroup?: number;
navigation: boolean;
className: string;
tabIndex?: number | undefined;
Expand All @@ -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<number>(0);

const getSlideId = useCallback(
Expand All @@ -47,7 +63,8 @@ export function Carousel(props: CarouselProps): ReactElement {
};

const options: SwiperOptions = {
slidesPerView: 1,
slidesPerView,
slidesPerGroup,
centeredSlides: true,
loop,
navigation,
Expand Down Expand Up @@ -76,9 +93,9 @@ export function Carousel(props: CarouselProps): ReactElement {
<Swiper
onActiveIndexChange={updateSwiperIndex}
wrapperTag={"ul"}
{...options}
onClick={onClick}
onSwiper={updateSwiperIndex}
{...options}
>
{items?.map((item, index) => (
<SwiperSlide tag={"li"} aria-hidden={index !== activeIndex} key={item.id} id={getSlideId(item)}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,13 +156,16 @@ describe("Carousel", () => {
jest.resetAllMocks();
jest.spyOn(Math, "random").mockReturnValue(0.123456789);
});

const defaultCarouselProps: CarouselProps = {
id: "Carousel",
className: "",
items: [
{ id: "1" as GUID, content: <div>test1</div> },
{ id: "2" as GUID, content: <div>test2</div> }
],
slidesPerView: 1,
slidesPerGroup: 1,
pagination: true,
animation: true,
autoplay: true,
Expand All @@ -177,21 +180,25 @@ describe("Carousel", () => {

expect(asFragment()).toMatchSnapshot();
});

it("renders correctly without pagination", () => {
const { asFragment } = render(<Carousel {...defaultCarouselProps} pagination={false} />);

expect(asFragment()).toMatchSnapshot();
});

it("renders correctly without navigation", () => {
const { asFragment } = render(<Carousel {...defaultCarouselProps} navigation={false} />);

expect(asFragment()).toMatchSnapshot();
});

it("renders correctly with minimal setup", () => {
const { asFragment } = render(<Carousel {...defaultCarouselProps} pagination={false} navigation={false} />);

expect(asFragment()).toMatchSnapshot();
});

afterEach(() => {
jest.restoreAllMocks();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ export interface CarouselContainerProps {
autoplay: boolean;
delay: number;
loop: boolean;
slidesPerView: number;
slidesPerGroup: number;
animation: boolean;
onClickAction?: ActionValue;
}
Expand All @@ -40,6 +42,8 @@ export interface CarouselPreviewProps {
autoplay: boolean;
delay: number | null;
loop: boolean;
slidesPerView: number | null;
slidesPerGroup: number | null;
animation: boolean;
onClickAction: {} | null;
}
Loading