{ "$schema": "https://blode.co/ui/schema/registry-item.json", "name": "carousel", "title": "Carousel", "author": "Matthew Blode", "description": "A slideshow component for cycling through a set of content.", "dependencies": ["embla-carousel-react"], "registryDependencies": ["button"], "files": [ { "path": "ui/carousel.tsx", "content": "\"use client\";\n\nimport { ArrowLeft, ArrowRight } from \"blode-icons-react\";\nimport useEmblaCarousel from \"embla-carousel-react\";\nimport type { UseEmblaCarouselType } from \"embla-carousel-react\";\nimport type * as React from \"react\";\nimport { createContext, useCallback, useContext, useEffect, useMemo, useState } from \"react\";\n\nimport { cn } from \"@/lib/utils\";\nimport { Button } from \"@/components/ui/button\";\n\ntype CarouselApi = UseEmblaCarouselType[1];\ntype UseCarouselParameters = Parameters;\ntype CarouselOptions = UseCarouselParameters[0];\ntype CarouselPlugin = UseCarouselParameters[1];\n\ninterface CarouselProps {\n opts?: CarouselOptions;\n orientation?: \"horizontal\" | \"vertical\";\n plugins?: CarouselPlugin;\n setApi?: (api: CarouselApi) => void;\n}\n\ntype CarouselContextProps = {\n carouselRef: ReturnType[0];\n api: ReturnType[1];\n scrollPrev: () => void;\n scrollNext: () => void;\n canScrollPrev: boolean;\n canScrollNext: boolean;\n} & CarouselProps;\n\nconst CarouselContext = createContext(null);\n\nconst useCarousel = () => {\n const context = useContext(CarouselContext);\n\n if (!context) {\n throw new Error(\"useCarousel must be used within a \");\n }\n\n return context;\n};\n\nconst Carousel = ({\n orientation = \"horizontal\",\n opts,\n setApi,\n plugins,\n className,\n children,\n ...props\n}: React.ComponentProps<\"div\"> & CarouselProps) => {\n const [carouselRef, api] = useEmblaCarousel(\n {\n ...opts,\n axis: orientation === \"horizontal\" ? \"x\" : \"y\",\n },\n plugins,\n );\n const [canScrollPrev, setCanScrollPrev] = useState(false);\n const [canScrollNext, setCanScrollNext] = useState(false);\n\n const onSelect = useCallback((emblaApi: CarouselApi) => {\n if (!emblaApi) {\n return;\n }\n setCanScrollPrev(emblaApi.canScrollPrev());\n setCanScrollNext(emblaApi.canScrollNext());\n }, []);\n\n const scrollPrev = useCallback(() => {\n api?.scrollPrev();\n }, [api]);\n\n const scrollNext = useCallback(() => {\n api?.scrollNext();\n }, [api]);\n\n const handleKeyDown = useCallback(\n (event: React.KeyboardEvent) => {\n if (event.key === \"ArrowLeft\") {\n event.preventDefault();\n scrollPrev();\n } else if (event.key === \"ArrowRight\") {\n event.preventDefault();\n scrollNext();\n }\n },\n [scrollPrev, scrollNext],\n );\n\n useEffect(() => {\n if (!(api && setApi)) {\n return;\n }\n setApi(api);\n }, [api, setApi]);\n\n useEffect(() => {\n if (!api) {\n return;\n }\n // oxlint-disable-next-line react/react-compiler -- syncing initial Embla state into React on subscribe is intentional\n onSelect(api);\n api.on(\"reInit\", onSelect);\n api.on(\"select\", onSelect);\n\n return () => {\n api?.off(\"select\", onSelect);\n };\n }, [api, onSelect]);\n\n const contextValue = useMemo(\n () => ({\n api,\n canScrollNext,\n canScrollPrev,\n carouselRef,\n opts,\n orientation: orientation || (opts?.axis === \"y\" ? \"vertical\" : \"horizontal\"),\n scrollNext,\n scrollPrev,\n }),\n [api, canScrollNext, canScrollPrev, carouselRef, opts, orientation, scrollNext, scrollPrev],\n );\n\n return (\n \n {/* oxlint-disable jsx-a11y/prefer-tag-over-role -- role=\"region\" with aria-roledescription is the WAI-ARIA carousel pattern */}\n \n {children}\n \n {/* oxlint-enable jsx-a11y/prefer-tag-over-role */}\n \n );\n};\n\nconst CarouselContent = ({ className, ...props }: React.ComponentProps<\"div\">) => {\n const { carouselRef, orientation } = useCarousel();\n\n return (\n
\n \n
\n );\n};\n\nconst CarouselItem = ({ className, ...props }: React.ComponentProps<\"div\">) => {\n const { orientation } = useCarousel();\n\n return (\n /* oxlint-disable jsx-a11y/prefer-tag-over-role -- role=\"group\" with aria-roledescription is the WAI-ARIA carousel slide pattern */\n \n /* oxlint-enable jsx-a11y/prefer-tag-over-role */\n );\n};\n\nconst CarouselPrevious = ({\n className,\n variant = \"outline\",\n size = \"icon\",\n ...props\n}: React.ComponentProps) => {\n const { orientation, scrollPrev, canScrollPrev } = useCarousel();\n\n return (\n \n \n Previous slide\n \n );\n};\n\nconst CarouselNext = ({\n className,\n variant = \"outline\",\n size = \"icon\",\n ...props\n}: React.ComponentProps) => {\n const { orientation, scrollNext, canScrollNext } = useCarousel();\n\n return (\n \n \n Next slide\n \n );\n};\n\nexport {\n type CarouselApi,\n Carousel,\n CarouselContent,\n CarouselItem,\n CarouselPrevious,\n CarouselNext,\n};\n", "type": "registry:ui", "target": "" } ], "type": "registry:ui" }