Skip to main content

orbital_date_pickers/building_blocks/time_clock/
dial.rs

1use 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}