ui_storybook/components/recorder/
capture_source_row.rs1use leptos::prelude::*;
9
10use crate::components::primitives::{Camera, IconTile, IconTileKind, Meter, Mic, ToggleSwitch};
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq)]
15pub enum CaptureSourceKind {
16 Camera,
18 Microphone,
20}
21
22impl CaptureSourceKind {
23 #[must_use]
25 pub fn label(self) -> &'static str {
26 match self {
27 CaptureSourceKind::Camera => "camera",
28 CaptureSourceKind::Microphone => "microphone",
29 }
30 }
31}
32
33#[derive(Debug, Clone, PartialEq)]
35pub struct CaptureSourceView {
36 pub id: String,
38 pub kind: CaptureSourceKind,
40 pub title: String,
42 pub subtitle: String,
44 pub enabled: bool,
46 pub expanded: bool,
48 pub favorite: bool,
50 pub level: Option<f32>,
53}
54
55#[component]
56pub fn CaptureSourceRow(view: CaptureSourceView) -> impl IntoView {
57 let mut class = String::from("capture-source-row");
58 if view.expanded {
59 class.push_str(" capture-source-row-expanded");
60 }
61 if !view.enabled {
62 class.push_str(" capture-source-row-off");
63 }
64 let chevron_class = if view.expanded {
65 "capture-source-chevron capture-source-chevron-open"
66 } else {
67 "capture-source-chevron"
68 };
69 let kind_label = view.kind.label();
70 let toggle_label = format!("Enable {kind_label}");
71 let meter_view = if view.kind == CaptureSourceKind::Microphone {
72 view.level
73 .map(|level| view! { <Meter level=level bar_count=10 /> })
74 } else {
75 None
76 };
77 view! {
78 <div class=class data-kind=match view.kind {
79 CaptureSourceKind::Camera => "camera",
80 CaptureSourceKind::Microphone => "microphone",
81 }>
82 <span class="capture-source-leading">
83 <IconTile kind=IconTileKind::Device>
84 {match view.kind {
85 CaptureSourceKind::Camera => view! { <Camera /> }.into_any(),
86 CaptureSourceKind::Microphone => view! { <Mic /> }.into_any(),
87 }}
88 </IconTile>
89 </span>
90 <span class="capture-source-text">
91 <span class="capture-source-title">
92 {view.title}
93 {view.favorite.then(|| view! {
94 <span class="capture-source-star" aria-label="Favourite" title="Favourite">"★"</span>
95 })}
96 </span>
97 <span class="capture-source-subtitle">{view.subtitle}</span>
98 </span>
99 {meter_view.map(|m| view! { <span class="capture-source-meter">{m}</span> })}
100 <span class="capture-source-toggle">
101 <ToggleSwitch checked=view.enabled label=toggle_label />
102 </span>
103 <button class=chevron_class aria-label="Expand device picker" aria-expanded=view.expanded>
104 "▾"
105 </button>
106 </div>
107 }
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113
114 #[test]
115 fn each_kind_has_a_unique_label() {
116 assert_ne!(
117 CaptureSourceKind::Camera.label(),
118 CaptureSourceKind::Microphone.label(),
119 );
120 }
121
122 #[test]
123 fn labels_are_lowercase_singular() {
124 for k in [CaptureSourceKind::Camera, CaptureSourceKind::Microphone] {
125 let l = k.label();
126 assert_eq!(l.to_lowercase(), l);
127 assert!(!l.starts_with("the "));
130 }
131 }
132}