import type { Accessor, Component, ComponentProps, VoidProps } from "solid-js" import { createContext, createEffect, createMemo, createSignal, mergeProps, splitProps, useContext } from "solid-js" import type { CreateEmblaCarouselType } from "embla-carousel-solid" import createEmblaCarousel from "embla-carousel-solid" import { cn } from "~/styles/utils" import type { ButtonProps } from "~/components/ui/button" import { Button } from "~/components/ui/button" export type CarouselApi = CreateEmblaCarouselType[1] type UseCarouselParameters = Parameters type CarouselOptions = NonNullable type CarouselPlugin = NonNullable type CarouselProps = { opts?: ReturnType plugins?: ReturnType orientation?: "horizontal" | "vertical" setApi?: (api: CarouselApi) => void } type CarouselContextProps = { carouselRef: ReturnType[0] api: ReturnType[1] scrollPrev: () => void scrollNext: () => void canScrollPrev: Accessor canScrollNext: Accessor } & CarouselProps const CarouselContext = createContext | null>(null) const useCarousel = () => { const context = useContext(CarouselContext) if (!context) { throw new Error("useCarousel must be used within a ") } return context() } const Carousel: Component> = (rawProps) => { const props = mergeProps<(CarouselProps & ComponentProps<"div">)[]>( { orientation: "horizontal" }, rawProps ) const [local, others] = splitProps(props, [ "orientation", "opts", "setApi", "plugins", "class", "children" ]) const [carouselRef, api] = createEmblaCarousel( () => ({ ...local.opts, axis: local.orientation === "horizontal" ? "x" : "y" }), () => (local.plugins === undefined ? [] : local.plugins) ) const [canScrollPrev, setCanScrollPrev] = createSignal(false) const [canScrollNext, setCanScrollNext] = createSignal(false) const onSelect = (api: NonNullable>) => { setCanScrollPrev(api.canScrollPrev()) setCanScrollNext(api.canScrollNext()) } const scrollPrev = () => { api()?.scrollPrev() } const scrollNext = () => { api()?.scrollNext() } const handleKeyDown = (event: KeyboardEvent) => { if (event.key === "ArrowLeft") { event.preventDefault() scrollPrev() } else if (event.key === "ArrowRight") { event.preventDefault() scrollNext() } } createEffect(() => { if (!api() || !local.setApi) { return } local.setApi(api) }) createEffect(() => { if (!api()) { return } onSelect(api()!) api()!.on("reInit", onSelect) api()!.on("select", onSelect) return () => { api()?.off("select", onSelect) } }) const value = createMemo( () => ({ carouselRef, api, opts: local.opts, orientation: local.orientation || (local.opts?.axis === "y" ? "vertical" : "horizontal"), scrollPrev, scrollNext, canScrollPrev, canScrollNext }) satisfies CarouselContextProps ) return (
{local.children}
) } const CarouselContent: Component> = (props) => { const [local, others] = splitProps(props, ["class"]) const { carouselRef, orientation } = useCarousel() return (
) } const CarouselItem: Component> = (props) => { const [local, others] = splitProps(props, ["class"]) const { orientation } = useCarousel() return (
) } type CarouselButtonProps = VoidProps const CarouselPrevious: Component = (rawProps) => { const props = mergeProps({ variant: "outline", size: "icon" }, rawProps) const [local, others] = splitProps(props, ["class", "variant", "size"]) const { orientation, scrollPrev, canScrollPrev } = useCarousel() return ( ) } const CarouselNext: Component = (rawProps) => { const props = mergeProps({ variant: "outline", size: "icon" }, rawProps) const [local, others] = splitProps(props, ["class", "variant", "size"]) const { orientation, scrollNext, canScrollNext } = useCarousel() return ( ) } export { Carousel, CarouselContent, CarouselItem, CarouselPrevious, CarouselNext }