Skip to main content

orbital_date_pickers/shared/
picker_styles.rs

1use orbital_theme::Density;
2use std::sync::OnceLock;
3use turf::inline_style_sheet_values;
4
5pub struct PickerClassNames {
6    pub layout: String,
7    pub date_field: String,
8    pub time_field: String,
9    pub datetime_field: String,
10    pub segment: String,
11    pub separator: String,
12}
13
14static PICKER_STYLES: OnceLock<(&'static str, PickerClassNames)> = OnceLock::new();
15
16fn picker_styles() -> (&'static str, PickerClassNames) {
17    let (style_sheet, _) = inline_style_sheet_values! {
18        .orb-picker-layout {
19            display: flex;
20            flex-direction: column;
21            width: fit-content;
22        }
23
24        .orb-date-range-field,
25        .orb-time-range-field,
26        .orb-datetime-range-field {
27            display: inline-flex;
28            align-items: center;
29            gap: var(--orb-space-inline-sm);
30            flex-wrap: wrap;
31        }
32
33        .orb-picker-range-field__separator {
34            color: var(--orb-color-text-secondary);
35            user-select: none;
36            flex-shrink: 0;
37        }
38
39        .orb-picker-datetime-field__row {
40            display: flex;
41            flex-direction: row;
42            flex-wrap: wrap;
43            align-items: flex-start;
44            gap: var(--orb-space-inline-md);
45            width: fit-content;
46            max-width: 100%;
47        }
48
49        .orb-picker-datetime-picker__row {
50            display: flex;
51            flex-direction: row;
52            flex-wrap: wrap;
53            align-items: flex-start;
54            gap: var(--orb-space-inline-md);
55            width: fit-content;
56            max-width: 100%;
57        }
58
59        .orb-picker-datetime-picker__row > .orbital-date-picker,
60        .orb-picker-datetime-picker__row > .orbital-time-picker {
61            width: auto;
62        }
63
64        .orb-picker-datetime-picker__row > .orbital-date-picker {
65            flex: 1 1 280px;
66            min-width: 0;
67        }
68
69        .orb-picker-datetime-picker__row > .orbital-time-picker {
70            flex: 0 1 220px;
71            min-width: 0;
72        }
73
74        .orb-picker-datetime-range-picker__row {
75            display: flex;
76            flex-direction: row;
77            flex-wrap: wrap;
78            align-items: flex-start;
79            gap: var(--orb-space-inline-md);
80            margin-top: var(--orb-space-block-md);
81            width: 100%;
82        }
83
84        .orb-picker-datetime-range-picker__row > .orb-picker-layout {
85            flex: 1 1 320px;
86            min-width: 0;
87        }
88
89        .orb-picker-layout-density-compact .orbital-calendar-item {
90            padding: 4px 6px;
91        }
92
93        .orb-picker-layout-density-compact .orbital-calendar {
94            height: 560px;
95        }
96
97        .orb-picker-layout-density-spacious .orbital-calendar-item {
98            padding: 12px 16px;
99        }
100
101        .orb-picker-layout-density-spacious .orbital-calendar {
102            height: 840px;
103        }
104
105        .orb-picker-shortcuts {
106            margin-bottom: var(--orb-space-block-sm);
107            padding-bottom: var(--orb-space-block-sm);
108            border-bottom: 1px solid var(--orb-color-border-subtle);
109        }
110    };
111
112    let class_names = PickerClassNames {
113        layout: "orb-picker-layout".to_string(),
114        date_field: "orb-date-field".to_string(),
115        time_field: "orb-time-field".to_string(),
116        datetime_field: "orb-datetime-field".to_string(),
117        segment: "orb-date-field__segment".to_string(),
118        separator: "orb-date-field__separator".to_string(),
119    };
120
121    (style_sheet, class_names)
122}
123
124/// Injected picker stylesheet and stable class names.
125pub fn picker_style_sheet() -> &'static str {
126    PICKER_STYLES.get_or_init(picker_styles).0
127}
128
129/// Stable class names for picker shells and segmented fields.
130pub fn picker_class_names() -> &'static PickerClassNames {
131    &PICKER_STYLES.get_or_init(picker_styles).1
132}
133
134/// Density modifier class for picker layout roots.
135pub fn picker_density_class(density: Density) -> &'static str {
136    match density {
137        Density::Compact => "orb-picker-layout--density-compact",
138        Density::Default => "",
139        Density::Spacious => "orb-picker-layout--density-spacious",
140    }
141}
142
143/// Inner density class for descendant selectors on calendar/field nodes.
144pub fn picker_density_inner_class(density: Density) -> &'static str {
145    match density {
146        Density::Compact => "orb-picker-layout-density-compact",
147        Density::Default => "",
148        Density::Spacious => "orb-picker-layout-density-spacious",
149    }
150}
151
152/// Build root layout class list for [`DateTimePicker`].
153pub fn datetime_picker_root_classes(density: Density) -> String {
154    format!(
155        "{} orb-picker-datetime-picker",
156        layout_root_classes(density)
157    )
158}
159
160/// Row class pairing date and time segmented fields side by side.
161pub fn datetime_field_row_class() -> &'static str {
162    "orb-picker-datetime-field__row"
163}
164
165/// Row class pairing date and time pickers side by side.
166pub fn datetime_picker_row_class() -> &'static str {
167    "orb-picker-datetime-picker__row"
168}
169
170/// Row class for start/end datetime pickers in range pickers.
171pub fn datetime_range_picker_row_class() -> &'static str {
172    "orb-picker-datetime-range-picker__row"
173}
174
175/// Build root layout class list for a picker shell.
176pub fn layout_root_classes(density: Density) -> String {
177    let names = picker_class_names();
178    let mut parts = vec![names.layout.clone()];
179    let modifier = picker_density_class(density);
180    if !modifier.is_empty() {
181        parts.push(modifier.to_string());
182    }
183    let inner = picker_density_inner_class(density);
184    if !inner.is_empty() {
185        parts.push(inner.to_string());
186    }
187    parts.join(" ")
188}
189
190/// Map theme density to core [`Input`](orbital_core_components::Input) size modifiers.
191pub fn input_size_class(density: Density) -> &'static str {
192    match density {
193        Density::Compact => "orbital-input--small",
194        Density::Default => "",
195        Density::Spacious => "orbital-input--large",
196    }
197}
198
199/// Build field root class list for combined date-time segmented fields.
200pub fn datetime_field_root_classes(density: Density) -> String {
201    let names = picker_class_names();
202    let mut parts = vec!["orbital-input".to_string(), names.datetime_field.clone()];
203    let size = input_size_class(density);
204    if !size.is_empty() {
205        parts.push(size.to_string());
206    }
207    parts.join(" ")
208}
209
210/// Segment class for datetime combined fields.
211pub fn datetime_segment_class() -> &'static str {
212    "orb-datetime-field__segment"
213}
214
215/// Separator class for datetime combined fields.
216pub fn datetime_separator_class() -> &'static str {
217    "orb-datetime-field__separator"
218}
219
220/// Build field root class list for segmented date/time fields.
221pub fn field_root_classes(is_time: bool, density: Density) -> String {
222    let names = picker_class_names();
223    let field = if is_time {
224        names.time_field.clone()
225    } else {
226        names.date_field.clone()
227    };
228    let mut parts = vec!["orbital-input".to_string(), field];
229    let size = input_size_class(density);
230    if !size.is_empty() {
231        parts.push(size.to_string());
232    }
233    parts.join(" ")
234}
235
236/// Segment and separator classes for time fields (time-specific BEM suffix).
237pub fn time_segment_class() -> &'static str {
238    "orb-time-field__segment"
239}
240
241pub fn time_separator_class() -> &'static str {
242    "orb-time-field__separator"
243}
244
245/// Build root class list for analog time clock surfaces.
246pub fn time_clock_root_classes(density: Density) -> String {
247    let mut parts = vec!["orb-picker-time-clock".to_string()];
248    let modifier = picker_density_class(density);
249    if !modifier.is_empty() {
250        parts.push(modifier.to_string());
251    }
252    let inner = picker_density_inner_class(density);
253    if !inner.is_empty() {
254        parts.push(inner.to_string());
255    }
256    parts.join(" ")
257}
258
259/// Build root class list for range field wrappers.
260pub fn range_field_root_classes(kind: &str, density: Density) -> String {
261    let mut parts = vec![kind.to_string()];
262    let inner = picker_density_inner_class(density);
263    if !inner.is_empty() {
264        parts.push(inner.to_string());
265    }
266    parts.join(" ")
267}