Skip to main content

orbital_date_pickers/shared/
datetime_locale.rs

1use super::DatetimeLocaleStrings;
2use leptos::prelude::*;
3use orbital_base_components::{DatetimeFormat, DatetimeTimezone, OrbitalDateTime};
4use orbital_macros::component_doc;
5
6/// BCP-47 or Orbital locale identifier for datetime formatting context.
7#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
8pub struct Locale(pub String);
9
10impl Locale {
11    pub fn new(tag: impl Into<String>) -> Self {
12        Self(tag.into())
13    }
14}
15
16impl From<&str> for Locale {
17    fn from(tag: &str) -> Self {
18        Self(tag.to_string())
19    }
20}
21
22/// Locale, format, timezone, reference-date defaults, and localized chrome strings for picker subtrees.
23#[derive(Clone, Debug, PartialEq)]
24pub struct DatetimeLocaleContext {
25    pub locale: Locale,
26    pub strings: DatetimeLocaleStrings,
27    pub default_format: DatetimeFormat,
28    pub default_timezone: DatetimeTimezone,
29    pub reference_date: OrbitalDateTime,
30}
31
32impl Default for DatetimeLocaleContext {
33    fn default() -> Self {
34        let locale = Locale::from("en-US");
35        Self {
36            strings: DatetimeLocaleStrings::for_tag(&locale.0),
37            locale,
38            default_format: DatetimeFormat::default(),
39            default_timezone: DatetimeTimezone::Local,
40            reference_date: OrbitalDateTime::utc_now(DatetimeTimezone::Local).start_of_day(),
41        }
42    }
43}
44
45#[derive(Clone)]
46struct DatetimeLocaleInjection(Memo<DatetimeLocaleContext>);
47
48fn default_locale_signal() -> Signal<Locale> {
49    Signal::from(Locale::from("en-US"))
50}
51
52fn default_format_signal() -> Signal<DatetimeFormat> {
53    Signal::from(DatetimeFormat::default())
54}
55
56fn default_timezone_signal() -> Signal<DatetimeTimezone> {
57    Signal::from(DatetimeTimezone::Local)
58}
59
60fn default_reference_date_signal() -> Signal<OrbitalDateTime> {
61    Signal::from(OrbitalDateTime::utc_now(DatetimeTimezone::Local).start_of_day())
62}
63
64/// Provides locale, format, timezone, and reference-date defaults to picker subtrees.
65///
66/// # Timezone
67///
68/// `default_timezone` supplies the fallback wall-clock zone for child pickers that do not set
69/// `appearance.timezone` explicitly. Prefer explicit `appearance.timezone` on each picker when
70/// the zone is user-controlled (e.g. UTC vs local).
71///
72/// # When to use
73///
74/// - Wrap date/time picker pages so child components share locale defaults
75/// - Set a reference calendar day for time-only pickers
76/// - Share defaults with `SchedulerCalendar` event dialogs in `orbital-scheduler` that compose [`DateTimePicker`](crate::DateTimePicker)
77///
78/// # Usage
79///
80/// Wrap picker content and read defaults via [`use_datetime_locale`].
81///
82/// # Best Practices
83///
84/// ## Do's
85///
86/// - Mount the same `DatetimeLocale` on server and client with matching signals for SSR.
87///
88/// ## Don'ts
89///
90/// - Do not confuse `Locale` (format tag) with scheduler toolbar strings — those use `SchedulerLocaleText` in `orbital-scheduler`.
91///
92/// # Examples
93///
94/// ## Default locale shell
95/// Provides baseline locale context and a calendar using localized weekday headers.
96/// <!-- preview -->
97/// ```rust
98/// use orbital_base_components::{DatetimeFormat, DatetimeTimezone, OrbitalDateTime, TryFromUnixSeconds};
99/// use crate::preview::{PickerPreviewExample, PickerPreviewKnobs};
100/// use crate::DateCalendar;
101/// let locale = Signal::from(Locale::from("fr-FR"));
102/// let default_format = Signal::from(DatetimeFormat::IsoDate);
103/// let default_timezone = Signal::from(DatetimeTimezone::Local);
104/// let reference_date = Signal::from(
105///     OrbitalDateTime::try_from_unix_seconds(1_735_689_600, DatetimeTimezone::Local).expect("valid"),
106/// );
107/// let value = RwSignal::new(None::<OrbitalDateTime>);
108/// view! {
109///     <PickerPreviewExample data_testid="datetime-locale-preview">
110///         <PickerPreviewKnobs />
111///         <DatetimeLocale
112///             locale=locale
113///             default_format=default_format
114///             default_timezone=default_timezone
115///             reference_date=reference_date
116///         >
117///             <DateCalendar bind=value />
118///             <p data-testid="datetime-locale-weekday-sample">
119///                 {move || use_datetime_locale_strings().weekday_header_labels()[0].clone()}
120///             </p>
121///         </DatetimeLocale>
122///     </PickerPreviewExample>
123/// }
124/// ```
125#[component_doc(
126    category = "Calendar & Time",
127    preview_slug = "datetime-locale",
128    preview_label = "Datetime Locale",
129    preview_icon = icondata::AiGlobalOutlined,
130)]
131#[component]
132pub fn DatetimeLocale(
133    /// BCP-47 or Orbital locale id for month names, weekday labels, first day of week.
134    #[prop(default = default_locale_signal())]
135    locale: Signal<Locale>,
136    /// Default display/parse format when appearance does not override.
137    #[prop(default = default_format_signal())]
138    default_format: Signal<DatetimeFormat>,
139    /// Default timezone for new values and parsing.
140    #[prop(default = default_timezone_signal())]
141    default_timezone: Signal<DatetimeTimezone>,
142    /// Reference calendar day for time-only pickers.
143    #[prop(default = default_reference_date_signal())]
144    reference_date: Signal<OrbitalDateTime>,
145    /// Subtree that consumes locale defaults.
146    children: Children,
147) -> impl IntoView {
148    let context = Memo::new(move |_| {
149        let tag = locale.get();
150        DatetimeLocaleContext {
151            locale: tag.clone(),
152            strings: DatetimeLocaleStrings::for_tag(&tag.0),
153            default_format: default_format.get(),
154            default_timezone: default_timezone.get(),
155            reference_date: reference_date.get(),
156        }
157    });
158    provide_context(DatetimeLocaleInjection(context));
159    children()
160}
161
162/// Returns the active [`DatetimeLocaleContext`] when inside [`DatetimeLocale`].
163pub fn use_datetime_locale() -> DatetimeLocaleContext {
164    use_context::<DatetimeLocaleInjection>()
165        .map(|injection| injection.0.get())
166        .unwrap_or_default()
167}
168
169/// Default timezone from the nearest [`DatetimeLocale`] provider.
170pub fn use_default_timezone() -> DatetimeTimezone {
171    use_datetime_locale().default_timezone
172}
173
174/// Returns localized picker chrome strings from the nearest [`DatetimeLocale`] provider.
175pub fn use_datetime_locale_strings() -> DatetimeLocaleStrings {
176    use_datetime_locale().strings
177}
178
179/// Reference calendar day from the nearest [`DatetimeLocale`] provider.
180pub fn use_reference_date() -> OrbitalDateTime {
181    use_datetime_locale().reference_date
182}