{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "carousel",
  "title": "Carousel",
  "description": "Carousel from the Get IT Done design system.",
  "dependencies": [
    "embla-carousel-react@^8.5.1",
    "lucide-react@^0.379.0"
  ],
  "registryDependencies": [
    "https://zero-design.itdone.de/r/button.json",
    "https://zero-design.itdone.de/r/labels.json",
    "https://zero-design.itdone.de/r/labels-context.json",
    "https://zero-design.itdone.de/r/pagination-dot-group.json",
    "https://zero-design.itdone.de/r/utils.json",
    "https://zero-design.itdone.de/r/theme.json"
  ],
  "files": [
    {
      "path": "registry/ui/carousel.tsx",
      "content": "'use client';\n\nimport useEmblaCarousel, { type UseEmblaCarouselType } from 'embla-carousel-react';\nimport { ArrowLeft, ArrowRight, ChevronLeft, ChevronRight } from 'lucide-react';\nimport * as React from 'react';\n\nimport { cn } from '@/lib/utils';\nimport { Button } from '@/components/ui/button';\nimport { PaginationDotGroup, type PaginationDotGroupProps } from '@/components/ui/pagination-dot-group';\nimport { useLabels } from '@/components/ui/labels-context';\nimport type { LabelProp } from '@/components/ui/labels';\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 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 = React.createContext<CarouselContextProps | null>(null);\n\nfunction useCarousel() {\n  const context = React.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 = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement> & CarouselProps>(\n  ({ orientation = 'horizontal', opts, setApi, plugins, className, children, ...props }, ref) => {\n    const [carouselRef, api] = useEmblaCarousel(\n      {\n        ...opts,\n        axis: orientation === 'horizontal' ? 'x' : 'y',\n      },\n      plugins,\n    );\n    const [canScrollPrev, setCanScrollPrev] = React.useState(false);\n    const [canScrollNext, setCanScrollNext] = React.useState(false);\n\n    const onSelect = React.useCallback((api: CarouselApi) => {\n      if (!api) {\n        return;\n      }\n\n      setCanScrollPrev(api.canScrollPrev());\n      setCanScrollNext(api.canScrollNext());\n    }, []);\n\n    const scrollPrev = React.useCallback(() => {\n      api?.scrollPrev();\n    }, [api]);\n\n    const scrollNext = React.useCallback(() => {\n      api?.scrollNext();\n    }, [api]);\n\n    const handleKeyDown = React.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    React.useEffect(() => {\n      if (!api || !setApi) {\n        return;\n      }\n\n      setApi(api);\n    }, [api, setApi]);\n\n    React.useEffect(() => {\n      if (!api) {\n        return;\n      }\n\n      onSelect(api);\n      api.on('reInit', onSelect);\n      api.on('select', onSelect);\n\n      return () => {\n        api?.off('reInit', onSelect);\n        api?.off('select', onSelect);\n      };\n    }, [api, onSelect]);\n\n    return (\n      <CarouselContext.Provider\n        value={{\n          carouselRef,\n          api: api,\n          opts,\n          orientation: orientation || (opts?.axis === 'y' ? 'vertical' : 'horizontal'),\n          scrollPrev,\n          scrollNext,\n          canScrollPrev,\n          canScrollNext,\n        }}\n      >\n        <div\n          ref={ref}\n          onKeyDownCapture={handleKeyDown}\n          className={cn('relative w-full', className)}\n          role=\"region\"\n          aria-roledescription=\"carousel\"\n          {...props}\n        >\n          {children}\n        </div>\n      </CarouselContext.Provider>\n    );\n  },\n);\nCarousel.displayName = 'Carousel';\n\nconst CarouselContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n  ({ className, ...props }, ref) => {\n    const { carouselRef, orientation } = useCarousel();\n\n    return (\n      <div ref={carouselRef} className=\"overflow-hidden\">\n        <div\n          ref={ref}\n          className={cn(\n            'flex',\n            orientation === 'horizontal' ? '-ml-4' : '-mt-4 flex-col',\n            '[&_[data-id=\"carousel-item\"]]:first:pl-0',\n            className,\n          )}\n          {...props}\n        />\n      </div>\n    );\n  },\n);\nCarouselContent.displayName = 'CarouselContent';\n\nconst CarouselItem = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n  ({ className, ...props }, ref) => {\n    const { orientation } = useCarousel();\n\n    return (\n      <div\n        ref={ref}\n        role=\"group\"\n        aria-roledescription=\"slide\"\n        data-id=\"carousel-item\"\n        className={cn('min-w-0 shrink-0 grow-0 basis-full', orientation === 'horizontal' ? 'pl-4' : 'pt-4', className)}\n        {...props}\n      />\n    );\n  },\n);\nCarouselItem.displayName = 'CarouselItem';\n\n// Figma Carousel image arrows: round buttons laid over the image. `md` is 40px with a 20px chevron at 16px from the edge,\n// `lg` is 42px with a 24px chevron at 20px. Tone follows the Figma Theme: Primary, Dark or Light.\nexport type CarouselOverlayTone = 'primary' | 'dark' | 'light';\nexport interface CarouselOverlayProps {\n  // Opt in to the round arrow laid over the slide image. Without it the arrow keeps its outline look outside the slides.\n  overlayTone?: CarouselOverlayTone;\n  overlaySize?: 'md' | 'lg';\n}\n\nconst overlayToneClass: Record<CarouselOverlayTone, string> = {\n  primary: 'border-transparent bg-primary-700 text-static-white hover:bg-primary-800',\n  dark: 'border-transparent bg-static-black/90 text-static-white backdrop-blur-sm hover:bg-static-black',\n  light: 'border-transparent bg-background text-icon-default backdrop-blur-sm hover:bg-muted',\n};\n\nconst overlayArrowClass = (tone: CarouselOverlayTone, size: 'md' | 'lg', side: 'left' | 'right') =>\n  cn(\n    'absolute top-1/2 -translate-y-1/2 rounded-full p-0',\n    size === 'lg' ? 'h-control-lg w-control-lg' : 'h-control-md w-control-md',\n    side === 'left' ? (size === 'lg' ? 'left-5' : 'left-4') : size === 'lg' ? 'right-5' : 'right-4',\n    overlayToneClass[tone],\n  );\n\nconst CarouselPrevious = React.forwardRef<HTMLButtonElement, React.ComponentProps<typeof Button> & CarouselOverlayProps & { previousLabel?: string }>(\n  ({ className, variant = 'outline', size = 'icon', overlayTone, overlaySize = 'md', previousLabel, ...props }, ref) => {\n    const l = useLabels({ previousSlide: previousLabel });\n    const { orientation, scrollPrev, canScrollPrev } = useCarousel();\n    if (overlayTone) {\n      return (\n        <Button\n          type=\"button\"\n          ref={ref}\n          variant=\"ghost\"\n          size=\"no-size\"\n          className={cn(overlayArrowClass(overlayTone, overlaySize, 'left'), className)}\n          disabled={!canScrollPrev}\n          onClick={scrollPrev}\n          {...props}\n        >\n          <ChevronLeft aria-hidden=\"true\" className={overlaySize === 'lg' ? 'h-icon-lg w-icon-lg' : 'h-icon-md w-icon-md'} />\n          <span className=\"sr-only\">{l.previousSlide}</span>\n        </Button>\n      );\n    }\n\n    return (\n      <Button\n        type=\"button\"\n        ref={ref}\n        variant={variant}\n        size={size}\n        className={cn(\n          'absolute h-8 w-8 rounded-full',\n          orientation === 'horizontal'\n            ? '-left-12 top-1/2 -translate-y-1/2'\n            : '-top-12 left-1/2 -translate-x-1/2 rotate-90',\n          className,\n        )}\n        disabled={!canScrollPrev}\n        onClick={scrollPrev}\n        {...props}\n      >\n        <ArrowLeft className=\"h-4 w-4\" />\n        <span className=\"sr-only\">{l.previousSlide}</span>\n      </Button>\n    );\n  },\n);\nCarouselPrevious.displayName = 'CarouselPrevious';\n\nconst CarouselNext = React.forwardRef<HTMLButtonElement, React.ComponentProps<typeof Button> & CarouselOverlayProps & { nextLabel?: string }>(\n  ({ className, variant = 'outline', size = 'icon', overlayTone, overlaySize = 'md', nextLabel, ...props }, ref) => {\n    const l = useLabels({ nextSlide: nextLabel });\n    const { orientation, scrollNext, canScrollNext } = useCarousel();\n    if (overlayTone) {\n      return (\n        <Button\n          type=\"button\"\n          ref={ref}\n          variant=\"ghost\"\n          size=\"no-size\"\n          className={cn(overlayArrowClass(overlayTone, overlaySize, 'right'), className)}\n          disabled={!canScrollNext}\n          onClick={scrollNext}\n          {...props}\n        >\n          <ChevronRight aria-hidden=\"true\" className={overlaySize === 'lg' ? 'h-icon-lg w-icon-lg' : 'h-icon-md w-icon-md'} />\n          <span className=\"sr-only\">{l.nextSlide}</span>\n        </Button>\n      );\n    }\n\n    return (\n      <Button\n        type=\"button\"\n        ref={ref}\n        variant={variant}\n        size={size}\n        className={cn(\n          'absolute h-8 w-8 rounded-full',\n          orientation === 'horizontal'\n            ? '-right-12 top-1/2 -translate-y-1/2'\n            : '-bottom-12 left-1/2 -translate-x-1/2 rotate-90',\n          className,\n        )}\n        disabled={!canScrollNext}\n        onClick={scrollNext}\n        {...props}\n      >\n        <ArrowRight className=\"h-4 w-4\" />\n        <span className=\"sr-only\">{l.nextSlide}</span>\n      </Button>\n    );\n  },\n);\nCarouselNext.displayName = 'CarouselNext';\n\nexport type CarouselDotsProps = Omit<PaginationDotGroupProps, 'count' | 'current' | 'onSelect'> & {\n  // Accessible name of each dot from the 1 based slide and the count. Wins over the provider text.\n  slideLabel?: LabelProp<[number, number]>;\n  // Lays the group over the slide image, centred 16px from the bottom (Figma Carousel image).\n  overlay?: boolean;\n};\n\n// Dot or line indicators bound to the carousel (Figma Carousel image pagination). Each indicator scrolls to its slide.\nconst CarouselDots = React.forwardRef<HTMLDivElement, CarouselDotsProps>(({ getItemLabel, slideLabel, overlay = false, className, ...props }, ref) => {\n  const l = useLabels({ goToSlide: slideLabel });\n  const { api } = useCarousel();\n  const [count, setCount] = React.useState(0);\n  const [selected, setSelected] = React.useState(0);\n\n  React.useEffect(() => {\n    if (!api) return;\n    const update = () => {\n      setCount(api.scrollSnapList().length);\n      setSelected(api.selectedScrollSnap());\n    };\n    update();\n    api.on('reInit', update);\n    api.on('select', update);\n    return () => {\n      api.off('reInit', update);\n      api.off('select', update);\n    };\n  }, [api]);\n\n  if (count < 2) return null;\n  return (\n    <PaginationDotGroup\n      ref={ref}\n      count={count}\n      current={selected}\n      onSelect={(i) => api?.scrollTo(i)}\n      getItemLabel={getItemLabel ?? ((i, n) => l.goToSlide(i + 1, n))}\n      className={cn(overlay && 'absolute bottom-4 left-1/2 -translate-x-1/2', className)}\n      {...props}\n    />\n  );\n});\nCarouselDots.displayName = 'CarouselDots';\n\nexport { Carousel, CarouselContent, CarouselDots, CarouselItem, CarouselNext, CarouselPrevious, useCarousel, type CarouselApi };\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}