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}