{ "$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 \n \n \n \n {selectedVoice ? (\n \n \n \n \n {selectedVoice.name}\n \n ) : (\n placeholder\n )}\n \n \n \n \n \n \n \n No voice found.\n \n {voices.map((voice) => (\n {\n onValueChange?.(voice.voiceId!)\n }}\n />\n ))}\n \n \n \n \n \n \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 Boolean(k))}\n onSelect={onSelect}\n className=\"flex items-center gap-3\"\n >\n setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n onClick={handlePreview}\n >\n \n {preview && isHovered && (\n \n {isPlaying ? (\n \n ) : (\n \n )}\n \n )}\n \n\n \n {voice.name}\n {voice.labels && (\n \n {voice.labels.accent && {voice.labels.accent}}\n {voice.labels.gender && •}\n {voice.labels.gender && (\n {voice.labels.gender}\n )}\n {voice.labels.age && •}\n {voice.labels.age && (\n {voice.labels.age}\n )}\n \n )}\n \n\n \n \n )\n}\n\nexport { VoicePicker, VoicePickerItem }\n", "type": "registry:ui" } ], "type": "registry:ui" }