{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "voice-picker",
  "dependencies": [
    "@elevenlabs/elevenlabs-js"
  ],
  "registryDependencies": [
    "button",
    "badge",
    "command",
    "popover",
    "https://ui.elevenlabs.io/r/orb.json",
    "https://ui.elevenlabs.io/r/audio-player.json"
  ],
  "files": [
    {
      "path": "components/ui/voice-picker.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport type { ElevenLabs } from \"@elevenlabs/elevenlabs-js\"\nimport { Check, ChevronsUpDown, Pause, Play } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport {\n  AudioPlayerProvider,\n  useAudioPlayer,\n} from \"@/components/ui/audio-player\"\nimport { Button } from \"@/components/ui/button\"\nimport {\n  Command,\n  CommandEmpty,\n  CommandGroup,\n  CommandInput,\n  CommandItem,\n  CommandList,\n} from \"@/components/ui/command\"\nimport { Orb } from \"@/components/ui/orb\"\nimport {\n  Popover,\n  PopoverContent,\n  PopoverTrigger,\n} from \"@/components/ui/popover\"\n\ninterface VoicePickerProps {\n  voices: ElevenLabs.Voice[]\n  value?: string\n  onValueChange?: (value: string) => void\n  placeholder?: string\n  className?: string\n  open?: boolean\n  onOpenChange?: (open: boolean) => void\n}\n\nfunction VoicePicker({\n  voices,\n  value,\n  onValueChange,\n  placeholder = \"Select a voice...\",\n  className,\n  open,\n  onOpenChange,\n}: VoicePickerProps) {\n  const [internalOpen, setInternalOpen] = React.useState(false)\n  const isControlled = open !== undefined\n  const isOpen = isControlled ? open : internalOpen\n  const setIsOpen = isControlled ? onOpenChange : setInternalOpen\n\n  const selectedVoice = voices.find((v) => v.voiceId === value)\n\n  return (\n    <AudioPlayerProvider>\n      <Popover open={isOpen} onOpenChange={setIsOpen}>\n        <PopoverTrigger asChild>\n          <Button\n            variant=\"outline\"\n            role=\"combobox\"\n            aria-expanded={isOpen}\n            className={cn(\"w-full justify-between\", className)}\n          >\n            {selectedVoice ? (\n              <div className=\"flex items-center gap-2 overflow-hidden\">\n                <div className=\"relative size-6 shrink-0 overflow-visible\">\n                  <Orb agentState=\"thinking\" className=\"absolute inset-0\" />\n                </div>\n                <span className=\"truncate\">{selectedVoice.name}</span>\n              </div>\n            ) : (\n              placeholder\n            )}\n            <ChevronsUpDown className=\"ml-2 size-4 shrink-0 opacity-50\" />\n          </Button>\n        </PopoverTrigger>\n        <PopoverContent className=\"w-[var(--radix-popover-trigger-width)] p-0\">\n          <Command>\n            <CommandInput placeholder=\"Search voices...\" />\n            <CommandList>\n              <CommandEmpty>No voice found.</CommandEmpty>\n              <CommandGroup>\n                {voices.map((voice) => (\n                  <VoicePickerItem\n                    key={voice.voiceId}\n                    voice={voice}\n                    isSelected={value === voice.voiceId}\n                    onSelect={() => {\n                      onValueChange?.(voice.voiceId!)\n                    }}\n                  />\n                ))}\n              </CommandGroup>\n            </CommandList>\n          </Command>\n        </PopoverContent>\n      </Popover>\n    </AudioPlayerProvider>\n  )\n}\n\ninterface VoicePickerItemProps {\n  voice: ElevenLabs.Voice\n  isSelected: boolean\n  onSelect: () => void\n}\n\nfunction VoicePickerItem({\n  voice,\n  isSelected,\n  onSelect,\n}: VoicePickerItemProps) {\n  const [isHovered, setIsHovered] = React.useState(false)\n  const player = useAudioPlayer()\n\n  const preview = voice.previewUrl\n  const audioItem = React.useMemo(\n    () => (preview ? { id: voice.voiceId!, src: preview, data: voice } : null),\n    [preview, voice]\n  )\n\n  const isPlaying =\n    audioItem && player.isItemActive(audioItem.id) && player.isPlaying\n\n  const handlePreview = React.useCallback(\n    async (e: React.MouseEvent) => {\n      e.preventDefault()\n      e.stopPropagation()\n\n      if (!audioItem) return\n\n      if (isPlaying) {\n        player.pause()\n      } else {\n        player.play(audioItem)\n      }\n    },\n    [audioItem, isPlaying, player]\n  )\n\n  return (\n    <CommandItem\n      value={voice.voiceId!}\n      keywords={[\n        voice.name,\n        voice.labels?.accent,\n        voice.labels?.gender,\n        voice.labels?.age,\n        voice.labels?.description,\n        voice.labels?.[\"use case\"],\n      ].filter((k): k is string => Boolean(k))}\n      onSelect={onSelect}\n      className=\"flex items-center gap-3\"\n    >\n      <div\n        className=\"relative z-10 size-8 shrink-0 cursor-pointer overflow-visible\"\n        onMouseEnter={() => setIsHovered(true)}\n        onMouseLeave={() => setIsHovered(false)}\n        onClick={handlePreview}\n      >\n        <Orb\n          agentState={isPlaying ? \"talking\" : undefined}\n          className=\"pointer-events-none absolute inset-0\"\n        />\n        {preview && isHovered && (\n          <div className=\"pointer-events-none absolute inset-0 flex size-8 shrink-0 items-center justify-center rounded-full bg-black/40 backdrop-blur-sm transition-opacity hover:bg-black/50\">\n            {isPlaying ? (\n              <Pause className=\"size-3 text-white\" />\n            ) : (\n              <Play className=\"size-3 text-white\" />\n            )}\n          </div>\n        )}\n      </div>\n\n      <div className=\"flex flex-1 flex-col gap-0.5\">\n        <span className=\"font-medium\">{voice.name}</span>\n        {voice.labels && (\n          <div className=\"text-muted-foreground flex items-center gap-1.5 text-xs\">\n            {voice.labels.accent && <span>{voice.labels.accent}</span>}\n            {voice.labels.gender && <span>•</span>}\n            {voice.labels.gender && (\n              <span className=\"capitalize\">{voice.labels.gender}</span>\n            )}\n            {voice.labels.age && <span>•</span>}\n            {voice.labels.age && (\n              <span className=\"capitalize\">{voice.labels.age}</span>\n            )}\n          </div>\n        )}\n      </div>\n\n      <Check\n        className={cn(\n          \"ml-auto size-4 shrink-0\",\n          isSelected ? \"opacity-100\" : \"opacity-0\"\n        )}\n      />\n    </CommandItem>\n  )\n}\n\nexport { VoicePicker, VoicePickerItem }\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}