"use client"; import { useControllableState } from "@radix-ui/react-use-controllable-state"; import { Button } from "@/components/ui/button"; import { Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut, } from "@/components/ui/command"; import { Dialog, DialogContent, DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; import { Spinner } from "@/components/ui/spinner"; import { cn } from "@/lib/utils"; import { CircleSmallIcon, MarsIcon, MarsStrokeIcon, NonBinaryIcon, PauseIcon, PlayIcon, TransgenderIcon, VenusAndMarsIcon, VenusIcon, } from "lucide-react"; import type { ComponentProps, ReactNode } from "react"; import { createContext, useCallback, useContext, useMemo } from "react"; interface VoiceSelectorContextValue { value: string | undefined; setValue: (value: string | undefined) => void; open: boolean; setOpen: (open: boolean) => void; } const VoiceSelectorContext = createContext( null ); export const useVoiceSelector = () => { const context = useContext(VoiceSelectorContext); if (!context) { throw new Error( "VoiceSelector components must be used within VoiceSelector" ); } return context; }; export type VoiceSelectorProps = ComponentProps & { value?: string; defaultValue?: string; onValueChange?: (value: string | undefined) => void; }; export const VoiceSelector = ({ value: valueProp, defaultValue, onValueChange, open: openProp, defaultOpen = false, onOpenChange, children, ...props }: VoiceSelectorProps) => { const [value, setValue] = useControllableState({ defaultProp: defaultValue, onChange: onValueChange, prop: valueProp, }); const [open, setOpen] = useControllableState({ defaultProp: defaultOpen, onChange: onOpenChange, prop: openProp, }); const voiceSelectorContext = useMemo( () => ({ open, setOpen, setValue, value }), [value, setValue, open, setOpen] ); return ( {children} ); }; export type VoiceSelectorTriggerProps = ComponentProps; export const VoiceSelectorTrigger = (props: VoiceSelectorTriggerProps) => ( ); export type VoiceSelectorContentProps = ComponentProps & { title?: ReactNode; }; export const VoiceSelectorContent = ({ className, children, title = "Voice Selector", ...props }: VoiceSelectorContentProps) => ( {title} {children} ); export type VoiceSelectorDialogProps = ComponentProps; export const VoiceSelectorDialog = (props: VoiceSelectorDialogProps) => ( ); export type VoiceSelectorInputProps = ComponentProps; export const VoiceSelectorInput = ({ className, ...props }: VoiceSelectorInputProps) => ( ); export type VoiceSelectorListProps = ComponentProps; export const VoiceSelectorList = (props: VoiceSelectorListProps) => ( ); export type VoiceSelectorEmptyProps = ComponentProps; export const VoiceSelectorEmpty = (props: VoiceSelectorEmptyProps) => ( ); export type VoiceSelectorGroupProps = ComponentProps; export const VoiceSelectorGroup = (props: VoiceSelectorGroupProps) => ( ); export type VoiceSelectorItemProps = ComponentProps; export const VoiceSelectorItem = ({ className, ...props }: VoiceSelectorItemProps) => ( ); export type VoiceSelectorShortcutProps = ComponentProps; export const VoiceSelectorShortcut = (props: VoiceSelectorShortcutProps) => ( ); export type VoiceSelectorSeparatorProps = ComponentProps< typeof CommandSeparator >; export const VoiceSelectorSeparator = (props: VoiceSelectorSeparatorProps) => ( ); export type VoiceSelectorGenderProps = ComponentProps<"span"> & { value?: | "male" | "female" | "transgender" | "androgyne" | "non-binary" | "intersex"; }; export const VoiceSelectorGender = ({ className, value, children, ...props }: VoiceSelectorGenderProps) => { let icon: ReactNode | null = null; switch (value) { case "male": { icon = ; break; } case "female": { icon = ; break; } case "transgender": { icon = ; break; } case "androgyne": { icon = ; break; } case "non-binary": { icon = ; break; } case "intersex": { icon = ; break; } default: { icon = ; } } return ( {children ?? icon} ); }; export type VoiceSelectorAccentProps = ComponentProps<"span"> & { value?: | "american" | "british" | "australian" | "canadian" | "irish" | "scottish" | "indian" | "south-african" | "new-zealand" | "spanish" | "french" | "german" | "italian" | "portuguese" | "brazilian" | "mexican" | "argentinian" | "japanese" | "chinese" | "korean" | "russian" | "arabic" | "dutch" | "swedish" | "norwegian" | "danish" | "finnish" | "polish" | "turkish" | "greek" | string; }; export const VoiceSelectorAccent = ({ className, value, children, ...props }: VoiceSelectorAccentProps) => { let emoji: string | null = null; switch (value) { case "american": { emoji = "🇺🇸"; break; } case "british": { emoji = "🇬🇧"; break; } case "australian": { emoji = "🇦🇺"; break; } case "canadian": { emoji = "🇨🇦"; break; } case "irish": { emoji = "🇮🇪"; break; } case "scottish": { emoji = "🏴󠁧󠁢󠁳󠁣󠁴󠁿"; break; } case "indian": { emoji = "🇮🇳"; break; } case "south-african": { emoji = "🇿🇦"; break; } case "new-zealand": { emoji = "🇳🇿"; break; } case "spanish": { emoji = "🇪🇸"; break; } case "french": { emoji = "🇫🇷"; break; } case "german": { emoji = "🇩🇪"; break; } case "italian": { emoji = "🇮🇹"; break; } case "portuguese": { emoji = "🇵🇹"; break; } case "brazilian": { emoji = "🇧🇷"; break; } case "mexican": { emoji = "🇲🇽"; break; } case "argentinian": { emoji = "🇦🇷"; break; } case "japanese": { emoji = "🇯🇵"; break; } case "chinese": { emoji = "🇨🇳"; break; } case "korean": { emoji = "🇰🇷"; break; } case "russian": { emoji = "🇷🇺"; break; } case "arabic": { emoji = "🇸🇦"; break; } case "dutch": { emoji = "🇳🇱"; break; } case "swedish": { emoji = "🇸🇪"; break; } case "norwegian": { emoji = "🇳🇴"; break; } case "danish": { emoji = "🇩🇰"; break; } case "finnish": { emoji = "🇫🇮"; break; } case "polish": { emoji = "🇵🇱"; break; } case "turkish": { emoji = "🇹🇷"; break; } case "greek": { emoji = "🇬🇷"; break; } default: { emoji = null; } } return ( {children ?? emoji} ); }; export type VoiceSelectorAgeProps = ComponentProps<"span">; export const VoiceSelectorAge = ({ className, ...props }: VoiceSelectorAgeProps) => ( ); export type VoiceSelectorNameProps = ComponentProps<"span">; export const VoiceSelectorName = ({ className, ...props }: VoiceSelectorNameProps) => ( ); export type VoiceSelectorDescriptionProps = ComponentProps<"span">; export const VoiceSelectorDescription = ({ className, ...props }: VoiceSelectorDescriptionProps) => ( ); export type VoiceSelectorAttributesProps = ComponentProps<"div">; export const VoiceSelectorAttributes = ({ className, children, ...props }: VoiceSelectorAttributesProps) => (
{children}
); export type VoiceSelectorBulletProps = ComponentProps<"span">; export const VoiceSelectorBullet = ({ className, ...props }: VoiceSelectorBulletProps) => ( ); export type VoiceSelectorPreviewProps = Omit< ComponentProps<"button">, "children" > & { playing?: boolean; loading?: boolean; onPlay?: () => void; }; export const VoiceSelectorPreview = ({ className, playing, loading, onPlay, onClick, ...props }: VoiceSelectorPreviewProps) => { const handleClick = useCallback( (event: React.MouseEvent) => { event.stopPropagation(); onClick?.(event); onPlay?.(); }, [onClick, onPlay] ); let icon = ; if (loading) { icon = ; } else if (playing) { icon = ; } return ( ); };