Files
web/components/Carousel/index.tsx
Chuma Mcphoy (We Ahead) 38cce4b136 Merged in feat/SW-1542-carousel-functionality (pull request #1311)
feat(SW-1542): Carousel component

* feat(SW-1542): add Embla Carousel component and use in CarouselCards

* fix(SW-1542): remove max-width constraint for card on ipad

* fix(SW-1542): Add padding to start page content container

* refactor(SW-1542): Improve Embla Carousel type imports

* refactor(SW-1542): Remove unnecessary carousel wrapper div

* refactor(SW-1542): Modularize Carousel component structure

* refactor(SW-1542): Remove carousel dots display

* feat(SW-1542): Add carousel dots navigation

* refactor(SW-1542): Update Carousel component styling and types

* refactor(SW-1542): Remove uneeded useCallback from Carousel navigation methods

* refactor(SW-1542): Modify CarouselContextProps type to exclude className

* refactor(SW-1542): Optimize React imports in Carousel components

* refactor(SW-1542): Consolidate Carousel component and remove CarouselRoot

* refactor(SW-1542): Update Carousel navigation methods to use function-based scroll checks

* refactor(SW-1542): Add explicit children prop support to CarouselContent component

* refactor(SW-1542): Add children prop support to CarouselItem component


Approved-by: Christian Andolf
2025-02-14 10:53:14 +00:00

109 lines
2.4 KiB
TypeScript

"use client"
import { cx } from "class-variance-authority"
import useEmblaCarousel from "embla-carousel-react"
import { useCallback, useEffect, useState } from "react"
import { CarouselContent } from "./CarouselContent"
import { CarouselContext } from "./CarouselContext"
import { CarouselDots } from "./CarouselDots"
import { CarouselItem } from "./CarouselItem"
import { CarouselNext, CarouselPrevious } from "./CarouselNavigation"
import styles from "./carousel.module.css"
import type { CarouselApi, CarouselProps } from "./types"
function Carousel({
opts,
setApi,
plugins,
className,
children,
}: CarouselProps) {
const [carouselRef, api] = useEmblaCarousel(
{
...opts,
axis: "x",
},
plugins
)
const [selectedIndex, setSelectedIndex] = useState(0)
const onSelect = useCallback((api: CarouselApi) => {
if (!api) return
setSelectedIndex(api.selectedScrollSnap())
}, [])
function scrollPrev() {
api?.scrollPrev()
}
function scrollNext() {
api?.scrollNext()
}
function handleKeyDown(event: React.KeyboardEvent<HTMLDivElement>) {
if (event.key === "ArrowLeft") {
event.preventDefault()
scrollPrev()
} else if (event.key === "ArrowRight") {
event.preventDefault()
scrollNext()
}
}
useEffect(() => {
if (!api || !setApi) return
setApi(api)
}, [api, setApi])
useEffect(() => {
if (!api) return
onSelect(api)
api.on("reInit", onSelect)
api.on("select", onSelect)
return () => {
api.off("select", onSelect)
}
}, [api, onSelect])
return (
<CarouselContext.Provider
value={{
carouselRef,
api,
opts,
plugins,
setApi,
children,
scrollPrev,
scrollNext,
canScrollPrev: api?.canScrollPrev.bind(api) ?? (() => false),
canScrollNext: api?.canScrollNext.bind(api) ?? (() => false),
selectedIndex,
}}
>
<div
onKeyDownCapture={handleKeyDown}
className={cx(styles.root, className)}
role="region"
aria-roledescription="carousel"
>
{children}
</div>
</CarouselContext.Provider>
)
}
Carousel.Content = CarouselContent
Carousel.Item = CarouselItem
Carousel.Next = CarouselNext
Carousel.Previous = CarouselPrevious
Carousel.Dots = CarouselDots
export { Carousel }
export type { CarouselApi }