Device picker menu
Popover that opens from the CaptureSourceRow chevron. Lists
available devices for the camera or microphone slot. Composes UI-03
PopoverSurface + MenuList + a custom device-picker-row shape
(thumbnail + name/detail + optional badge + optional meter + selected
check).
States
| State | Story |
|---|---|
| Cameras populated | device-picker-camera-open |
| Microphones populated, live meters | device-picker-microphone-open |
| No devices detected | device-picker-empty |
| Permission needed | device-picker-permission-needed |
API
#![allow(unused)] fn main() { use ui_storybook::components::{ DevicePickerMenu, DevicePickerState, DeviceOptionView, CaptureSourceKind, }; view! { <DevicePickerMenu kind=CaptureSourceKind::Camera devices=fixtures::devices::sample_camera_options() // optional — defaults to Populated state=DevicePickerState::Populated /> } }
When state != Populated, devices is ignored. The component
renders a centered icon + headline + subtitle from a fixed
template. This keeps the parent from having to branch on which list
to pass — pass the real list always; the picker handles the empty
case itself.
DeviceOptionView shape
#![allow(unused)] fn main() { pub struct DeviceOptionView { pub id: &'static str, pub name: &'static str, pub detail: &'static str, pub badge: Option<&'static str>, // "Wireless", "New" pub selected: bool, // ✓ checkmark pub level: Option<f32>, // microphone meter pub thumbnail: Option<DeviceThumb>, // camera thumbnail tile } }
Cameras typically set thumbnail + level = None. Microphones
typically set level = Some(0..=1) + thumbnail = None. The
component handles both gracefully.
Composition
flowchart TD
Picker[DevicePickerMenu] --> Surface[PopoverSurface]
Surface --> Body[body slot]
Body --> Branch{state}
Branch -->|Populated| List[MenuList → MenuSection × N → device-picker-row × N]
Branch -->|Empty| EmptyState[icon + headline + subtitle]
Branch -->|PermissionNeeded| PermState[warn icon + headline + subtitle]
Surface --> Footer[MenuFooter — Connect/Pair button]