{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "ai-voice-selector",
  "title": "AI: Voice Selector",
  "description": "A searchable voice picker with language and provider metadata.",
  "dependencies": [
    "@untitledui/icons@^0.0.23"
  ],
  "registryDependencies": [
    "@nx-ui/utils",
    "@nx-ui/button",
    "@nx-ui/command",
    "@nx-ui/dialog",
    "@nx-ui/spinner",
    "@nx-ui/ai-use-controllable-state"
  ],
  "files": [
    {
      "path": "components/ai/voice-selector.tsx",
      "content": "// Ported from vercel/ai-elements@6a9d5b1822ffb10bba4bd97175f01edd7d8651cd.\n\"use client\";\n\nimport { useControllableState } from \"./use-controllable-state\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Command,\n  CommandDialog,\n  CommandEmpty,\n  CommandGroup,\n  CommandInput,\n  CommandItem,\n  CommandList,\n  CommandSeparator,\n  CommandShortcut,\n} from \"@/components/ui/command\";\nimport {\n  Dialog,\n  DialogContent,\n  DialogTitle,\n  DialogTrigger,\n} from \"@/components/ui/dialog\";\nimport { Spinner } from \"@/components/ui/spinner\";\nimport { cn } from \"@/lib/utils\";\nimport {\n  Circle as CircleSmallIcon,\n  User01 as MarsIcon,\n  User01 as MarsStrokeIcon,\n  User01 as NonBinaryIcon,\n  PauseCircle as PauseIcon,\n  Play as PlayIcon,\n  User01 as TransgenderIcon,\n  Users01 as VenusAndMarsIcon,\n  User01 as VenusIcon,\n} from \"@untitledui/icons\";\nimport type { ComponentProps, ReactNode } from \"react\";\nimport { createContext, useCallback, useContext, useMemo } from \"react\";\n\ninterface VoiceSelectorContextValue {\n  value: string | undefined;\n  setValue: (value: string | undefined) => void;\n  open: boolean;\n  setOpen: (open: boolean) => void;\n}\n\nconst VoiceSelectorContext = createContext<VoiceSelectorContextValue | null>(\n  null\n);\n\nexport const useVoiceSelector = () => {\n  const context = useContext(VoiceSelectorContext);\n  if (!context) {\n    throw new Error(\n      \"VoiceSelector components must be used within VoiceSelector\"\n    );\n  }\n  return context;\n};\n\nexport type VoiceSelectorProps = Omit<ComponentProps<typeof Dialog>, \"onOpenChange\"> & {\n  value?: string;\n  defaultValue?: string;\n  onValueChange?: (value: string | undefined) => void;\n  onOpenChange?: (open: boolean) => void;\n};\n\nexport const VoiceSelector = ({\n  value: valueProp,\n  defaultValue,\n  onValueChange,\n  open: openProp,\n  defaultOpen = false,\n  onOpenChange,\n  children,\n  ...props\n}: VoiceSelectorProps) => {\n  const [value, setValue] = useControllableState<string | undefined>({\n    defaultProp: defaultValue,\n    onChange: onValueChange,\n    prop: valueProp,\n  });\n\n  const [open, setOpen] = useControllableState({\n    defaultProp: defaultOpen,\n    onChange: onOpenChange,\n    prop: openProp,\n  });\n\n  const voiceSelectorContext = useMemo(\n    () => ({ open: open ?? false, setOpen, setValue, value }),\n    [value, setValue, open, setOpen]\n  );\n\n  return (\n    <VoiceSelectorContext.Provider value={voiceSelectorContext}>\n      <Dialog onOpenChange={setOpen} open={open} {...props}>\n        {children}\n      </Dialog>\n    </VoiceSelectorContext.Provider>\n  );\n};\n\nexport type VoiceSelectorTriggerProps = ComponentProps<typeof DialogTrigger>;\n\nexport const VoiceSelectorTrigger = (props: VoiceSelectorTriggerProps) => (\n  <DialogTrigger {...props} />\n);\n\nexport type VoiceSelectorContentProps = ComponentProps<typeof DialogContent> & {\n  title?: ReactNode;\n};\n\nexport const VoiceSelectorContent = ({\n  className,\n  children,\n  title = \"Voice Selector\",\n  ...props\n}: VoiceSelectorContentProps) => (\n  <DialogContent\n    aria-describedby={undefined}\n    className={cn(\"p-0\", className)}\n    {...props}\n  >\n    <DialogTitle className=\"sr-only\">{title}</DialogTitle>\n    <Command className=\"**:data-[slot=command-input-wrapper]:h-auto\">\n      {children}\n    </Command>\n  </DialogContent>\n);\n\nexport type VoiceSelectorDialogProps = ComponentProps<typeof CommandDialog>;\n\nexport const VoiceSelectorDialog = (props: VoiceSelectorDialogProps) => (\n  <CommandDialog {...props} />\n);\n\nexport type VoiceSelectorInputProps = ComponentProps<typeof CommandInput>;\n\nexport const VoiceSelectorInput = ({\n  className,\n  ...props\n}: VoiceSelectorInputProps) => (\n  <CommandInput className={cn(\"h-auto py-3.5\", className)} {...props} />\n);\n\nexport type VoiceSelectorListProps = ComponentProps<typeof CommandList>;\n\nexport const VoiceSelectorList = (props: VoiceSelectorListProps) => (\n  <CommandList {...props} />\n);\n\nexport type VoiceSelectorEmptyProps = ComponentProps<typeof CommandEmpty>;\n\nexport const VoiceSelectorEmpty = (props: VoiceSelectorEmptyProps) => (\n  <CommandEmpty {...props} />\n);\n\nexport type VoiceSelectorGroupProps = ComponentProps<typeof CommandGroup>;\n\nexport const VoiceSelectorGroup = (props: VoiceSelectorGroupProps) => (\n  <CommandGroup {...props} />\n);\n\nexport type VoiceSelectorItemProps = ComponentProps<typeof CommandItem>;\n\nexport const VoiceSelectorItem = ({\n  className,\n  onSelect,\n  ...props\n}: VoiceSelectorItemProps) => {\n  const { setValue, setOpen } = useVoiceSelector();\n  const handleSelect = useCallback((nextValue: string) => {\n    onSelect?.(nextValue);\n    setValue(nextValue);\n    setOpen(false);\n  }, [onSelect, setValue, setOpen]);\n\n  return <CommandItem className={cn(\"px-4 py-2\", className)} onSelect={handleSelect} {...props} />;\n};\n\nexport type VoiceSelectorShortcutProps = ComponentProps<typeof CommandShortcut>;\n\nexport const VoiceSelectorShortcut = (props: VoiceSelectorShortcutProps) => (\n  <CommandShortcut {...props} />\n);\n\nexport type VoiceSelectorSeparatorProps = ComponentProps<\n  typeof CommandSeparator\n>;\n\nexport const VoiceSelectorSeparator = (props: VoiceSelectorSeparatorProps) => (\n  <CommandSeparator {...props} />\n);\n\nexport type VoiceSelectorGenderProps = ComponentProps<\"span\"> & {\n  value?:\n    | \"male\"\n    | \"female\"\n    | \"transgender\"\n    | \"androgyne\"\n    | \"non-binary\"\n    | \"intersex\";\n};\n\nexport const VoiceSelectorGender = ({\n  className,\n  value,\n  children,\n  ...props\n}: VoiceSelectorGenderProps) => {\n  let icon: ReactNode | null = null;\n\n  switch (value) {\n    case \"male\": {\n      icon = <MarsIcon className=\"size-4\" />;\n      break;\n    }\n    case \"female\": {\n      icon = <VenusIcon className=\"size-4\" />;\n      break;\n    }\n    case \"transgender\": {\n      icon = <TransgenderIcon className=\"size-4\" />;\n      break;\n    }\n    case \"androgyne\": {\n      icon = <MarsStrokeIcon className=\"size-4\" />;\n      break;\n    }\n    case \"non-binary\": {\n      icon = <NonBinaryIcon className=\"size-4\" />;\n      break;\n    }\n    case \"intersex\": {\n      icon = <VenusAndMarsIcon className=\"size-4\" />;\n      break;\n    }\n    default: {\n      icon = <CircleSmallIcon className=\"size-4\" />;\n    }\n  }\n\n  return (\n    <span className={cn(\"text-muted-foreground text-xs\", className)} {...props}>\n      {children ?? icon}\n    </span>\n  );\n};\n\nexport type VoiceSelectorAccentProps = ComponentProps<\"span\"> & {\n  value?:\n    | \"american\"\n    | \"british\"\n    | \"australian\"\n    | \"canadian\"\n    | \"irish\"\n    | \"scottish\"\n    | \"indian\"\n    | \"south-african\"\n    | \"new-zealand\"\n    | \"spanish\"\n    | \"french\"\n    | \"german\"\n    | \"italian\"\n    | \"portuguese\"\n    | \"brazilian\"\n    | \"mexican\"\n    | \"argentinian\"\n    | \"japanese\"\n    | \"chinese\"\n    | \"korean\"\n    | \"russian\"\n    | \"arabic\"\n    | \"dutch\"\n    | \"swedish\"\n    | \"norwegian\"\n    | \"danish\"\n    | \"finnish\"\n    | \"polish\"\n    | \"turkish\"\n    | \"greek\"\n    | string;\n};\n\nexport const VoiceSelectorAccent = ({\n  className,\n  value,\n  children,\n  ...props\n}: VoiceSelectorAccentProps) => {\n  let emoji: string | null = null;\n\n  switch (value) {\n    case \"american\": {\n      emoji = \"🇺🇸\";\n      break;\n    }\n    case \"british\": {\n      emoji = \"🇬🇧\";\n      break;\n    }\n    case \"australian\": {\n      emoji = \"🇦🇺\";\n      break;\n    }\n    case \"canadian\": {\n      emoji = \"🇨🇦\";\n      break;\n    }\n    case \"irish\": {\n      emoji = \"🇮🇪\";\n      break;\n    }\n    case \"scottish\": {\n      emoji = \"🏴󠁧󠁢󠁳󠁣󠁴󠁿\";\n      break;\n    }\n    case \"indian\": {\n      emoji = \"🇮🇳\";\n      break;\n    }\n    case \"south-african\": {\n      emoji = \"🇿🇦\";\n      break;\n    }\n    case \"new-zealand\": {\n      emoji = \"🇳🇿\";\n      break;\n    }\n    case \"spanish\": {\n      emoji = \"🇪🇸\";\n      break;\n    }\n    case \"french\": {\n      emoji = \"🇫🇷\";\n      break;\n    }\n    case \"german\": {\n      emoji = \"🇩🇪\";\n      break;\n    }\n    case \"italian\": {\n      emoji = \"🇮🇹\";\n      break;\n    }\n    case \"portuguese\": {\n      emoji = \"🇵🇹\";\n      break;\n    }\n    case \"brazilian\": {\n      emoji = \"🇧🇷\";\n      break;\n    }\n    case \"mexican\": {\n      emoji = \"🇲🇽\";\n      break;\n    }\n    case \"argentinian\": {\n      emoji = \"🇦🇷\";\n      break;\n    }\n    case \"japanese\": {\n      emoji = \"🇯🇵\";\n      break;\n    }\n    case \"chinese\": {\n      emoji = \"🇨🇳\";\n      break;\n    }\n    case \"korean\": {\n      emoji = \"🇰🇷\";\n      break;\n    }\n    case \"russian\": {\n      emoji = \"🇷🇺\";\n      break;\n    }\n    case \"arabic\": {\n      emoji = \"🇸🇦\";\n      break;\n    }\n    case \"dutch\": {\n      emoji = \"🇳🇱\";\n      break;\n    }\n    case \"swedish\": {\n      emoji = \"🇸🇪\";\n      break;\n    }\n    case \"norwegian\": {\n      emoji = \"🇳🇴\";\n      break;\n    }\n    case \"danish\": {\n      emoji = \"🇩🇰\";\n      break;\n    }\n    case \"finnish\": {\n      emoji = \"🇫🇮\";\n      break;\n    }\n    case \"polish\": {\n      emoji = \"🇵🇱\";\n      break;\n    }\n    case \"turkish\": {\n      emoji = \"🇹🇷\";\n      break;\n    }\n    case \"greek\": {\n      emoji = \"🇬🇷\";\n      break;\n    }\n    default: {\n      emoji = null;\n    }\n  }\n\n  return (\n    <span className={cn(\"text-muted-foreground text-xs\", className)} {...props}>\n      {children ?? emoji}\n    </span>\n  );\n};\n\nexport type VoiceSelectorAgeProps = ComponentProps<\"span\">;\n\nexport const VoiceSelectorAge = ({\n  className,\n  ...props\n}: VoiceSelectorAgeProps) => (\n  <span\n    className={cn(\"text-muted-foreground text-xs tabular-nums\", className)}\n    {...props}\n  />\n);\n\nexport type VoiceSelectorNameProps = ComponentProps<\"span\">;\n\nexport const VoiceSelectorName = ({\n  className,\n  ...props\n}: VoiceSelectorNameProps) => (\n  <span\n    className={cn(\"flex-1 truncate text-left font-medium\", className)}\n    {...props}\n  />\n);\n\nexport type VoiceSelectorDescriptionProps = ComponentProps<\"span\">;\n\nexport const VoiceSelectorDescription = ({\n  className,\n  ...props\n}: VoiceSelectorDescriptionProps) => (\n  <span className={cn(\"text-muted-foreground text-xs\", className)} {...props} />\n);\n\nexport type VoiceSelectorAttributesProps = ComponentProps<\"div\">;\n\nexport const VoiceSelectorAttributes = ({\n  className,\n  children,\n  ...props\n}: VoiceSelectorAttributesProps) => (\n  <div className={cn(\"flex items-center text-xs\", className)} {...props}>\n    {children}\n  </div>\n);\n\nexport type VoiceSelectorBulletProps = ComponentProps<\"span\">;\n\nexport const VoiceSelectorBullet = ({\n  className,\n  ...props\n}: VoiceSelectorBulletProps) => (\n  <span\n    aria-hidden=\"true\"\n    className={cn(\"select-none text-border\", className)}\n    {...props}\n  >\n    &bull;\n  </span>\n);\n\nexport type VoiceSelectorPreviewProps = Omit<\n  ComponentProps<\"button\">,\n  \"children\"\n> & {\n  playing?: boolean;\n  loading?: boolean;\n  onPlay?: () => void;\n};\n\nexport const VoiceSelectorPreview = ({\n  className,\n  playing,\n  loading,\n  onPlay,\n  onClick,\n  ...props\n}: VoiceSelectorPreviewProps) => {\n  const handleClick = useCallback(\n    (event: React.MouseEvent<HTMLButtonElement>) => {\n      event.stopPropagation();\n      onClick?.(event);\n      onPlay?.();\n    },\n    [onClick, onPlay]\n  );\n\n  let icon = <PlayIcon className=\"size-3\" />;\n\n  if (loading) {\n    icon = <Spinner className=\"size-3\" />;\n  } else if (playing) {\n    icon = <PauseIcon className=\"size-3\" />;\n  }\n\n  return (\n    <Button\n      aria-label={playing ? \"Pause preview\" : \"Play preview\"}\n      className={cn(\"size-6\", className)}\n      disabled={loading}\n      onClick={handleClick}\n      size=\"icon-sm\"\n      type=\"button\"\n      variant=\"outline\"\n      {...props}\n    >\n      {icon}\n    </Button>\n  );\n};\n",
      "type": "registry:ui",
      "target": "components/ai/voice-selector.tsx"
    }
  ],
  "type": "registry:ui"
}