{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "wave-zoom",
  "title": "Wave Zoom",
  "description": "wave player with mouse-wheel zoom via wavesurfer.js ZoomPlugin.",
  "dependencies": [
    "wavesurfer.js",
    "lucide-react"
  ],
  "registryDependencies": [
    "@waves-cn/wave-cn",
    "button",
    "switch",
    "label"
  ],
  "files": [
    {
      "path": "src/registry/components/wave-zoom.tsx",
      "content": "\"use client\";\n\nimport { useState, useEffect, useMemo, useId, type CSSProperties } from \"react\";\nimport ZoomPlugin from \"wavesurfer.js/dist/plugins/zoom.esm.js\";\nimport { Button } from \"@/components/ui/button\";\nimport { Switch } from \"@/components/ui/switch\";\nimport { Label } from \"@/components/ui/label\";\nimport { Play, Pause, SkipBack, SkipForward } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\nimport WavesurferPlayer, { useWavePlayer } from \"@/lib/wave-cn\";\n\n/**\n * Props for the WaveZoom component\n */\nexport type WaveZoomProps = {\n  /** Audio source URL */\n  src?: string;\n  /** @deprecated use `src` */\n  url?: string;\n  /** Optional title shown above the waveform */\n  title?: string;\n  /** Wave bar color. Accepts any CSS value including var(--*) tokens @default \"var(--muted-foreground)\" */\n  waveColor?: string;\n  /** Progress bar color. Accepts any CSS value including var(--*) tokens @default \"var(--primary)\" */\n  progressColor?: string;\n  /** Wave canvas height in px @default 64 */\n  waveHeight?: number;\n  /** Bar width in px @default 3 */\n  barWidth?: number;\n  /** Gap between bars in px @default 2 */\n  barGap?: number;\n  /** Bar border radius in px @default 2 */\n  barRadius?: number;\n  /** Zoom magnification per scroll step @default 0.5 */\n  zoomScale?: number;\n  /** Maximum zoom level in px/s @default 1000 */\n  maxZoom?: number;\n  /** Initial zoom level in px/s @default 100 */\n  defaultZoom?: number;\n  /** Seconds to skip on forward/backward @default 5 */\n  skipSeconds?: number;\n  /** Called when playback starts */\n  onPlay?: () => void;\n  /** Called when playback pauses */\n  onPause?: () => void;\n  /** Called when playback finishes */\n  onFinish?: () => void;\n  /** Called with current time on every audio process tick */\n  onTimeUpdate?: (currentTime: number, duration: number) => void;\n  /** Root element class */\n  className?: string;\n  style?: CSSProperties;\n};\n\n/**\n * Wave player with mouse-wheel zoom via ZoomPlugin\n */\nexport function WaveZoom({\n  src,\n  url,\n  title,\n  waveColor,\n  progressColor,\n  waveHeight,\n  barWidth,\n  barGap,\n  barRadius,\n  zoomScale = 0.5,\n  maxZoom = 1000,\n  defaultZoom = 100,\n  skipSeconds = 5,\n  onPlay,\n  onPause,\n  onFinish,\n  onTimeUpdate,\n  className,\n  style,\n}: WaveZoomProps) {\n  const source = src ?? url;\n\n  // Apply the initial zoom once on ready rather than as a `minPxPerSec` prop:\n  // the core re-applies every option prop through `setOptions()` when any of\n  // them changes, which would reset the user's wheel-zoom on each toggle.\n  const player = useWavePlayer({\n    onReady: (ws) => ws.zoom(defaultZoom),\n    onPlay,\n    onPause,\n    onFinish,\n    onTimeUpdate,\n  });\n  const switchId = useId();\n\n  const [currentZoom, setCurrentZoom] = useState(defaultZoom);\n  const [autoScroll, setAutoScroll] = useState(true);\n  const [fillParent, setFillParent] = useState(true);\n  const [autoCenter, setAutoCenter] = useState(true);\n\n  const plugins = useMemo(\n    () =>\n      typeof document === \"undefined\"\n        ? []\n        : [ZoomPlugin.create({ scale: zoomScale, maxZoom })],\n    [zoomScale, maxZoom],\n  );\n\n  // Mirror the live zoom level; `ws.on` returns its own unsubscribe.\n  useEffect(() => {\n    const ws = player.wavesurfer.current;\n    if (!player.isReady || !ws) return;\n    return ws.on(\"zoom\", (minPxPerSec) => {\n      setCurrentZoom(Math.round(minPxPerSec));\n    });\n  }, [player.isReady, player.wavesurfer]);\n\n  const forward = () => player.wavesurfer.current?.skip(skipSeconds);\n  const backward = () => player.wavesurfer.current?.skip(-skipSeconds);\n\n  const switches = [\n    { label: \"Auto scroll\", value: autoScroll, onChange: setAutoScroll },\n    { label: \"Fill parent\", value: fillParent, onChange: setFillParent },\n    { label: \"Auto center\", value: autoCenter, onChange: setAutoCenter },\n  ] as const;\n\n  return (\n    <div className={cn(\"w-full space-y-4\", className)} style={style}>\n      {title && (\n        <p className=\"text-sm font-medium text-foreground truncate\">{title}</p>\n      )}\n\n      <p className=\"text-xs text-muted-foreground\" aria-live=\"polite\">\n        Zoom:{\" \"}\n        <span className=\"tabular-nums font-medium text-foreground\">\n          {currentZoom}\n        </span>{\" \"}\n        px/s\n        <span className=\"ml-2 opacity-60\">— scroll to zoom</span>\n      </p>\n\n      <div className=\"w-full rounded-md overflow-hidden bg-muted/40\">\n        <WavesurferPlayer\n          url={source}\n          waveColor={waveColor}\n          progressColor={progressColor}\n          height={waveHeight}\n          barWidth={barWidth}\n          barGap={barGap}\n          barRadius={barRadius}\n          dragToSeek\n          autoScroll={autoScroll}\n          fillParent={fillParent}\n          autoCenter={autoCenter}\n          plugins={plugins}\n          {...player.handlers}\n        />\n      </div>\n\n      <div className=\"flex flex-wrap items-center gap-x-6 gap-y-2\">\n        {switches.map(({ label, value, onChange }, index) => {\n          const id = `${switchId}-${index}`;\n          return (\n            <div key={label} className=\"flex items-center gap-2\">\n              <Switch\n                id={id}\n                checked={value}\n                onCheckedChange={onChange}\n                disabled={!player.isReady}\n              />\n              <Label\n                htmlFor={id}\n                className=\"text-sm text-muted-foreground cursor-pointer\"\n              >\n                {label}\n              </Label>\n            </div>\n          );\n        })}\n      </div>\n\n      <div className=\"flex items-center gap-2\">\n        <Button\n          size=\"icon\"\n          variant=\"outline\"\n          onClick={backward}\n          disabled={!player.isReady}\n          aria-label={`Backward ${skipSeconds}s`}\n        >\n          <SkipBack className=\"size-4\" />\n        </Button>\n\n        <Button\n          size=\"icon\"\n          onClick={player.togglePlay}\n          disabled={!player.isReady}\n          aria-label={player.isPlaying ? \"Pause\" : \"Play\"}\n        >\n          {player.isPlaying ? (\n            <Pause className=\"size-4\" />\n          ) : (\n            <Play className=\"size-4\" />\n          )}\n        </Button>\n\n        <Button\n          size=\"icon\"\n          variant=\"outline\"\n          onClick={forward}\n          disabled={!player.isReady}\n          aria-label={`Forward ${skipSeconds}s`}\n        >\n          <SkipForward className=\"size-4\" />\n        </Button>\n      </div>\n    </div>\n  );\n}\n\nexport default WaveZoom;\n",
      "type": "registry:component",
      "target": "components/waves-cn/wave-zoom.tsx"
    }
  ],
  "type": "registry:component"
}