Files
f780f31b-fa13-43fb-a74a-208…/research/replit/carousel.md
Nikolay Pecheniev a835343280 Initial commit
2026-04-27 18:07:16 +03:00

6.1 KiB

import * as React from "react" import useEmblaCarousel, { type UseEmblaCarouselType, } from "embla-carousel-react" import { ArrowLeft, ArrowRight } from "lucide-react"

import { cn } from "@/lib/utils" import { Button } from "@/components/ui/button"

type CarouselApi = UseEmblaCarouselType[1] type UseCarouselParameters = Parameters type CarouselOptions = UseCarouselParameters[0] type CarouselPlugin = UseCarouselParameters[1]

type CarouselProps = { opts?: CarouselOptions plugins?: CarouselPlugin orientation?: "horizontal" | "vertical" setApi?: (api: CarouselApi) => void }

type CarouselContextProps = { carouselRef: ReturnType[0] api: ReturnType[1] scrollPrev: () => void scrollNext: () => void canScrollPrev: boolean canScrollNext: boolean } & CarouselProps

const CarouselContext = React.createContext<CarouselContextProps | null>(null)

function useCarousel() { const context = React.useContext(CarouselContext)

if (!context) { throw new Error("useCarousel must be used within a ") }

return context }

const Carousel = React.forwardRef< HTMLDivElement, React.HTMLAttributes & CarouselProps

( ( { orientation = "horizontal", opts, setApi, 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 onSelect = React.useCallback((api: CarouselApi) => {
  if (!api) {
    return
  }

  setCanScrollPrev(api.canScrollPrev())
  setCanScrollNext(api.canScrollNext())
}, [])

const scrollPrev = React.useCallback(() => {
  api?.scrollPrev()
}, [api])

const scrollNext = React.useCallback(() => {
  api?.scrollNext()
}, [api])

const handleKeyDown = React.useCallback(
  (event: React.KeyboardEvent<HTMLDivElement>) => {
    if (event.key === "ArrowLeft") {
      event.preventDefault()
      scrollPrev()
    } else if (event.key === "ArrowRight") {
      event.preventDefault()
      scrollNext()
    }
  },
  [scrollPrev, scrollNext]
)

React.useEffect(() => {
  if (!api || !setApi) {
    return
  }

  setApi(api)
}, [api, setApi])

React.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: api,
      opts,
      orientation:
        orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
      scrollPrev,
      scrollNext,
      canScrollPrev,
      canScrollNext,
    }}
  >
    <div
      ref={ref}
      onKeyDownCapture={handleKeyDown}
      className={cn("relative", className)}
      role="region"
      aria-roledescription="carousel"
      {...props}
    >
      {children}
    </div>
  </CarouselContext.Provider>
)

} ) Carousel.displayName = "Carousel"

const CarouselContent = React.forwardRef< HTMLDivElement, React.HTMLAttributes

(({ className, ...props }, ref) => { const { carouselRef, orientation } = useCarousel()

return (

<div ref={ref} className={cn( "flex", orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col", className )} {...props} />
) }) CarouselContent.displayName = "CarouselContent"

const CarouselItem = React.forwardRef< HTMLDivElement, React.HTMLAttributes

(({ className, ...props }, ref) => { const { orientation } = useCarousel()

return ( <div ref={ref} role="group" aria-roledescription="slide" className={cn( "min-w-0 shrink-0 grow-0 basis-full", orientation === "horizontal" ? "pl-4" : "pt-4", className )} {...props} /> ) }) CarouselItem.displayName = "CarouselItem"

const CarouselPrevious = React.forwardRef< HTMLButtonElement, React.ComponentProps

(({ className, variant = "outline", size = "icon", ...props }, ref) => { const { orientation, scrollPrev, canScrollPrev } = useCarousel()

return ( <Button ref={ref} variant={variant} size={size} className={cn( "absolute h-8 w-8 rounded-full", orientation === "horizontal" ? "-left-12 top-1/2 -translate-y-1/2" : "-top-12 left-1/2 -translate-x-1/2 rotate-90", className )} disabled={!canScrollPrev} onClick={scrollPrev} {...props} > Previous slide ) }) CarouselPrevious.displayName = "CarouselPrevious"

const CarouselNext = React.forwardRef< HTMLButtonElement, React.ComponentProps

(({ className, variant = "outline", size = "icon", ...props }, ref) => { const { orientation, scrollNext, canScrollNext } = useCarousel()

return ( <Button ref={ref} variant={variant} size={size} className={cn( "absolute h-8 w-8 rounded-full", orientation === "horizontal" ? "-right-12 top-1/2 -translate-y-1/2" : "-bottom-12 left-1/2 -translate-x-1/2 rotate-90", className )} disabled={!canScrollNext} onClick={scrollNext} {...props} > Next slide ) }) CarouselNext.displayName = "CarouselNext"

export { type CarouselApi, Carousel, CarouselContent, CarouselItem, CarouselPrevious, CarouselNext, }