use-carousel

PreviousNext
Docs
shadcrafthook

Preview

Loading preview…
hooks/use-carousel.ts
import useEmblaCarousel, { type UseEmblaCarouselType } from "embla-carousel-react";
import * as React from "react";

type CarouselApi = UseEmblaCarouselType[1];
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>;
type CarouselOptions = UseCarouselParameters[0];
type CarouselPlugin = UseCarouselParameters[1];

interface UseCarouselProps {
  count: number;
  initialIndex?: number;
  orientation?: "horizontal" | "vertical";
  opts?: CarouselOptions;
  plugins?: CarouselPlugin;
  setApi?: (api: CarouselApi) => void;
}

export function useCarousel({
  count,
  initialIndex = 0,
  orientation = "horizontal",
  opts,
  plugins,
  setApi,
}: UseCarouselProps) {
  const [carouselRef, api] = useEmblaCarousel(
    {
      inViewThreshold: 1,
      ...opts,
      startIndex: initialIndex,
      axis: orientation === "horizontal" ? "x" : "y",
    },
    plugins
  );

  const [activeIndex, setActiveIndex] = React.useState(initialIndex);
  const [canScrollPrev, setCanScrollPrev] = React.useState(false);
  const [canScrollNext, setCanScrollNext] = React.useState(count > 1);

  const onSelect = React.useCallback((emblaApi: CarouselApi) => {
    if (!emblaApi) return;
    setActiveIndex(emblaApi.selectedScrollSnap());
    setCanScrollPrev(emblaApi.canScrollPrev());
    setCanScrollNext(emblaApi.canScrollNext());
  }, []);

  React.useEffect(() => {
    if (!api) return;
    onSelect(api);
    api.on("select", onSelect);
    api.on("reInit", onSelect);
    return () => {
      api.off("select", onSelect);
      api.off("reInit", onSelect);
    };
  }, [api, onSelect]);

  React.useEffect(() => {
    if (!api || !setApi) return;
    setApi(api);
  }, [api, setApi]);

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

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

  const scrollTo = React.useCallback(
    (index: number) => {
      if (!api) return;
      const clamped = Math.max(0, Math.min(index, count - 1));
      api.scrollTo(clamped);
    },
    [api, count]
  );

  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]
  );

  return {
    carouselRef,
    api,
    opts,
    orientation: orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
    activeIndex,
    canScrollPrev,
    canScrollNext,
    scrollPrev,
    scrollNext,
    scrollTo,
    handleKeyDown,
  } as const;
}

Installation

npx shadcn@latest add @shadcraft/use-carousel

Usage

import { UseCarousel } from "@/hooks/use-carousel"
const value = UseCarousel()