{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"reel","type":"registry:ui","title":"reel","description":"A composable Reel component that looks like Instagram Stories - a full-height, 9:16 aspect ratio container with video playback and progress indicators.","author":"Hayden Bleasel <hello@haydenbleasel.com>","dependencies":["@radix-ui/react-use-controllable-state","lucide-react","motion"],"devDependencies":[],"registryDependencies":["button","progress"],"files":[{"type":"registry:ui","path":"index.tsx","content":"\"use client\";\n\nimport { useControllableState } from \"@radix-ui/react-use-controllable-state\";\nimport {\n  ChevronLeft,\n  ChevronRight,\n  Pause,\n  Play,\n  Volume2,\n  VolumeX,\n} from \"lucide-react\";\nimport { AnimatePresence, motion } from \"motion/react\";\nimport type {\n  ComponentProps,\n  HTMLAttributes,\n  MouseEventHandler,\n  ReactNode,\n  VideoHTMLAttributes,\n} from \"react\";\nimport {\n  createContext,\n  useCallback,\n  useContext,\n  useEffect,\n  useRef,\n  useState,\n} from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport { Progress } from \"@/components/ui/progress\";\nimport { cn } from \"@/lib/utils\";\n\n// Explicit type for reel items\nexport type ReelItem = {\n  id: string | number;\n  type: \"video\" | \"image\";\n  src: string;\n  duration: number; // Duration in seconds for both video and image\n  alt?: string;\n  title?: string;\n  description?: string;\n};\n\ntype ReelContextType = {\n  currentIndex: number;\n  setCurrentIndex: (index: number) => void;\n  isPlaying: boolean;\n  setIsPlaying: (playing: boolean) => void;\n  isMuted: boolean;\n  setIsMuted: (muted: boolean) => void;\n  progress: number;\n  setProgress: (progress: number) => void;\n  duration: number;\n  setDuration: (duration: number) => void;\n  data: ReelItem[];\n  currentItem: ReelItem;\n  isNavigating: boolean;\n  setIsNavigating: (navigating: boolean) => void;\n  isTransitioning: boolean;\n  setIsTransitioning: (transitioning: boolean) => void;\n};\n\nconst ReelContext = createContext<ReelContextType | undefined>(undefined);\n\nconst useReelContext = () => {\n  const context = useContext(ReelContext);\n  if (!context) {\n    throw new Error(\"useReelContext must be used within a Reel\");\n  }\n  return context;\n};\n\nexport type ReelProps = HTMLAttributes<HTMLDivElement> & {\n  data: ReelItem[];\n  defaultIndex?: number;\n  index?: number;\n  onIndexChange?: (index: number) => void;\n  defaultPlaying?: boolean;\n  playing?: boolean;\n  onPlayingChange?: (playing: boolean) => void;\n  defaultMuted?: boolean;\n  muted?: boolean;\n  onMutedChange?: (muted: boolean) => void;\n  autoPlay?: boolean;\n};\n\nexport const Reel = ({\n  className,\n  data,\n  defaultIndex = 0,\n  index: controlledIndex,\n  onIndexChange: controlledOnIndexChange,\n  defaultPlaying,\n  playing: controlledPlaying,\n  onPlayingChange: controlledOnPlayingChange,\n  defaultMuted = true,\n  muted: controlledMuted,\n  onMutedChange: controlledOnMutedChange,\n  autoPlay = true,\n  ...props\n}: ReelProps) => {\n  const [currentIndex, setCurrentIndexState] = useControllableState({\n    defaultProp: defaultIndex,\n    prop: controlledIndex,\n    onChange: controlledOnIndexChange,\n  });\n\n  const [isPlaying, setIsPlaying] = useControllableState({\n    defaultProp: defaultPlaying ?? autoPlay,\n    prop: controlledPlaying,\n    onChange: controlledOnPlayingChange,\n  });\n\n  const [isMuted, setIsMuted] = useControllableState({\n    defaultProp: defaultMuted,\n    prop: controlledMuted,\n    onChange: controlledOnMutedChange,\n  });\n\n  const [progress, setProgress] = useState(0);\n  const [duration, setDuration] = useState(0);\n  const [isNavigating, setIsNavigating] = useState(false);\n  const [isTransitioning, setIsTransitioning] = useState(false);\n\n  const setCurrentIndex = useCallback(\n    (index: number) => {\n      setIsTransitioning(true);\n      setProgress(0); // Reset progress immediately to prevent showing 100% during transition\n      setCurrentIndexState(index);\n    },\n    [setCurrentIndexState]\n  );\n\n  const currentItem = data[currentIndex];\n\n  return (\n    <ReelContext.Provider\n      value={{\n        currentIndex,\n        setCurrentIndex,\n        isPlaying,\n        setIsPlaying,\n        isMuted,\n        setIsMuted,\n        progress,\n        setProgress,\n        duration,\n        setDuration,\n        data,\n        currentItem,\n        isNavigating,\n        setIsNavigating,\n        isTransitioning,\n        setIsTransitioning,\n      }}\n    >\n      <div\n        className={cn(\n          \"relative isolate h-full w-auto overflow-hidden bg-black\",\n          \"aspect-[9/16]\",\n          className\n        )}\n        {...props}\n      />\n    </ReelContext.Provider>\n  );\n};\n\nexport type ReelContentProps = Omit<\n  HTMLAttributes<HTMLDivElement>,\n  \"children\"\n> & {\n  children: (item: ReelItem, index: number) => ReactNode;\n};\n\nexport const ReelContent = ({\n  className,\n  children,\n  ...props\n}: ReelContentProps) => {\n  const { currentIndex, currentItem, setIsTransitioning } = useReelContext();\n\n  const renderContent = () => {\n    if (typeof children === \"function\") {\n      return children(currentItem, currentIndex);\n    }\n    const childrenArray = Array.isArray(children) ? children : [children];\n    return childrenArray[currentIndex];\n  };\n\n  return (\n    <div\n      className={cn(\"relative size-full\", className)}\n      data-reel-content\n      {...props}\n    >\n      <AnimatePresence mode=\"wait\">\n        <motion.div\n          animate={{ opacity: 1 }}\n          className=\"absolute inset-0\"\n          exit={{ opacity: 0 }}\n          initial={{ opacity: 0 }}\n          key={currentIndex}\n          onAnimationComplete={() => {\n            // Mark transition as complete when fade-in completes\n            setIsTransitioning(false);\n          }}\n          transition={{ duration: 0.3 }}\n        >\n          {renderContent()}\n        </motion.div>\n      </AnimatePresence>\n    </div>\n  );\n};\n\nexport type ReelItemProps = HTMLAttributes<HTMLDivElement>;\n\nexport const ReelItem = ({ className, ...props }: ReelItemProps) => (\n  <div\n    className={cn(\"relative size-full overflow-hidden\", className)}\n    data-reel-item\n    {...props}\n  />\n);\n\nexport type ReelVideoProps = VideoHTMLAttributes<HTMLVideoElement>;\n\nconst MS_TO_SECONDS = 1000;\nconst PERCENTAGE = 100;\n\nexport const ReelVideo = ({ className, ...props }: ReelVideoProps) => {\n  const videoRef = useRef<HTMLVideoElement>(null);\n  const {\n    isPlaying,\n    isMuted,\n    setDuration,\n    setProgress,\n    currentIndex,\n    setCurrentIndex,\n    data,\n    progress,\n    currentItem,\n    isTransitioning,\n  } = useReelContext();\n  const animationFrameRef = useRef<number | undefined>(undefined);\n  const startTimeRef = useRef<number | undefined>(undefined);\n  const pausedProgressRef = useRef<number>(0);\n  const duration = currentItem.duration;\n\n  // Set duration when component mounts or currentIndex changes\n  useEffect(() => {\n    setDuration(duration);\n    // Don't reset progress here anymore - it's handled in ReelContent after transition\n    if (!isTransitioning) {\n      pausedProgressRef.current = 0;\n    }\n  }, [duration, setDuration, isTransitioning]);\n\n  // Handle muting\n  useEffect(() => {\n    const video = videoRef.current;\n    if (!video) {\n      return;\n    }\n    video.muted = isMuted;\n  }, [isMuted]);\n\n  // Store progress when pausing\n  useEffect(() => {\n    if (!isPlaying) {\n      pausedProgressRef.current = progress;\n    }\n  }, [isPlaying, progress]);\n\n  // Handle play/pause with duration-based progress\n  useEffect(() => {\n    const video = videoRef.current;\n    if (!video) {\n      return;\n    }\n\n    if (isPlaying && !isTransitioning) {\n      video.play().catch(() => {\n        // Ignore autoplay errors\n      });\n\n      // Start progress animation only when not transitioning\n      const elapsedTime = (pausedProgressRef.current * duration) / PERCENTAGE;\n      startTimeRef.current = performance.now() - elapsedTime * MS_TO_SECONDS;\n\n      const updateProgress = (currentTime: number) => {\n        const elapsed =\n          (currentTime - (startTimeRef.current || 0)) / MS_TO_SECONDS;\n        const newProgress = (elapsed / duration) * PERCENTAGE;\n\n        if (newProgress >= PERCENTAGE) {\n          const totalItems = data?.length || 0;\n          if (currentIndex < totalItems - 1) {\n            setCurrentIndex(currentIndex + 1);\n          } else {\n            setCurrentIndex(0);\n          }\n        } else {\n          setProgress(newProgress);\n          animationFrameRef.current = requestAnimationFrame(updateProgress);\n        }\n      };\n\n      animationFrameRef.current = requestAnimationFrame(updateProgress);\n    } else if (!isTransitioning) {\n      video.pause();\n    }\n\n    return () => {\n      if (animationFrameRef.current) {\n        cancelAnimationFrame(animationFrameRef.current);\n      }\n    };\n  }, [\n    isPlaying,\n    duration,\n    currentIndex,\n    setProgress,\n    setCurrentIndex,\n    data,\n    isTransitioning,\n  ]);\n\n  // biome-ignore lint/correctness/useExhaustiveDependencies: Reset video when index changes\n  useEffect(() => {\n    const video = videoRef.current;\n    if (video) {\n      video.currentTime = 0;\n    }\n  }, [currentIndex]);\n\n  return (\n    <video\n      className={cn(\"absolute inset-0 size-full object-cover\", className)}\n      loop\n      muted={isMuted}\n      playsInline\n      ref={videoRef}\n      {...props}\n    />\n  );\n};\n\nexport type ReelImageProps = Omit<ComponentProps<\"img\">, \"alt\"> & {\n  alt: string;\n  duration?: number;\n  width?: number | string;\n  height?: number | string;\n};\n\nconst DEFAULT_IMAGE_DURATION = 5;\n\nexport const ReelImage = ({\n  className,\n  alt,\n  duration = DEFAULT_IMAGE_DURATION,\n  width,\n  height,\n  ...props\n}: ReelImageProps) => {\n  const {\n    isPlaying,\n    setDuration,\n    setProgress,\n    currentIndex,\n    setCurrentIndex,\n    data,\n    progress,\n    isTransitioning,\n  } = useReelContext();\n  const animationFrameRef = useRef<number | undefined>(undefined);\n  const startTimeRef = useRef<number | undefined>(undefined);\n  const pausedProgressRef = useRef<number>(0);\n\n  // biome-ignore lint/correctness/useExhaustiveDependencies: Reset progress when index changes\n  useEffect(() => {\n    setDuration(duration);\n    // Don't reset progress here anymore - it's handled in ReelContent after transition\n    if (!isTransitioning) {\n      pausedProgressRef.current = 0;\n    }\n  }, [currentIndex, duration, setDuration, isTransitioning]);\n\n  // Handle play/pause\n  useEffect(() => {\n    if (isPlaying && !isTransitioning) {\n      const elapsedTime = (pausedProgressRef.current * duration) / PERCENTAGE;\n      startTimeRef.current = performance.now() - elapsedTime * MS_TO_SECONDS;\n\n      const updateProgress = (currentTime: number) => {\n        const elapsed =\n          (currentTime - (startTimeRef.current || 0)) / MS_TO_SECONDS;\n        const newProgress = (elapsed / duration) * PERCENTAGE;\n\n        if (newProgress >= PERCENTAGE) {\n          const totalItems = data?.length || 0;\n\n          if (currentIndex < totalItems - 1) {\n            setCurrentIndex(currentIndex + 1);\n          } else {\n            setCurrentIndex(0);\n          }\n        } else {\n          setProgress(newProgress);\n          pausedProgressRef.current = newProgress;\n          animationFrameRef.current = requestAnimationFrame(updateProgress);\n        }\n      };\n\n      animationFrameRef.current = requestAnimationFrame(updateProgress);\n    } else if (!isTransitioning) {\n      pausedProgressRef.current = progress;\n    }\n\n    return () => {\n      if (animationFrameRef.current) {\n        cancelAnimationFrame(animationFrameRef.current);\n      }\n    };\n  }, [\n    isPlaying,\n    duration,\n    currentIndex,\n    setProgress,\n    setCurrentIndex,\n    data,\n    progress,\n    isTransitioning,\n  ]);\n\n  return (\n    // biome-ignore lint/performance/noImgElement: \"Reel is framework-agnostic\"\n    <img\n      alt={alt}\n      className={cn(\"absolute inset-0 size-full object-cover\", className)}\n      height={height}\n      width={width}\n      {...props}\n    />\n  );\n};\n\nexport type ReelProgressProps = HTMLAttributes<HTMLDivElement> & {\n  children?: (\n    item: ReelItem,\n    index: number,\n    isActive: boolean,\n    progress: number\n  ) => ReactNode;\n};\n\nexport const ReelProgress = ({\n  className,\n  children,\n  ...props\n}: ReelProgressProps) => {\n  const { progress, currentIndex, data } = useReelContext();\n  const FULL_PROGRESS = 100;\n\n  const calculateProgress = (index: number) => {\n    if (index < currentIndex) {\n      return FULL_PROGRESS;\n    }\n    if (index === currentIndex) {\n      return progress;\n    }\n\n    return 0;\n  };\n\n  if (typeof children === \"function\") {\n    return (\n      <div\n        className={cn(\n          \"absolute top-0 right-0 left-0 z-40 flex gap-1 p-2\",\n          className\n        )}\n        {...props}\n      >\n        {data.map((item, index) => (\n          <div className=\"relative flex-1\" key={`${item.id}-progress`}>\n            {children(\n              item,\n              index,\n              index === currentIndex,\n              calculateProgress(index)\n            )}\n          </div>\n        ))}\n      </div>\n    );\n  }\n\n  return (\n    <div\n      className={cn(\n        \"absolute top-0 right-0 left-0 z-40 flex gap-1 p-2\",\n        className\n      )}\n      {...props}\n    >\n      {data.map((item, index) => (\n        <Progress\n          className=\"h-0.5 flex-1 bg-white/30 [&>div]:bg-white [&>div]:transition-none\"\n          key={`${item.id}-progress`}\n          value={calculateProgress(index)}\n        />\n      ))}\n    </div>\n  );\n};\n\nexport type ReelControlsProps = HTMLAttributes<HTMLDivElement>;\n\nexport const ReelControls = ({ className, ...props }: ReelControlsProps) => (\n  <div\n    className={cn(\n      \"absolute right-0 bottom-0 left-0 z-20 flex items-center justify-between p-4\",\n      \"bg-gradient-to-t from-black/60 to-transparent\",\n      className\n    )}\n    {...props}\n  />\n);\n\nexport type ReelPreviousButtonProps = ComponentProps<typeof Button>;\n\nexport const ReelPreviousButton = ({\n  className,\n  children,\n  ...props\n}: ReelPreviousButtonProps) => {\n  const { currentIndex, setCurrentIndex, setIsNavigating } = useReelContext();\n  const NAVIGATION_RESET_DELAY = 50;\n\n  const handlePrevious = () => {\n    if (currentIndex > 0) {\n      setIsNavigating(true);\n      setCurrentIndex(currentIndex - 1);\n      setTimeout(() => setIsNavigating(false), NAVIGATION_RESET_DELAY);\n    }\n  };\n\n  return (\n    <Button\n      aria-label=\"Previous\"\n      className={cn(\n        \"rounded-full text-white hover:bg-white/10 hover:text-white\",\n        className\n      )}\n      disabled={currentIndex === 0}\n      onClick={handlePrevious}\n      size=\"icon\"\n      type=\"button\"\n      variant=\"ghost\"\n      {...props}\n    >\n      {children || <ChevronLeft className=\"size-4\" />}\n    </Button>\n  );\n};\n\nexport type ReelNextButtonProps = ComponentProps<typeof Button>;\n\nexport const ReelNextButton = ({\n  className,\n  children,\n  ...props\n}: ReelNextButtonProps) => {\n  const { currentIndex, setCurrentIndex, data, setIsNavigating } =\n    useReelContext();\n  const totalItems = data?.length || 0;\n  const NAVIGATION_RESET_DELAY = 50;\n\n  const handleNext = () => {\n    if (currentIndex < totalItems - 1) {\n      setIsNavigating(true);\n      setCurrentIndex(currentIndex + 1);\n      setTimeout(() => setIsNavigating(false), NAVIGATION_RESET_DELAY);\n    }\n  };\n\n  return (\n    <Button\n      aria-label=\"Next\"\n      className={cn(\n        \"rounded-full text-white hover:bg-white/10 hover:text-white\",\n        className\n      )}\n      disabled={currentIndex === totalItems - 1}\n      onClick={handleNext}\n      size=\"icon\"\n      type=\"button\"\n      variant=\"ghost\"\n      {...props}\n    >\n      {children || <ChevronRight className=\"size-4\" />}\n    </Button>\n  );\n};\n\nexport type ReelPlayButtonProps = ComponentProps<typeof Button>;\n\nexport const ReelPlayButton = ({\n  className,\n  children,\n  ...props\n}: ReelPlayButtonProps) => {\n  const { isPlaying, setIsPlaying } = useReelContext();\n\n  return (\n    <Button\n      aria-label={isPlaying ? \"Pause\" : \"Play\"}\n      className={cn(\n        \"rounded-full text-white hover:bg-white/10 hover:text-white\",\n        className\n      )}\n      onClick={() => setIsPlaying(!isPlaying)}\n      size=\"icon\"\n      variant=\"ghost\"\n      {...props}\n    >\n      {children ||\n        (isPlaying ? (\n          <Pause className=\"size-4\" />\n        ) : (\n          <Play className=\"size-4\" />\n        ))}\n    </Button>\n  );\n};\n\nexport type ReelMuteButtonProps = ComponentProps<typeof Button>;\n\nexport const ReelMuteButton = ({\n  className,\n  children,\n  ...props\n}: ReelMuteButtonProps) => {\n  const { isMuted, setIsMuted } = useReelContext();\n\n  return (\n    <Button\n      aria-label={isMuted ? \"Unmute\" : \"Mute\"}\n      className={cn(\n        \"rounded-full text-white hover:bg-white/10 hover:text-white\",\n        className\n      )}\n      onClick={() => setIsMuted(!isMuted)}\n      size=\"icon\"\n      variant=\"ghost\"\n      {...props}\n    >\n      {children ||\n        (isMuted ? (\n          <VolumeX className=\"size-4\" />\n        ) : (\n          <Volume2 className=\"size-4\" />\n        ))}\n    </Button>\n  );\n};\n\nexport type ReelNavigationProps = HTMLAttributes<HTMLButtonElement>;\n\nexport const ReelNavigation = ({\n  className,\n  ...props\n}: ReelNavigationProps) => {\n  const { setCurrentIndex, currentIndex, data, setIsNavigating } =\n    useReelContext();\n  const totalItems = data?.length || 0;\n  const NAVIGATION_RESET_DELAY = 50;\n  const HALF_WIDTH_DIVISOR = 2;\n\n  const handleClick: MouseEventHandler<HTMLButtonElement> = (e) => {\n    const rect = e.currentTarget.getBoundingClientRect();\n    const x = e.clientX - rect.left;\n    const width = rect.width;\n\n    if (x < width / HALF_WIDTH_DIVISOR) {\n      if (currentIndex > 0) {\n        setIsNavigating(true);\n        setCurrentIndex(currentIndex - 1);\n        setTimeout(() => setIsNavigating(false), NAVIGATION_RESET_DELAY);\n      }\n    } else if (currentIndex < totalItems - 1) {\n      setIsNavigating(true);\n      setCurrentIndex(currentIndex + 1);\n      setTimeout(() => setIsNavigating(false), NAVIGATION_RESET_DELAY);\n    }\n  };\n\n  return (\n    <button\n      className={cn(\"absolute inset-0 z-10 flex\", className)}\n      onClick={handleClick}\n      type=\"button\"\n      {...props}\n    >\n      <div className=\"flex-1 cursor-pointer\" />\n      <div className=\"flex-1 cursor-pointer\" />\n    </button>\n  );\n};\n\nexport type ReelOverlayProps = HTMLAttributes<HTMLDivElement>;\n\nexport const ReelOverlay = ({ className, ...props }: ReelOverlayProps) => (\n  <div\n    className={cn(\"pointer-events-none absolute inset-0 z-30\", className)}\n    {...props}\n  />\n);\n\nexport type ReelHeaderProps = HTMLAttributes<HTMLDivElement>;\n\nexport const ReelHeader = ({ className, ...props }: ReelHeaderProps) => (\n  <div\n    className={cn(\n      \"absolute top-0 right-0 left-0 z-20 p-4 pt-6\",\n      \"bg-gradient-to-b from-black/60 to-transparent\",\n      className\n    )}\n    {...props}\n  />\n);\n\nexport type ReelFooterProps = HTMLAttributes<HTMLDivElement>;\n\nexport const ReelFooter = ({ className, ...props }: ReelFooterProps) => (\n  <div\n    className={cn(\n      \"absolute right-0 bottom-0 left-0 z-20 p-4\",\n      \"bg-gradient-to-t from-black/60 to-transparent\",\n      className\n    )}\n    {...props}\n  />\n);\n","target":"components/kibo-ui/reel/index.tsx"},{"type":"registry:ui","path":"reel-controlled.tsx","content":"\"use client\";\n\nimport { useState } from \"react\";\nimport {\n  Reel,\n  ReelContent,\n  ReelControls,\n  ReelFooter,\n  ReelHeader,\n  ReelImage,\n  type ReelItem,\n  ReelNavigation,\n  ReelProgress,\n  ReelVideo,\n} from \"./index\";\n\n// Example data for the reel\nconst reelItems: ReelItem[] = [\n  {\n    id: \"video-1\",\n    type: \"video\",\n    src: \"/videos/sample-video-1.mp4\",\n    duration: 10, // 10 seconds\n    title: \"First Video\",\n    description: \"This is the first video in the reel\",\n  },\n  {\n    id: \"image-1\",\n    type: \"image\",\n    src: \"/images/sample-landscape.jpg\",\n    duration: 5, // 5 seconds\n    alt: \"Beautiful landscape\",\n    title: \"Nature Photo\",\n    description: \"A stunning landscape photograph\",\n  },\n  {\n    id: \"video-2\",\n    type: \"video\",\n    src: \"/videos/sample-video-2.mp4\",\n    duration: 15, // 15 seconds\n    title: \"Second Video\",\n    description: \"Another exciting video\",\n  },\n  {\n    id: \"image-2\",\n    type: \"image\",\n    src: \"/images/sample-cityscape.jpg\",\n    duration: 7, // 7 seconds\n    alt: \"City skyline\",\n    title: \"Urban Photography\",\n    description: \"Modern city architecture\",\n  },\n];\n\nexport function ReelControlledExample() {\n  // Controlled state for all reel properties\n  const [currentIndex, setCurrentIndex] = useState(0);\n  const [isPlaying, setIsPlaying] = useState(true);\n  const [isMuted, setIsMuted] = useState(true);\n\n  // External controls\n  const handleJumpToItem = (index: number) => {\n    setCurrentIndex(index);\n  };\n\n  const handlePlayPause = () => {\n    setIsPlaying(!isPlaying);\n  };\n\n  const handleToggleMute = () => {\n    setIsMuted(!isMuted);\n  };\n\n  const handleNext = () => {\n    if (currentIndex < reelItems.length - 1) {\n      setCurrentIndex(currentIndex + 1);\n    } else {\n      setCurrentIndex(0); // Loop back to start\n    }\n  };\n\n  const handlePrevious = () => {\n    if (currentIndex > 0) {\n      setCurrentIndex(currentIndex - 1);\n    } else {\n      setCurrentIndex(reelItems.length - 1); // Loop to end\n    }\n  };\n\n  const currentItem = reelItems[currentIndex];\n\n  return (\n    <div className=\"flex flex-col gap-6\">\n      {/* External Controls */}\n      <div className=\"flex flex-col gap-4 rounded-lg border p-4\">\n        <h3 className=\"font-semibold text-lg\">External Controls</h3>\n\n        <div className=\"flex gap-2\">\n          <button\n            className=\"rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600\"\n            onClick={handlePrevious}\n            type=\"button\"\n          >\n            Previous\n          </button>\n          <button\n            className=\"rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600\"\n            onClick={handlePlayPause}\n            type=\"button\"\n          >\n            {isPlaying ? \"Pause\" : \"Play\"}\n          </button>\n          <button\n            className=\"rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600\"\n            onClick={handleNext}\n            type=\"button\"\n          >\n            Next\n          </button>\n          <button\n            className=\"rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600\"\n            onClick={handleToggleMute}\n            type=\"button\"\n          >\n            {isMuted ? \"Unmute\" : \"Mute\"}\n          </button>\n        </div>\n\n        <div className=\"flex gap-2\">\n          {reelItems.map((item, index) => (\n            <button\n              className={`rounded px-3 py-1 ${\n                index === currentIndex\n                  ? \"bg-blue-500 text-white\"\n                  : \"bg-gray-200 hover:bg-gray-300\"\n              }`}\n              key={item.id}\n              onClick={() => handleJumpToItem(index)}\n              type=\"button\"\n            >\n              {index + 1}\n            </button>\n          ))}\n        </div>\n\n        <div className=\"text-gray-600 text-sm\">\n          <p>Current Index: {currentIndex}</p>\n          <p>Playing: {isPlaying ? \"Yes\" : \"No\"}</p>\n          <p>Muted: {isMuted ? \"Yes\" : \"No\"}</p>\n          <p>Current Item: {currentItem.title}</p>\n        </div>\n      </div>\n\n      {/* Controlled Reel Component */}\n      <div className=\"mx-auto w-full max-w-sm\">\n        <Reel\n          data={reelItems}\n          index={currentIndex}\n          muted={isMuted}\n          onIndexChange={setCurrentIndex}\n          onMutedChange={setIsMuted}\n          onPlayingChange={setIsPlaying}\n          playing={isPlaying}\n        >\n          <ReelProgress />\n\n          <ReelHeader>\n            <div className=\"text-white\">\n              <h2 className=\"font-bold text-xl\">Controlled Reel</h2>\n              <p className=\"text-sm opacity-80\">\n                Item {currentIndex + 1} of {reelItems.length}\n              </p>\n            </div>\n          </ReelHeader>\n\n          <ReelContent>\n            {(item) => (\n              <>\n                {item.type === \"video\" ? (\n                  <ReelVideo src={item.src} />\n                ) : (\n                  <ReelImage\n                    alt={item.alt || \"\"}\n                    duration={item.duration}\n                    src={item.src}\n                  />\n                )}\n              </>\n            )}\n          </ReelContent>\n\n          <ReelNavigation />\n\n          <ReelFooter>\n            <div className=\"text-white\">\n              <h3 className=\"font-semibold text-lg\">{currentItem.title}</h3>\n              {currentItem.description && (\n                <p className=\"mt-1 text-sm opacity-90\">\n                  {currentItem.description}\n                </p>\n              )}\n            </div>\n          </ReelFooter>\n\n          <ReelControls />\n        </Reel>\n      </div>\n\n      {/* Advanced Controlled Example with Custom UI */}\n      <div className=\"flex flex-col gap-4 rounded-lg border p-4\">\n        <h3 className=\"font-semibold text-lg\">Advanced Controlled Example</h3>\n        <p className=\"text-gray-600 text-sm\">\n          This example shows how you can build a completely custom UI while\n          controlling the Reel state from outside.\n        </p>\n\n        <div className=\"flex gap-4\">\n          <div className=\"flex-1\">\n            <Reel\n              className=\"aspect-[9/16]\"\n              data={reelItems}\n              index={currentIndex}\n              muted={isMuted}\n              onIndexChange={setCurrentIndex}\n              onMutedChange={setIsMuted}\n              onPlayingChange={setIsPlaying}\n              playing={isPlaying}\n            >\n              <ReelContent>\n                {(item) => (\n                  <>\n                    {item.type === \"video\" ? (\n                      <ReelVideo src={item.src} />\n                    ) : (\n                      <ReelImage\n                        alt={item.alt || \"\"}\n                        duration={item.duration}\n                        src={item.src}\n                      />\n                    )}\n                  </>\n                )}\n              </ReelContent>\n            </Reel>\n          </div>\n\n          <div className=\"flex flex-1 flex-col justify-center gap-4\">\n            <div>\n              <h4 className=\"font-semibold\">{currentItem.title}</h4>\n              <p className=\"text-gray-600 text-sm\">{currentItem.description}</p>\n            </div>\n\n            <div className=\"space-y-2\">\n              <div className=\"flex items-center gap-2\">\n                <label className=\"font-medium text-sm\" htmlFor=\"timeline\">\n                  Timeline:\n                </label>\n                <input\n                  className=\"flex-1\"\n                  id=\"timeline\"\n                  max={reelItems.length - 1}\n                  min={0}\n                  onChange={(e) => setCurrentIndex(Number(e.target.value))}\n                  type=\"range\"\n                  value={currentIndex}\n                />\n              </div>\n\n              <div className=\"flex items-center gap-2\">\n                <input\n                  checked={isPlaying}\n                  id=\"autoplay\"\n                  onChange={(e) => setIsPlaying(e.target.checked)}\n                  type=\"checkbox\"\n                />\n                <label className=\"text-sm\" htmlFor=\"autoplay\">\n                  Auto-play\n                </label>\n              </div>\n\n              <div className=\"flex items-center gap-2\">\n                <input\n                  checked={isMuted}\n                  id=\"muted\"\n                  onChange={(e) => setIsMuted(e.target.checked)}\n                  type=\"checkbox\"\n                />\n                <label className=\"text-sm\" htmlFor=\"muted\">\n                  Muted\n                </label>\n              </div>\n            </div>\n\n            <div className=\"grid grid-cols-2 gap-2\">\n              {reelItems.map((item, index) => (\n                <button\n                  className={`rounded p-2 text-xs ${\n                    index === currentIndex\n                      ? \"bg-blue-500 text-white\"\n                      : \"bg-gray-100 hover:bg-gray-200\"\n                  }`}\n                  key={item.id}\n                  onClick={() => setCurrentIndex(index)}\n                  type=\"button\"\n                >\n                  {item.title}\n                </button>\n              ))}\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n}\n\n// Example of using the controlled Reel in a parent component\nexport default function ControlledReelPage() {\n  return (\n    <div className=\"container mx-auto py-8\">\n      <h1 className=\"mb-8 text-center font-bold text-3xl\">\n        Controlled Reel Examples\n      </h1>\n      <ReelControlledExample />\n    </div>\n  );\n}\n","target":"components/kibo-ui/reel/reel-controlled.tsx"}],"css":{}}