|
@@ -1,216 +0,0 @@
|
|
|
-"use client";
|
|
|
-
|
|
|
-import * as React from "react";
|
|
|
-import { ArrowLeft, ArrowRight } from "lucide-react";
|
|
|
-import useEmblaCarousel, { UseEmblaCarouselType } from "embla-carousel-react";
|
|
|
-
|
|
|
-import { cn } from "@/shared/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;
|
|
|
-}
|
|
|
-
|
|
|
-const Carousel = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement> & 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
|
|
|
- aria-roledescription="carousel"
|
|
|
- className={cn("relative", className)}
|
|
|
- onKeyDownCapture={handleKeyDown}
|
|
|
- ref={ref}
|
|
|
- role="region"
|
|
|
- {...props}
|
|
|
- >
|
|
|
- {children}
|
|
|
- </div>
|
|
|
- </CarouselContext.Provider>
|
|
|
- );
|
|
|
- },
|
|
|
-);
|
|
|
-Carousel.displayName = "Carousel";
|
|
|
-
|
|
|
-const CarouselContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(({ className, ...props }, ref) => {
|
|
|
- const { carouselRef, orientation } = useCarousel();
|
|
|
-
|
|
|
- return (
|
|
|
- <div className="overflow-hidden" ref={carouselRef}>
|
|
|
- <div className={cn("flex", orientation === "horizontal" ? "" : "flex-col", className)} ref={ref} {...props} />
|
|
|
- </div>
|
|
|
- );
|
|
|
-});
|
|
|
-CarouselContent.displayName = "CarouselContent";
|
|
|
-
|
|
|
-const CarouselItem = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(({ className, ...props }, ref) => {
|
|
|
- const { orientation } = useCarousel();
|
|
|
-
|
|
|
- return (
|
|
|
- <div
|
|
|
- aria-roledescription="slide"
|
|
|
- className={cn("shrink-0 grow-0", orientation === "horizontal" ? "" : "pt-4", className)}
|
|
|
- ref={ref}
|
|
|
- role="group"
|
|
|
- {...props}
|
|
|
- />
|
|
|
- );
|
|
|
-});
|
|
|
-CarouselItem.displayName = "CarouselItem";
|
|
|
-
|
|
|
-const CarouselPrevious = React.forwardRef<HTMLButtonElement, React.ComponentProps<typeof Button>>(
|
|
|
- ({ className, variant = "outline", ...props }, ref) => {
|
|
|
- const { orientation, scrollPrev, canScrollPrev } = useCarousel();
|
|
|
-
|
|
|
- return (
|
|
|
- <Button
|
|
|
- className={cn(
|
|
|
- "absolute size-[30px]",
|
|
|
- orientation === "horizontal"
|
|
|
- ? "-bottom-8 left-[39%] sm:-left-12 sm:top-1/2 sm:-translate-y-1/2"
|
|
|
- : "-top-12 left-1/2 -translate-x-1/2 rotate-90",
|
|
|
- className,
|
|
|
- )}
|
|
|
- disabled={!canScrollPrev}
|
|
|
- onClick={scrollPrev}
|
|
|
- ref={ref}
|
|
|
- variant={variant}
|
|
|
- {...props}
|
|
|
- >
|
|
|
- <ArrowLeft className="h-4 w-4" />
|
|
|
- <span className="sr-only">Previous slide</span>
|
|
|
- </Button>
|
|
|
- );
|
|
|
- },
|
|
|
-);
|
|
|
-CarouselPrevious.displayName = "CarouselPrevious";
|
|
|
-
|
|
|
-const CarouselNext = React.forwardRef<HTMLButtonElement, React.ComponentProps<typeof Button>>(
|
|
|
- ({ className, variant = "outline", ...props }, ref) => {
|
|
|
- const { orientation, scrollNext, canScrollNext } = useCarousel();
|
|
|
-
|
|
|
- return (
|
|
|
- <Button
|
|
|
- className={cn(
|
|
|
- "absolute size-[30px]",
|
|
|
- orientation === "horizontal"
|
|
|
- ? "-bottom-8 right-[39%] sm:-right-12 sm:top-1/2 sm:-translate-y-1/2"
|
|
|
- : "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
|
|
|
- className,
|
|
|
- )}
|
|
|
- disabled={!canScrollNext}
|
|
|
- onClick={scrollNext}
|
|
|
- ref={ref}
|
|
|
- variant={variant}
|
|
|
- {...props}
|
|
|
- >
|
|
|
- <ArrowRight className="h-4 w-4" />
|
|
|
- <span className="sr-only">Next slide</span>
|
|
|
- </Button>
|
|
|
- );
|
|
|
- },
|
|
|
-);
|
|
|
-CarouselNext.displayName = "CarouselNext";
|
|
|
-
|
|
|
-export { type CarouselApi, Carousel, CarouselContent, CarouselItem, CarouselPrevious, CarouselNext };
|