ui_storybook/components/recorder/
recording_toolbar.rs1use leptos::prelude::*;
9
10#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
11pub enum RecordingState {
12 #[default]
13 Idle,
14 Recording,
15 Paused,
16}
17
18impl RecordingState {
19 fn css(self) -> &'static str {
20 match self {
21 RecordingState::Idle => "rec-idle",
22 RecordingState::Recording => "rec-recording",
23 RecordingState::Paused => "rec-paused",
24 }
25 }
26
27 fn status_label(self) -> &'static str {
28 match self {
29 RecordingState::Idle => "Ready",
30 RecordingState::Recording => "Recording",
31 RecordingState::Paused => "Paused",
32 }
33 }
34}
35
36#[component]
37pub fn RecordingToolbar(
38 #[prop(optional)]
40 state: RecordingState,
41 #[prop(optional)]
43 elapsed_seconds: f32,
44 #[prop(optional, into)]
46 source: String,
47) -> impl IntoView {
48 let class = format!("recording-toolbar {}", state.css());
49 let status = state.status_label();
50 let timer = format_clock(elapsed_seconds.max(0.0));
51 let source_label = if source.is_empty() {
52 "Pick a source".to_string()
53 } else {
54 source
55 };
56
57 let actions = match state {
58 RecordingState::Idle => view! {
59 <button class="rec-action rec-action-primary" type="button">
60 <span class="rec-dot rec-dot-static"></span>
61 <span>"Start recording"</span>
62 </button>
63 }
64 .into_any(),
65 RecordingState::Recording => view! {
66 <button class="rec-action rec-action-secondary" type="button">"Pause"</button>
67 <button class="rec-action rec-action-stop" type="button">"Stop"</button>
68 }
69 .into_any(),
70 RecordingState::Paused => view! {
71 <button class="rec-action rec-action-primary" type="button">
72 <span>"Resume"</span>
73 </button>
74 <button class="rec-action rec-action-stop" type="button">"Stop"</button>
75 }
76 .into_any(),
77 };
78
79 view! {
80 <div class=class role="toolbar" aria-label="Recording controls">
81 <div class="rec-status">
82 <span class="rec-dot"></span>
83 <span class="rec-status-label">{status}</span>
84 </div>
85 <div class="rec-timer">{timer}</div>
86 <div class="rec-source">
87 <span class="rec-source-glyph">"⌬"</span>
88 <span class="rec-source-label">{source_label}</span>
89 </div>
90 <div class="rec-actions">{actions}</div>
91 </div>
92 }
93}
94
95#[allow(
96 clippy::cast_possible_truncation,
97 clippy::cast_sign_loss,
98 reason = "elapsed is positive and small, fits in u32"
99)]
100fn format_clock(seconds: f32) -> String {
101 let total = seconds.round() as u32;
102 let h = total / 3600;
103 let m = (total % 3600) / 60;
104 let s = total % 60;
105 if h > 0 {
106 format!("{h}:{m:02}:{s:02}")
107 } else {
108 format!("{m:02}:{s:02}")
109 }
110}