# Mic Selector A composable dropdown component for selecting audio input devices with permission handling and device change detection. The `MicSelector` component provides a flexible and composable interface for selecting microphone input devices. Built on shadcn/ui's Command and Popover components, it features automatic device detection, permission handling, dynamic device list updates, and intelligent device name parsing. See `scripts/mic-selector.tsx` for this example. ## Installation ```bash npx ai-elements@latest add mic-selector ``` ## Features - Fully composable architecture with granular control components - Automatic audio input device enumeration - Permission-based device name display - Real-time device change detection via devicechange events - Intelligent device label parsing with ID extraction - Controlled and uncontrolled component patterns - Responsive width matching between trigger and content - Built on shadcn/ui Command and Popover components - Full TypeScript support with proper types for all components ## Props ### `` Root Popover component that provides context for all child components. | Prop | Type | Default | Description | |------|------|---------|-------------| | `defaultValue` | `string` | - | The default selected device ID (uncontrolled). | | `value` | `string` | - | The selected device ID (controlled). | | `onValueChange` | `(deviceId: string) => void` | - | Callback fired when the selected device changes. | | `defaultOpen` | `boolean` | `false` | The default open state (uncontrolled). | | `open` | `boolean` | - | The open state (controlled). | | `onOpenChange` | `(open: boolean) => void` | - | Callback fired when the open state changes. Automatically requests microphone permission when opened without permission. | | `...props` | `React.ComponentProps` | - | Any other props are spread to the Popover component. | ### `` Button that opens the microphone selector popover. Automatically tracks its width to match the popover content. | Prop | Type | Default | Description | |------|------|---------|-------------| | `...props` | `React.ComponentProps` | - | Any other props are spread to the Button component. | ### `` Displays the currently selected microphone name or a placeholder. | Prop | Type | Default | Description | |------|------|---------|-------------| | `...props` | `React.ComponentProps<` | - | Any other props are spread to the span element. | ### `` Container for the Command component, rendered inside the popover. | Prop | Type | Default | Description | |------|------|---------|-------------| | `popoverOptions` | `React.ComponentProps` | - | Props to pass to the underlying PopoverContent component. | | `...props` | `React.ComponentProps` | - | Any other props are spread to the Command component. | ### `` Search input for filtering microphones. | Prop | Type | Default | Description | |------|------|---------|-------------| | `...props` | `React.ComponentProps` | - | Any other props are spread to the CommandInput component. | ### `` Wrapper for the list of microphone items. Uses render props pattern to provide access to device data. | Prop | Type | Default | Description | |------|------|---------|-------------| | `children` | `(devices: MediaDeviceInfo[]) => ReactNode` | - | Render function that receives the array of available devices. | | `...props` | `Omit, ` | - | Any other props are spread to the CommandList component. | ### `` Message shown when no microphones match the search. | Prop | Type | Default | Description | |------|------|---------|-------------| | `children` | `ReactNode` | - | The message to display. | | `...props` | `React.ComponentProps` | - | Any other props are spread to the CommandEmpty component. | ### `` Selectable item representing a microphone. | Prop | Type | Default | Description | |------|------|---------|-------------| | `value` | `string` | - | The device ID for this item. | | `...props` | `React.ComponentProps` | - | Any other props are spread to the CommandItem component. | ### `` Displays a formatted microphone label with intelligent device ID parsing. Automatically extracts and styles device IDs in the format (XXXX:XXXX). | Prop | Type | Default | Description | |------|------|---------|-------------| | `device` | `MediaDeviceInfo` | - | The MediaDeviceInfo object for the device. | | `...props` | `React.ComponentProps<` | - | Any other props are spread to the span element. | ## Hooks ### `useAudioDevices()` A custom hook for managing audio input devices. This hook is used internally by the `MicSelector` component but can also be used independently. ```tsx import { useAudioDevices } from "@repo/elements/mic-selector"; export default function Example() { const { devices, loading, error, hasPermission, loadDevices } = useAudioDevices(); return (
{loading &&

Loading devices...

} {error &&

Error: {error}

} {devices.map((device) => (
{device.label}
))} {!hasPermission && ( )}
); } ``` #### Return Value | Prop | Type | Default | Description | |------|------|---------|-------------| | `devices` | `MediaDeviceInfo[]` | - | Array of available audio input devices. | | `loading` | `boolean` | - | Whether devices are currently being loaded. | | `error` | `string | null` | - | Error message if device loading failed. | | `hasPermission` | `boolean` | - | Whether microphone permission has been granted. | | `loadDevices` | `() => Promise` | - | Function to request microphone permission and load device names. | ## Behavior ### Permission Handling The component implements a two-stage permission approach: 1. **Without Permission**: Initially loads devices without requesting permission. Device labels may show as generic names (e.g., "Microphone 1"). 2. **With Permission**: When the popover is opened and permission hasn't been granted, automatically requests microphone access and displays actual device names. ### Device Label Parsing The `MicSelectorLabel` component intelligently parses device names that include hardware IDs in the format `(XXXX:XXXX)`. It splits the label into the device name and ID, styling the ID with muted text for better readability. For example: `"MacBook Pro Microphone (1a2b:3c4d)"` becomes: - Device name: `"MacBook Pro Microphone"` - Device ID: `"(1a2b:3c4d)"` (styled with muted color) ### Width Synchronization The `MicSelectorTrigger` uses a ResizeObserver to track its width and automatically synchronizes it with the `MicSelectorContent` popover width for a cohesive appearance. ### Device Change Detection The component listens for `devicechange` events (e.g., plugging/unplugging microphones) and automatically updates the device list in real-time. ## Accessibility - Uses semantic HTML with proper ARIA attributes via shadcn/ui components - Full keyboard navigation support through Command component - Screen reader friendly with proper labels and roles - Searchable device list for quick selection ## Notes - Requires a secure context (HTTPS or localhost) for microphone access - Browser may prompt user for microphone permission on first open - Device labels are only fully descriptive after permission is granted - Component handles cleanup of temporary media streams during permission requests - Uses Radix UI's `useControllableState` for flexible controlled/uncontrolled patterns