सामग्री पर जाएँ

कैरसेल

उत्पाद गैलरियों, प्रशंसापत्रों और हीरो सेक्शन के लिए स्वाइप करने योग्य स्लाइडशो — नियोब्रूटलिस्ट बॉर्डर और कठोर छायाओं में सजी हुई।

import * as React from "react"

import { Card, CardContent } from "@/components/ui/card"

कैरॉसेल स्लाइडों की पट्टी पर स्नैप करता है — हॉरिज़ॉन्टल या वर्टिकल — ड्रैग और स्वाइप, previous/next बटन, और imperative API के साथ। यह Embla Carousel लाइब्रेरी पर बना है और नियोब्रूटलिस्ट रेसिपी के अनुसार स्टाइल किया गया है: मोटे बॉर्डर, कठोर छायाएँ और बोल्ड टाइपोग्राफ़ी।

इसे इन स्थितियों में चुनें:

  • प्रोडक्ट गैलरी — लंबी स्क्रॉल की जगह तय स्लॉट में स्क्रीनशॉट या फ़ोटो घुमाएँ।
  • टेस्टिमोनियल और लोगो वॉल — सोशल प्रूफ़ घुमाएँ; Embla प्लगिन से autoplay।
  • ऑनबोर्डिंग और फ़ीचर टूर — छोटी श्रृंखला को एक-एक कार्ड पर दिखाएँ।

परिचय

carousel कंपोनेंट Embla Carousel लाइब्रेरी का उपयोग करके बनाया गया है।

इंस्टॉलेशन

pnpm dlx shadcn@latest add https://neobrutalism.com/r/base/carousel.json
npx shadcn@latest add https://neobrutalism.com/r/base/carousel.json
yarn dlx shadcn@latest add https://neobrutalism.com/r/base/carousel.json
bunx --bun shadcn@latest add https://neobrutalism.com/r/base/carousel.json

निम्नलिखित डिपेंडेंसी इंस्टॉल करें:

pnpm add embla-carousel-react
npm install embla-carousel-react
yarn add embla-carousel-react
bun add embla-carousel-react

निम्नलिखित कोड को कॉपी करके अपने प्रोजेक्ट में पेस्ट करें।

components/ui/carousel.tsx
"use client"

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

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

type CarouselApi = UseEmblaCarouselType[1]
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
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<typeof useEmblaCarousel>[0]
  api: ReturnType<typeof useEmblaCarousel>[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 <Carousel />")
  }

  return context
}

function Carousel({
  orientation = "horizontal",
  opts,
  setApi,
  plugins,
  className,
  children,
  ...props
}: React.ComponentProps<"div"> & CarouselProps) {
  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("reInit", onSelect)
      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
        onKeyDownCapture={handleKeyDown}
        className={cn("relative", className)}
        role="region"
        aria-roledescription="carousel"
        data-slot="carousel"
        {...props}
      >
        {children}
      </div>
    </CarouselContext.Provider>
  )
}

function CarouselContent({ className, ...props }: React.ComponentProps<"div">) {
  const { carouselRef, orientation } = useCarousel()

  return (
    <div
      ref={carouselRef}
      className="overflow-hidden"
      data-slot="carousel-content"
    >
      <div
        className={cn(
          "flex",
          orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
          className
        )}
        {...props}
      />
    </div>
  )
}

