orbital_date_pickers/building_blocks/time_clock/
dial.rs1use leptos::prelude::*;
2use orbital_core_components::{Button, ButtonAppearance, ButtonGroup, ButtonShape, ButtonSize};
3
4use crate::shared::{
5 hour_markers, is_minute_step_disabled, marker_position_style, minute_display_markers,
6 snap_minute, tick_endpoints, to_twelve_hour, to_twenty_four_hour, DEFAULT_FACE_SIZE,
7};
8
9use super::clock_number::ClockNumber;
10use super::interaction::ClockInteractionOverlay;
11use super::pointer::ClockPointer;
12
13#[derive(Clone, Copy, Debug, PartialEq, Eq)]
14pub enum ClockView {
15 Hours,
16 Minutes,
17}
18
19#[component]
20pub fn TimeClockDial(
21 view: RwSignal<ClockView>,
22 draft_hour_24: RwSignal<u32>,
23 draft_minute: RwSignal<u32>,
24 is_pm: RwSignal<bool>,
25 ampm: Signal<bool>,
26 minute_step: Signal<u32>,
27 disabled: Signal<bool>,
28 on_minute_selected: Callback<(u32, u32)>,
29) -> impl IntoView {
30 view! {
31 <div class="orb-picker-time-clock__face">
32 <svg class="orb-picker-time-clock__svg" viewBox="0 0 260 260" aria-hidden="true">
33 <circle class="orb-picker-time-clock__circle" cx="130" cy="130" r="125" />
34 {(0..12).map(|index| {
35 let (x1, y1, x2, y2) = tick_endpoints(index, DEFAULT_FACE_SIZE);
36 view! {
37 <line
38 class="orb-picker-time-clock__tick"
39 x1=x1
40 y1=y1
41 x2=x2
42 y2=y2
43 />
44 }
45 }).collect_view()}
46 <circle class="orb-picker-time-clock__center" cx="130" cy="130" r="4" />
47 </svg>
48
49 <ClockPointer
50 view=Signal::derive(move || view.get())
51 draft_hour_24=Signal::derive(move || draft_hour_24.get())
52 draft_minute=Signal::derive(move || draft_minute.get())
53 ampm=ampm
54 />
55
56 {move || {
57 match view.get() {
58 ClockView::Hours => hour_numbers_view(
59 ampm,
60 disabled,
61 draft_hour_24,
62 is_pm,
63 view,
64 )
65 .into_any(),
66 ClockView::Minutes => minute_numbers_view(
67 minute_step,
68 disabled,
69 draft_hour_24,
70 draft_minute,
71 view,
72 on_minute_selected,
73 )
74 .into_any(),
75 }
76 }}
77
78 <ClockInteractionOverlay
79 view=view
80 draft_hour_24=draft_hour_24
81 draft_minute=draft_minute
82 is_pm=is_pm
83 ampm=ampm
84 minute_step=minute_step
85 disabled=disabled
86 on_minute_selected=on_minute_selected
87 />
88 </div>
89 {move || {
90 ampm.get().then(|| {
91 view! {
92 <div class="orb-picker-time-clock__meridiem">
93 <ButtonGroup>
94 <Button
95 appearance=Signal::derive(move || {
96 if is_pm.get() {
97 ButtonAppearance::Secondary
98 } else {
99 ButtonAppearance::Primary
100 }
101 })
102 shape=Signal::from(ButtonShape::Rounded)
103 size=Signal::from(ButtonSize::Small)
104 disabled=disabled
105 on_click=Callback::new(move |_| {
106 if is_pm.get() {
107 is_pm.set(false);
108 let (display, _) = to_twelve_hour(draft_hour_24.get());
109 draft_hour_24.set(to_twenty_four_hour(display, false));
110 }
111 })
112 >
113 "AM"
114 </Button>
115 <Button
116 appearance=Signal::derive(move || {
117 if is_pm.get() {
118 ButtonAppearance::Primary
119 } else {
120 ButtonAppearance::Secondary
121 }
122 })
123 shape=Signal::from(ButtonShape::Rounded)
124 size=Signal::from(ButtonSize::Small)
125 disabled=disabled
126 on_click=Callback::new(move |_| {
127 if !is_pm.get() {
128 is_pm.set(true);
129 let (display, _) = to_twelve_hour(draft_hour_24.get());
130 draft_hour_24.set(to_twenty_four_hour(display, true));
131 }
132 })
133 >
134 "PM"
135 </Button>
136 </ButtonGroup>
137 </div>
138 }
139 })
140 }}
141 }
142}
143
144fn hour_numbers_view(
145 ampm: Signal<bool>,
146 disabled: Signal<bool>,
147 draft_hour_24: RwSignal<u32>,
148 is_pm: RwSignal<bool>,
149 view: RwSignal<ClockView>,
150) -> impl IntoView {
151 let markers = hour_markers(ampm.get_untracked());
152 let count = markers.len();
153 markers
154 .into_iter()
155 .enumerate()
156 .map(|(index, marker)| {
157 let (left, top) = marker_position_style(index, count, false);
158 let display = if ampm.get_untracked() {
159 marker.to_string()
160 } else {
161 format!("{marker:02}")
162 };
163 let aria_label = format!("{display} hours");
164 view! {
165 <ClockNumber
166 label=display.clone()
167 selected=Signal::derive(move || {
168 let selected = draft_hour_24.get();
169 if ampm.get() {
170 to_twelve_hour(selected).0 == marker
171 } else {
172 selected == marker
173 }
174 })
175 disabled=disabled
176 left=left
177 top=top
178 aria_label=aria_label
179 on_select=Callback::new(move |_| {
180 let hour_24 = if ampm.get_untracked() {
181 to_twenty_four_hour(marker, is_pm.get_untracked())
182 } else {
183 marker
184 };
185 draft_hour_24.set(hour_24);
186 view.set(ClockView::Minutes);
187 })
188 />
189 }
190 })
191 .collect_view()
192}
193
194fn minute_numbers_view(
195 minute_step: Signal<u32>,
196 disabled: Signal<bool>,
197 draft_hour_24: RwSignal<u32>,
198 draft_minute: RwSignal<u32>,
199 view: RwSignal<ClockView>,
200 on_minute_selected: Callback<(u32, u32)>,
201) -> impl IntoView {
202 let markers = minute_display_markers();
203 let count = markers.len();
204 markers
205 .into_iter()
206 .enumerate()
207 .map(|(index, minute)| {
208 let (left, top) = marker_position_style(index, count, false);
209 let label = format!("{minute:02}");
210 let aria_label = format!("{label} minutes");
211 view! {
212 <ClockNumber
213 label=label.clone()
214 selected=Signal::derive(move || {
215 snap_minute(draft_minute.get(), 5) == minute
216 })
217 disabled=Signal::derive(move || {
218 disabled.get()
219 || is_minute_step_disabled(minute, minute_step.get())
220 })
221 left=left
222 top=top
223 aria_label=aria_label
224 on_select=Callback::new(move |_| {
225 draft_minute.set(minute);
226 on_minute_selected.run((draft_hour_24.get_untracked(), minute));
227 view.set(ClockView::Hours);
228 })
229 />
230 }
231 })
232 .collect_view()
233}