{
  "$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<typeof useEmblaCarousel>;\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<typeof useEmblaCarousel>[0];\n  api: ReturnType<typeof useEmblaCarousel>[1];\n  scrollPrev: () => void;\n  scrollNext: () => void;\n  canScrollPrev: boolean;\n  canScrollNext: boolean;\n} & CarouselProps;\n\nconst CarouselContext = createContext<CarouselContextProps | null>(null);\n\nconst useCarousel = () => {\n  const context = useContext(CarouselContext);\n\n  if (!context) {\n    throw new Error(\"useCarousel must be used within a <Carousel />\");\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<HTMLDivElement>) => {\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    <CarouselContext.Provider value={contextValue}>\n      {/* oxlint-disable jsx-a11y/prefer-tag-over-role -- role=\"region\" with aria-roledescription is the WAI-ARIA carousel pattern */}\n      <div\n        aria-roledescription=\"carousel\"\n        className={cn(\"relative\", className)}\n        data-slot=\"carousel\"\n        onKeyDownCapture={handleKeyDown}\n        role=\"region\"\n        {...props}\n      >\n        {children}\n      </div>\n      {/* oxlint-enable jsx-a11y/prefer-tag-over-role */}\n    </CarouselContext.Provider>\n  );\n};\n\nconst CarouselContent = ({ className, ...props }: React.ComponentProps<\"div\">) => {\n  const { carouselRef, orientation } = useCarousel();\n\n  return (\n    <div className=\"overflow-hidden\" data-slot=\"carousel-content\" ref={carouselRef}>\n      <div\n        className={cn(\"flex\", orientation === \"horizontal\" ? \"-ml-4\" : \"-mt-4 flex-col\", className)}\n        {...props}\n      />\n    </div>\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    <div\n      aria-roledescription=\"slide\"\n      className={cn(\n        \"min-w-0 shrink-0 grow-0 basis-full\",\n        orientation === \"horizontal\" ? \"pl-4\" : \"pt-4\",\n        className,\n      )}\n      data-slot=\"carousel-item\"\n      role=\"group\"\n      {...props}\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<typeof Button>) => {\n  const { orientation, scrollPrev, canScrollPrev } = useCarousel();\n\n  return (\n    <Button\n      className={cn(\n        \"absolute size-8 rounded-full\",\n        orientation === \"horizontal\"\n          ? \"top-1/2 -left-12 -translate-y-1/2\"\n          : \"-top-12 left-1/2 -translate-x-1/2 rotate-90\",\n        className,\n      )}\n      data-slot=\"carousel-previous\"\n      disabled={!canScrollPrev}\n      onClick={scrollPrev}\n      size={size}\n      variant={variant}\n      {...props}\n    >\n      <ArrowLeft />\n      <span className=\"sr-only\">Previous slide</span>\n    </Button>\n  );\n};\n\nconst CarouselNext = ({\n  className,\n  variant = \"outline\",\n  size = \"icon\",\n  ...props\n}: React.ComponentProps<typeof Button>) => {\n  const { orientation, scrollNext, canScrollNext } = useCarousel();\n\n  return (\n    <Button\n      className={cn(\n        \"absolute size-8 rounded-full\",\n        orientation === \"horizontal\"\n          ? \"top-1/2 -right-12 -translate-y-1/2\"\n          : \"-bottom-12 left-1/2 -translate-x-1/2 rotate-90\",\n        className,\n      )}\n      data-slot=\"carousel-next\"\n      disabled={!canScrollNext}\n      onClick={scrollNext}\n      size={size}\n      variant={variant}\n      {...props}\n    >\n      <ArrowRight />\n      <span className=\"sr-only\">Next slide</span>\n    </Button>\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"
}
