mod styles;
use leptos::prelude::*;
use orbital_base_components::{ListNavigationMode, ListSelectionMode};
use orbital_core_components::{List, ListItem, ScrollArea};
use orbital_macros::component_doc;
use orbital_theme::use_theme_options;
use crate::shared::{
commit_time, format_slot_label, generate_time_slots, picker_style_sheet, resolve_anchor,
use_datetime_locale,
};
use super::field_types::{DigitalClockAppearance, DigitalClockBind};
use styles::digital_clock_styles;
#[component_doc(
category = "Calendar & Time",
preview_slug = "digital-clock",
preview_label = "Digital Clock",
preview_icon = icondata::AiFieldTimeOutlined,
)]
#[component]
pub fn DigitalClock(
#[prop(optional, into)]
bind: DigitalClockBind,
#[prop(optional, into)]
appearance: DigitalClockAppearance,
#[prop(optional, into)]
class: MaybeProp<String>,
) -> impl IntoView {
let DigitalClockBind { value } = bind;
let DigitalClockAppearance {
time_step,
ampm,
reference_date,
timezone,
disabled,
} = appearance;
let value = StoredValue::new(value);
let locale = use_datetime_locale();
let theme_options = use_theme_options();
let resolved_timezone = Signal::derive(move || timezone.get());
let resolved_reference = Signal::derive(move || reference_date.get().start_of_day());
let slots = Signal::derive(move || generate_time_slots(time_step.get().max(1)));
let selected_slot = Signal::derive(move || {
value
.get_value()
.get()
.and_then(|dt| dt.hour_minute_second())
.map(|(h, m, _)| (h, m))
});
let selection_label = Signal::derive(move || {
value
.get_value()
.get()
.and_then(|dt| dt.hour_minute_second())
.map(|(hour, minute, _)| format_slot_label(hour, minute, ampm.get()))
});
let root_class = move || {
let mut parts = vec!["orb-picker-digital-clock".to_string()];
match theme_options.get().density {
orbital_theme::Density::Compact => {
parts.push("orb-picker-digital-clock--density-compact".to_string())
}
orbital_theme::Density::Spacious => {
parts.push("orb-picker-digital-clock--density-spacious".to_string())
}
orbital_theme::Density::Default => {}
}
if let Some(extra) = class.get() {
if !extra.is_empty() {
parts.push(extra);
}
}
let _ = locale.locale;
parts.join(" ")
};
let select_slot = move |hour: u32, minute: u32| {
if disabled.get_untracked() {
return;
}
let anchor = resolve_anchor(
value.get_value().get_untracked(),
resolved_reference.get_untracked(),
resolved_timezone.get_untracked(),
);
if let Some(committed) =
commit_time(anchor, hour, minute, 0, resolved_timezone.get_untracked())
{
value.get_value().set(Some(committed));
}
};
view! {
<style>{digital_clock_styles()}</style>
<style>{picker_style_sheet()}</style>
<div class=root_class data-orbital-picker="" role="group" aria-label="Digital clock">
<div
class=move || {
if selection_label.get().is_some() {
"orb-picker-digital-clock__readout".to_string()
} else {
"orb-picker-digital-clock__readout orb-picker-digital-clock__readout--placeholder".to_string()
}
}
aria-live="polite"
>
{move || selection_label.get().unwrap_or_else(|| "Select a time".to_string())}
</div>
<ScrollArea class="orb-picker-digital-clock__scroll">
<List
navigation_mode=ListNavigationMode::Nav
selection_mode=ListSelectionMode::Single
>
{move || {
let use_ampm = ampm.get();
slots
.get()
.into_iter()
.map(|(hour, minute)| {
let label = format_slot_label(hour, minute, use_ampm);
let is_selected = selected_slot.get() == Some((hour, minute));
view! {
<ListItem
class="orb-picker-digital-clock__item"
selected=Signal::from(is_selected)
on_click=Callback::new(move |_| select_slot(hour, minute))
>
{label}
</ListItem>
}
})
.collect_view()
}}
</List>
</ScrollArea>
</div>
}
}