Skip to main content

orbital_date_pickers/pickers/
date_time_picker.rs

1//! [`DateTimePicker`] — combined date and time pickers on one [`OrbitalDateTime`] bind.
2
3use leptos::prelude::*;
4use orbital_core_components::{DatePicker, DatePickerAppearance, TimePicker, TimePickerAppearance};
5use orbital_macros::component_doc;
6use orbital_theme::use_theme_options;
7
8use crate::building_blocks::{DateTimePickerAppearance, DateTimePickerBind};
9use crate::shared::{
10    datetime_picker_root_classes, datetime_picker_row_class, picker_style_sheet,
11    use_datetime_coordinator,
12};
13
14/// Side-by-side date and time pickers sharing one [`OrbitalDateTime`] bind.
15///
16/// DateTimePicker composes core [`DatePicker`](orbital_core_components::DatePicker) and
17/// [`TimePicker`](orbital_core_components::TimePicker). Changing the calendar day preserves the
18/// existing time-of-day. For keyboard segment entry without popovers, use [`DateTimeField`](crate::DateTimeField).
19///
20/// # When to use
21///
22/// - Event scheduling forms (start/end datetime)
23/// - Flows that need both calendar and scroll-column time selection
24/// - Combined date and time selection with calendar and scroll-column pickers
25///
26/// # Usage
27///
28/// 1. Bind `Option<OrbitalDateTime>` via [`DateTimePickerBind`].
29/// 2. Set `appearance.date_format` and `appearance.time_format` for locale masks.
30/// 3. Wrap in [`DatetimeLocale`](crate::DatetimeLocale) for shared defaults.
31///
32/// # Examples
33///
34/// ## Date and time pickers
35/// Default US date + 12-hour time with bind readout for E2E.
36/// <!-- preview -->
37/// ```rust
38/// use orbital_base_components::{OrbitalDateTime, ToUnixSeconds};
39/// use crate::preview::{PickerPreviewExample, PickerPreviewKnobs};
40/// let value = RwSignal::new(None::<OrbitalDateTime>);
41/// view! {
42///     <PickerPreviewExample data_testid="date-time-picker-preview">
43///         <PickerPreviewKnobs />
44///         <DateTimePicker bind=value />
45///         <div data-testid="date-time-picker-preview-VALUE">{move || value.get().map(|v| v.to_unix_seconds().to_string()).unwrap_or_else(|| "none".to_string())}</div>
46///     </PickerPreviewExample>
47/// }
48/// ```
49///
50/// ## Bind readout
51/// Selecting date and time updates the bound [`OrbitalDateTime`].
52/// <!-- preview -->
53/// ```rust
54/// use orbital_base_components::{OrbitalDateTime, ToUnixSeconds};
55/// use crate::preview::PickerPreviewExample;
56/// let value = RwSignal::new(None::<OrbitalDateTime>);
57/// view! {
58///     <PickerPreviewExample data_testid="DTP-02">
59///         <DateTimePicker bind=value />
60///         <div data-testid="DTP-02-VALUE">{move || value.get().map(|v| v.to_unix_seconds().to_string()).unwrap_or_else(|| "none".to_string())}</div>
61///     </PickerPreviewExample>
62/// }
63/// ```
64///
65/// ## 24-hour time
66/// Date picker with 24-hour scroll columns.
67/// <!-- preview -->
68/// ```rust
69/// use orbital_base_components::OrbitalDateTime;
70/// use crate::preview::PickerPreviewExample;
71/// let value = RwSignal::new(None::<OrbitalDateTime>);
72/// view! {
73///     <PickerPreviewExample data_testid="DTP-03">
74///         <DateTimePicker bind=value appearance=DateTimePickerAppearance::time24() />
75///     </PickerPreviewExample>
76/// }
77/// ```
78///
79/// ## Disabled
80/// Both pickers are non-interactive.
81/// <!-- preview -->
82/// ```rust
83/// use orbital_base_components::OrbitalDateTime;
84/// use crate::preview::PickerPreviewExample;
85/// let value = RwSignal::new(None::<OrbitalDateTime>);
86/// view! {
87///     <PickerPreviewExample data_testid="DTP-04">
88///         <DateTimePicker bind=value appearance=DateTimePickerAppearance { disabled: Signal::from(true), ..Default::default() } />
89///     </PickerPreviewExample>
90/// }
91/// ```
92#[component_doc(
93    category = "Calendar & Time",
94    preview_slug = "date-time-picker",
95    preview_label = "Date Time Picker",
96    preview_icon = icondata::AiCalendarOutlined,
97)]
98#[component]
99pub fn DateTimePicker(
100    /// Value binding for the combined date-time pickers.
101    #[prop(optional, into)]
102    bind: DateTimePickerBind,
103    /// Date format, time format, timezone, and disabled state.
104    #[prop(optional, into)]
105    appearance: DateTimePickerAppearance,
106    /// Optional CSS class on the layout wrapper.
107    #[prop(optional, into)]
108    class: MaybeProp<String>,
109) -> impl IntoView {
110    let DateTimePickerBind { value, id, name } = bind;
111    let DateTimePickerAppearance {
112        date_format,
113        time_format,
114        timezone,
115        disabled,
116        placement,
117    } = appearance;
118
119    let locale = crate::use_datetime_locale();
120    let theme_options = use_theme_options();
121    let fallback_reference = Signal::derive(move || locale.reference_date);
122    let coordinator = use_datetime_coordinator(value, id, name, fallback_reference);
123
124    let date_appearance = DatePickerAppearance {
125        format: date_format,
126        timezone,
127        disabled,
128        placement,
129        ..Default::default()
130    };
131    let time_appearance = TimePickerAppearance {
132        format: time_format,
133        reference_date: coordinator.reference_date,
134        timezone,
135        disabled,
136    };
137
138    let root_class = move || {
139        let mut parts = vec![datetime_picker_root_classes(theme_options.get().density)];
140        if let Some(extra) = class.get() {
141            if !extra.is_empty() {
142                parts.push(extra);
143            }
144        }
145        parts.join(" ")
146    };
147
148    view! {
149        <style>{picker_style_sheet()}</style>
150        <div class=root_class data-orbital-picker="">
151            <div class=datetime_picker_row_class()>
152                <DatePicker bind=coordinator.date_bind appearance=date_appearance />
153                <TimePicker bind=coordinator.time_bind appearance=time_appearance />
154            </div>
155        </div>
156    }
157}