{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "carousel",
  "title": "Carousel",
  "description": "A touch and keyboard navigable content carousel powered by Embla.",
  "dependencies": [
    "embla-carousel-react@^8.6.0"
  ],
  "registryDependencies": [
    "@neumorphism-ui/utils",
    "@neumorphism-ui/button"
  ],
  "files": [
    {
      "path": "src/components/ui/carousel.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport useEmblaCarousel, { type UseEmblaCarouselType } from \"embla-carousel-react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\n\ntype CarouselApi = UseEmblaCarouselType[1];\ntype UseCarouselParameters = Parameters<typeof useEmblaCarousel>;\ntype CarouselOptions = UseCarouselParameters[0];\ntype CarouselPlugin = UseCarouselParameters[1];\n\ntype CarouselProps = {\n  opts?: CarouselOptions;\n  plugins?: CarouselPlugin;\n  orientation?: \"horizontal\" | \"vertical\";\n  setApi?: (api: CarouselApi) => void;\n};\n\ntype CarouselContextValue = {\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 = React.createContext<CarouselContextValue | null>(null);\n\nfunction useCarousel() {\n  const context = React.useContext(CarouselContext);\n  if (!context) throw new Error(\"useCarousel must be used within a <Carousel />\");\n  return context;\n}\n\nfunction 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    { ...opts, axis: orientation === \"horizontal\" ? \"x\" : \"y\" },\n    plugins,\n  );\n  const [canScrollPrev, setCanScrollPrev] = React.useState(false);\n  const [canScrollNext, setCanScrollNext] = React.useState(false);\n\n  const onSelect = React.useCallback((instance: CarouselApi) => {\n    if (!instance) return;\n    setCanScrollPrev(instance.canScrollPrev());\n    setCanScrollNext(instance.canScrollNext());\n  }, []);\n\n  const scrollPrev = React.useCallback(() => api?.scrollPrev(), [api]);\n  const scrollNext = React.useCallback(() => api?.scrollNext(), [api]);\n\n  React.useEffect(() => {\n    if (api && setApi) setApi(api);\n  }, [api, setApi]);\n\n  React.useEffect(() => {\n    if (!api) return;\n    onSelect(api);\n    api.on(\"reInit\", onSelect);\n    api.on(\"select\", onSelect);\n    return () => {\n      api.off(\"reInit\", onSelect);\n      api.off(\"select\", onSelect);\n    };\n  }, [api, onSelect]);\n\n  const handleKeyDown = React.useCallback(\n    (event: React.KeyboardEvent<HTMLDivElement>) => {\n      const previousKey = orientation === \"horizontal\" ? \"ArrowLeft\" : \"ArrowUp\";\n      const nextKey = orientation === \"horizontal\" ? \"ArrowRight\" : \"ArrowDown\";\n      if (event.key === previousKey) {\n        event.preventDefault();\n        scrollPrev();\n      } else if (event.key === nextKey) {\n        event.preventDefault();\n        scrollNext();\n      }\n    },\n    [orientation, scrollNext, scrollPrev],\n  );\n\n  return (\n    <CarouselContext.Provider\n      value={{\n        carouselRef,\n        api,\n        opts,\n        plugins,\n        orientation,\n        setApi,\n        scrollPrev,\n        scrollNext,\n        canScrollPrev,\n        canScrollNext,\n      }}\n    >\n      <div\n        data-slot=\"carousel\"\n        role=\"region\"\n        aria-roledescription=\"carousel\"\n        onKeyDownCapture={handleKeyDown}\n        className={cn(\"relative min-w-0\", className)}\n        {...props}\n      >\n        {children}\n      </div>\n    </CarouselContext.Provider>\n  );\n}\n\nfunction CarouselContent({ className, ...props }: React.ComponentProps<\"div\">) {\n  const { carouselRef, orientation } = useCarousel();\n  return (\n    <div ref={carouselRef} data-slot=\"carousel-content\" className=\"overflow-hidden\">\n      <div\n        className={cn(\n          \"flex\",\n          orientation === \"horizontal\" ? \"-ml-4\" : \"-mt-4 flex-col\",\n          className,\n        )}\n        {...props}\n      />\n    </div>\n  );\n}\n\nfunction CarouselItem({ className, ...props }: React.ComponentProps<\"div\">) {\n  const { orientation } = useCarousel();\n  return (\n    <div\n      data-slot=\"carousel-item\"\n      role=\"group\"\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      {...props}\n    />\n  );\n}\n\ntype CarouselButtonProps = React.ComponentProps<typeof Button> & {\n  label?: string;\n};\n\nfunction CarouselPrevious({\n  className,\n  label = \"Previous slide\",\n  variant = \"soft\",\n  size = \"icon\",\n  ...props\n}: CarouselButtonProps) {\n  const { orientation, scrollPrev, canScrollPrev } = useCarousel();\n  return (\n    <Button\n      data-slot=\"carousel-previous\"\n      variant={variant}\n      size={size}\n      aria-label={label}\n      className={cn(\n        \"absolute z-10 size-10\",\n        orientation === \"horizontal\"\n          ? \"left-2 top-1/2 -translate-y-1/2\"\n          : \"top-2 left-1/2 -translate-x-1/2\",\n        className,\n      )}\n      disabled={!canScrollPrev}\n      onClick={scrollPrev}\n      {...props}\n    >\n      <svg aria-hidden=\"true\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.8\" strokeLinecap=\"round\" strokeLinejoin=\"round\"><path d={orientation === \"horizontal\" ? \"m14 6-6 6 6 6\" : \"m6 14 6-6 6 6\"} /></svg>\n    </Button>\n  );\n}\n\nfunction CarouselNext({\n  className,\n  label = \"Next slide\",\n  variant = \"soft\",\n  size = \"icon\",\n  ...props\n}: CarouselButtonProps) {\n  const { orientation, scrollNext, canScrollNext } = useCarousel();\n  return (\n    <Button\n      data-slot=\"carousel-next\"\n      variant={variant}\n      size={size}\n      aria-label={label}\n      className={cn(\n        \"absolute z-10 size-10\",\n        orientation === \"horizontal\"\n          ? \"right-2 top-1/2 -translate-y-1/2\"\n          : \"bottom-2 left-1/2 -translate-x-1/2\",\n        className,\n      )}\n      disabled={!canScrollNext}\n      onClick={scrollNext}\n      {...props}\n    >\n      <svg aria-hidden=\"true\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.8\" strokeLinecap=\"round\" strokeLinejoin=\"round\"><path d={orientation === \"horizontal\" ? \"m10 6 6 6-6 6\" : \"m6 10 6 6 6-6\"} /></svg>\n    </Button>\n  );\n}\n\nexport {\n  type CarouselApi,\n  Carousel,\n  CarouselContent,\n  CarouselItem,\n  CarouselNext,\n  CarouselPrevious,\n  useCarousel,\n};\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "navigation-disclosure"
  ],
  "type": "registry:ui"
}