/* eslint-disable react-hooks-extra/no-direct-set-state-in-use-effect */ import type { UseEmblaCarouselType } from 'embla-carousel-react' import Autoplay from 'embla-carousel-autoplay' import useEmblaCarousel from 'embla-carousel-react' import * as React from 'react' import { cn } from '@/utils/classnames' type CarouselApi = UseEmblaCarouselType[1] type UseCarouselParameters = Parameters type CarouselOptions = UseCarouselParameters[0] type CarouselPlugin = UseCarouselParameters[1] type CarouselProps = { opts?: CarouselOptions plugins?: CarouselPlugin orientation?: 'horizontal' | 'vertical' } type CarouselContextValue = { carouselRef: ReturnType[0] api: ReturnType[1] scrollPrev: () => void scrollNext: () => void selectedIndex: number canScrollPrev: boolean canScrollNext: boolean } & CarouselProps const CarouselContext = React.createContext(null) function useCarousel() { const context = React.useContext(CarouselContext) if (!context) throw new Error('useCarousel must be used within a ') return context } type TCarousel = { Content: typeof CarouselContent Item: typeof CarouselItem Previous: typeof CarouselPrevious Next: typeof CarouselNext Dot: typeof CarouselDot Plugin: typeof CarouselPlugins } & React.ForwardRefExoticComponent< React.HTMLAttributes & CarouselProps & React.RefAttributes > const Carousel: TCarousel = React.forwardRef( ({ orientation = 'horizontal', opts, plugins, className, children, ...props }, ref) => { const [carouselRef, api] = useEmblaCarousel( { ...opts, axis: orientation === 'horizontal' ? 'x' : 'y' }, plugins, ) const [canScrollPrev, setCanScrollPrev] = React.useState(false) const [canScrollNext, setCanScrollNext] = React.useState(false) const [selectedIndex, setSelectedIndex] = React.useState(0) const scrollPrev = React.useCallback(() => { api?.scrollPrev() }, [api]) const scrollNext = React.useCallback(() => { api?.scrollNext() }, [api]) React.useEffect(() => { if (!api) return const onSelect = (api: CarouselApi) => { if (!api) return setSelectedIndex(api.selectedScrollSnap()) setCanScrollPrev(api.canScrollPrev()) setCanScrollNext(api.canScrollNext()) } onSelect(api) api.on('reInit', onSelect) api.on('select', onSelect) return () => { api?.off('select', onSelect) } }, [api]) React.useImperativeHandle(ref, () => ({ carouselRef, api, opts, orientation, scrollPrev, scrollNext, selectedIndex, canScrollPrev, canScrollNext, })) return (
{children}
) }, ) as TCarousel Carousel.displayName = 'Carousel' const CarouselContent = React.forwardRef>( ({ className, ...props }, ref) => { const { orientation } = useCarousel() return (
) }, ) CarouselContent.displayName = 'CarouselContent' const CarouselItem = React.forwardRef>( ({ className, ...props }, ref) => { return (
) }, ) CarouselItem.displayName = 'CarouselItem' type CarouselActionProps = { children?: React.ReactNode } & Omit, 'disabled' | 'onClick'> const CarouselPrevious = React.forwardRef( ({ children, ...props }, ref) => { const { scrollPrev, canScrollPrev } = useCarousel() return ( ) }, ) CarouselPrevious.displayName = 'CarouselPrevious' const CarouselNext = React.forwardRef( ({ children, ...props }, ref) => { const { scrollNext, canScrollNext } = useCarousel() return ( ) }, ) CarouselNext.displayName = 'CarouselNext' const CarouselDot = React.forwardRef( ({ children, ...props }, ref) => { const { api, selectedIndex } = useCarousel() return api?.slideNodes().map((_, index) => { return ( ) }) }, ) CarouselDot.displayName = 'CarouselDot' const CarouselPlugins = { Autoplay, } Carousel.Content = CarouselContent Carousel.Item = CarouselItem Carousel.Previous = CarouselPrevious Carousel.Next = CarouselNext Carousel.Dot = CarouselDot Carousel.Plugin = CarouselPlugins export { Carousel, useCarousel }