function CarouselItem({ className, ...props }: React.ComponentProps<"div">) {
  const { orientation } = useCarousel()

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

function CarouselPrevious({
  className,
  variant = "outline",
  size = "icon-sm",
  ...props
}: React.ComponentProps<typeof Button>) {
  const { orientation, scrollPrev, canScrollPrev } = useCarousel()

  return (
    <Button
      data-slot="carousel-previous"
      variant={variant}
      size={size}
      className={cn(
        "absolute size-8 touch-manipulation rounded",
        orientation === "horizontal"
          ? "top-1/2 -left-12 -translate-y-1/2 hover:-translate-y-[calc(50%-2px)] active:-translate-y-[calc(50%-4px)]"
          : "-top-12 left-1/2 -translate-x-1/2 rotate-90 hover:-translate-x-[calc(50%-2px)] active:-translate-x-[calc(50%-4px)]",
        className
      )}
      disabled={!canScrollPrev}
      onClick={scrollPrev}
      {...props}
    >
      <ArrowLeftIcon className="cn-rtl-flip" />
      <span className="sr-only">Previous slide</span>
    </Button>
  )
}

function CarouselNext({
  className,
  variant = "outline",
  size = "icon-sm",
  ...props
}: React.ComponentProps<typeof Button>) {
  const { orientation, scrollNext, canScrollNext } = useCarousel()

  return (
    <Button
      data-slot="carousel-next"
      variant={variant}
      size={size}
      className={cn(
        "absolute size-8 touch-manipulation rounded",
        orientation === "horizontal"
          ? "top-1/2 -right-12 -translate-y-1/2 hover:-translate-y-[calc(50%-2px)] active:-translate-y-[calc(50%-4px)]"
          : "-bottom-12 left-1/2 -translate-x-1/2 rotate-90 hover:-translate-x-[calc(50%-2px)] active:-translate-x-[calc(50%-4px)]",
        className
      )}
      disabled={!canScrollNext}
      onClick={scrollNext}
      {...props}
    >
      <ArrowRightIcon className="cn-rtl-flip" />
      <span className="sr-only">Next slide</span>
    </Button>
  )
}

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

अपने प्रोजेक्ट सेटअप से मेल खाने के लिए import पाथ अपडेट करें।

उपयोग

import {
  Carousel,
  CarouselContent,
  CarouselItem,
  CarouselNext,
  CarouselPrevious,
} from "@/components/ui/carousel"
<Carousel>
  <CarouselContent>
    <CarouselItem>...</CarouselItem>
    <CarouselItem>...</CarouselItem>
    <CarouselItem>...</CarouselItem>
  </CarouselContent>
  <CarouselPrevious />
  <CarouselNext />
</Carousel>

कंपोज़िशन

Carousel बनाने के लिए निम्नलिखित कंपोज़िशन का उपयोग करें:

Carousel
├── CarouselContent
│   ├── CarouselItem
│   └── CarouselItem
├── CarouselPrevious
└── CarouselNext

उदाहरण

साइज़

आइटम का साइज़ सेट करने के लिए, आप <CarouselItem /> पर basis यूटिलिटी क्लास का उपयोग कर सकते हैं।

import * as React from "react"

import { Card, CardContent } from "@/components/ui/card"
// 33% of the carousel width.
<Carousel>
  <CarouselContent>
    <CarouselItem className="basis-1/3">...</CarouselItem>
    <CarouselItem className="basis-1/3">...</CarouselItem>
    <CarouselItem className="basis-1/3">...</CarouselItem>
  </CarouselContent>
</Carousel>
// 50% on small screens and 33% on larger screens.
<Carousel>
  <CarouselContent>
    <CarouselItem className="md:basis-1/2 lg:basis-1/3">...</CarouselItem>
    <CarouselItem className="md:basis-1/2 lg:basis-1/3">...</CarouselItem>
    <CarouselItem className="md:basis-1/2 lg:basis-1/3">...</CarouselItem>
  </CarouselContent>
</Carousel>

स्पेसिंग

आइटम के बीच स्पेसिंग सेट करने के लिए, हम <CarouselItem /> पर एक pl-[VALUE] यूटिलिटी और <CarouselContent /> पर एक नेगेटिव -ml-[VALUE] का उपयोग करते हैं।

import * as React from "react"

import { Card, CardContent } from "@/components/ui/card"
<Carousel>
  <CarouselContent className="-ml-4">
    <CarouselItem className="pl-4">...</CarouselItem>
    <CarouselItem className="pl-4">...</CarouselItem>
    <CarouselItem className="pl-4">...</CarouselItem>
  </CarouselContent>
</Carousel>
<Carousel>
  <CarouselContent className="-ml-2 md:-ml-4">
    <CarouselItem className="pl-2 md:pl-4">...</CarouselItem>
    <CarouselItem className="pl-2 md:pl-4">...</CarouselItem>
    <CarouselItem className="pl-2 md:pl-4">...</CarouselItem>
  </CarouselContent>
</Carousel>

ओरिएंटेशन

carousel का ओरिएंटेशन सेट करने के लिए orientation prop का उपयोग करें।

import * as React from "react"

import { Card, CardContent } from "@/components/ui/card"
<Carousel orientation="vertical | horizontal">
  <CarouselContent>
    <CarouselItem>...</CarouselItem>
    <CarouselItem>...</CarouselItem>
    <CarouselItem>...</CarouselItem>
  </CarouselContent>
</Carousel>

विकल्प

आप opts prop का उपयोग करके carousel को विकल्प पास कर सकते हैं। अधिक जानकारी के लिए Embla Carousel docs देखें।

<Carousel
  opts={{
    align: "start",
    loop: true,
  }}
>
  <CarouselContent>
    <CarouselItem>...</CarouselItem>
    <CarouselItem>...</CarouselItem>
    <CarouselItem>...</CarouselItem>
  </CarouselContent>
</Carousel>

API

carousel API का इंस्टेंस पाने के लिए एक state और setApi prop का उपयोग करें।

"use client"

import * as React from "react"
import { type CarouselApi } from "@/components/ui/carousel"
 
export function Example() {
  const [api, setApi] = React.useState<CarouselApi>()
  const [current, setCurrent] = React.useState(0)
  const [count, setCount] = React.useState(0)
 
  React.useEffect(() => {
    if (!api) {
      return
    }
 
    setCount(api.scrollSnapList().length)
    setCurrent(api.selectedScrollSnap() + 1)
 
    api.on("select", () => {
      setCurrent(api.selectedScrollSnap() + 1)
    })
  }, [api])
 
  return (
    <Carousel setApi={setApi}>
      <CarouselContent>
        <CarouselItem>...</CarouselItem>
        <CarouselItem>...</CarouselItem>
        <CarouselItem>...</CarouselItem>
      </CarouselContent>
    </Carousel>
  )
}

इवेंट्स

आप setApi से मिली api इंस्टेंस का उपयोग करके इवेंट्स सुन सकते हैं।

import { type CarouselApi } from "@/components/ui/carousel"
 
export function Example() {
  const [api, setApi] = React.useState<CarouselApi>()
 
  React.useEffect(() => {
    if (!api) {
      return
    }
 
    api.on("select", () => {
      // Do something on select.
    })
  }, [api])
 
  return (
    <Carousel setApi={setApi}>
      <CarouselContent>
        <CarouselItem>...</CarouselItem>
        <CarouselItem>...</CarouselItem>
        <CarouselItem>...</CarouselItem>
      </CarouselContent>
    </Carousel>
  )
}

इवेंट्स के उपयोग के बारे में अधिक जानकारी के लिए Embla Carousel docs देखें।

Plugins

आप carousel में plugins जोड़ने के लिए plugins prop का उपयोग कर सकते हैं।

import Autoplay from "embla-carousel-autoplay"
 
export function Example() {
  return (
    <Carousel
      plugins={[
        Autoplay({
          delay: 2000,
        }),
      ]}
    >
      // ...
    </Carousel>
  )
}
"use client"

import * as React from "react"

RTL

Neobrutalism में RTL सपोर्ट सक्षम करने के लिए, RTL कॉन्फ़िगरेशन गाइड देखें।

"use client"

import * as React from "react"

RTL भाषाओं के लिए carousel को लोकलाइज़ करते समय, आपको टेक्स्ट की दिशा से मेल खाने के लिए opts prop में direction विकल्प सेट करना होगा। इससे यह सुनिश्चित होता है कि carousel सही दिशा में स्क्रॉल करे।

<Carousel
  dir={dir}
  opts={{
    direction: dir,
  }}
>
  <CarouselContent>
    <CarouselItem>...</CarouselItem>
    <CarouselItem>...</CarouselItem>
    <CarouselItem>...</CarouselItem>
  </CarouselContent>
  <CarouselPrevious className="rtl:rotate-180" />
  <CarouselNext className="rtl:rotate-180" />
</Carousel>

direction विकल्प "ltr" या "rtl" स्वीकार करता है और उसे dir prop की वैल्यू से मेल खाना चाहिए। आप navigation बटन को सही दिशा में इंगित करने के लिए rtl:rotate-180 क्लास का उपयोग करके घुमाना भी चाह सकते हैं।

एक्सेसिबिलिटी

कैरॉसेल WAI-ARIA Carousel पैटर्न का पालन करता है: रूट role="region" और aria-roledescription="carousel" रेंडर करता है, हर स्लाइड role="group" और aria-roledescription="slide" होती है, और लूप न होने पर सिरों पर previous/next बटन अक्षम हो जाते हैं। Embla पॉइंटर और टच संभालता है; रैपर कीबोर्ड लेयर जोड़ता है।

कीबोर्ड इंटरैक्शन:

कुंजीक्रिया
Tab / Shift + Tabprevious / next नेविगेशन बटन पर फ़ोकस ले जाएँ
Space / Enterफ़ोकस किए गए नेविगेशन बटन को सक्रिय करें
ArrowLeft / ArrowRightकैरॉसेल के अंदर फ़ोकस होने पर पिछली / अगली स्लाइड पर स्क्रॉल करें

API रेफ़रेंस

props और plugins के बारे में अधिक जानकारी के लिए Embla Carousel docs देखें।