radix_leptos_primitives/components/
calendar.rs1use crate::utils::{merge_classes, generate_id};
2use leptos::callback::Callback;
3use leptos::children::Children;
4use leptos::prelude::*;
5
6#[component]
8pub fn Calendar(
9 #[prop(optional)] class: Option<String>,
10 #[prop(optional)] style: Option<String>,
11 #[prop(optional)] children: Option<Children>,
12 #[prop(optional)] value: Option<String>,
13 #[prop(optional)] min_date: Option<String>,
14 #[prop(optional)] max_date: Option<String>,
15 #[prop(optional)] disabled_dates: Option<Vec<String>>,
16 #[prop(optional)] locale: Option<String>,
17 #[prop(optional)] first_day_of_week: Option<u8>,
18 #[prop(optional)] show_week_numbers: Option<bool>,
19 #[prop(optional)] on_date_select: Option<Callback<String>>,
20 #[prop(optional)] on_month_change: Option<Callback<String>>,
21) -> impl IntoView {
22 let value = value.unwrap_or_default();
23 let min_date = min_date.unwrap_or_default();
24 let max_date = max_date.unwrap_or_default();
25 let disabled_dates = disabled_dates.unwrap_or_default();
26 let locale = locale.unwrap_or_else(|| "en-US".to_string());
27 let first_day_of_week = first_day_of_week.unwrap_or(0);
28 let show_week_numbers = show_week_numbers.unwrap_or(false);
29
30 let class = merge_classes(vec!["calendar", class.as_deref().unwrap_or("")]);
31
32 view! {
33 <div
34 class=class
35 style=style
36 role="grid"
37 aria-label="Calendar"
38 data-locale=locale
39 data-first-day-of-week=first_day_of_week
40 data-show-week-numbers=show_week_numbers
41 >
42 {children.map(|c| c())}
43 </div>
44 }
45}
46
47#[component]
49pub fn CalendarHeader(
50 #[prop(optional)] class: Option<String>,
51 #[prop(optional)] style: Option<String>,
52 #[prop(optional)] children: Option<Children>,
53 #[prop(optional)] month: Option<String>,
54 #[prop(optional)] year: Option<i32>,
55 #[prop(optional)] on_previous_month: Option<Callback<()>>,
56 #[prop(optional)] on_next_month: Option<Callback<()>>,
57) -> impl IntoView {
58 let month = month.unwrap_or_else(|| "January".to_string());
59 let year = year.unwrap_or(2024);
60
61 let class = merge_classes(vec!["calendar-header", class.as_deref().unwrap_or("")]);
62
63 view! {
64 <div
65 class=class
66 style=style
67 role="banner"
68 aria-label="Calendar header"
69 >
70 {children.map(|c| c())}
71 </div>
72 }
73}
74
75#[component]
77pub fn CalendarNavigation(
78 #[prop(optional)] class: Option<String>,
79 #[prop(optional)] style: Option<String>,
80 #[prop(optional)] on_previous: Option<Callback<()>>,
81 #[prop(optional)] on_next: Option<Callback<()>>,
82 #[prop(optional)] on_today: Option<Callback<()>>,
83) -> impl IntoView {
84 let class = merge_classes(vec!["calendar-navigation", class.as_deref().unwrap_or("")]);
85
86 view! {
87 <div
88 class=class
89 style=style
90 role="navigation"
91 aria-label="Calendar navigation"
92 >
93 <button
94 class="calendar-nav-previous"
95 type="button"
96 aria-label="Previous month"
97 on:click=move |_| {
98 if let Some(callback) = on_previous {
99 callback.run(());
100 }
101 }
102 >
103 "‹"
104 </button>
105 <button
106 class="calendar-nav-today"
107 type="button"
108 aria-label="Go to today"
109 on:click=move |_| {
110 if let Some(callback) = on_today {
111 callback.run(());
112 }
113 }
114 >
115 "Today"
116 </button>
117 <button
118 class="calendar-nav-next"
119 type="button"
120 aria-label="Next month"
121 on:click=move |_| {
122 if let Some(callback) = on_next {
123 callback.run(());
124 }
125 }
126 >
127 "›"
128 </button>
129 </div>
130 }
131}
132
133#[component]
135pub fn CalendarGrid(
136 #[prop(optional)] class: Option<String>,
137 #[prop(optional)] style: Option<String>,
138 #[prop(optional)] children: Option<Children>,
139 #[prop(optional)] month: Option<String>,
140 #[prop(optional)] year: Option<i32>,
141) -> impl IntoView {
142 let month = month.unwrap_or_else(|| "January".to_string());
143 let year = year.unwrap_or(2024);
144
145 let class = merge_classes(vec!["calendar-grid", class.as_deref().unwrap_or("")]);
146
147 view! {
148 <div
149 class=class
150 style=style
151 role="grid"
152 aria-label=format!("Calendar for {} {}", month, year)
153 >
154 {children.map(|c| c())}
155 </div>
156 }
157}
158
159#[component]
161pub fn CalendarDay(
162 #[prop(optional)] class: Option<String>,
163 #[prop(optional)] style: Option<String>,
164 #[prop(optional)] children: Option<Children>,
165 #[prop(optional)] date: Option<String>,
166 #[prop(optional)] day: Option<u8>,
167 #[prop(optional)] is_today: Option<bool>,
168 #[prop(optional)] isselected: Option<bool>,
169 #[prop(optional)] isdisabled: Option<bool>,
170 #[prop(optional)] is_other_month: Option<bool>,
171 #[prop(optional)] on_click: Option<Callback<String>>,
172) -> impl IntoView {
173 let date = date.unwrap_or_default();
174 let day = day.unwrap_or(1);
175 let is_today = is_today.unwrap_or(false);
176 let isselected = isselected.unwrap_or(false);
177 let isdisabled = isdisabled.unwrap_or(false);
178 let is_other_month = is_other_month.unwrap_or(false);
179
180 let class = merge_classes(vec!["calendar-day", class.as_deref().unwrap_or("")]);
181
182 let handle_click = move |_| {
183 if !isdisabled {
184 if let Some(callback) = on_click {
185 callback.run(date.clone());
186 }
187 }
188 };
189
190 view! {
191 <button
192 class=class
193 style=style
194 type="button"
195 disabled=isdisabled
196 aria-label=format!("{}", day)
197 aria-selected=isselected
198 on:click=handle_click
199 >
200 {children.map(|c| c())}
201 </button>
202 }
203}
204
205#[component]
207pub fn CalendarWeekHeader(
208 #[prop(optional)] class: Option<String>,
209 #[prop(optional)] style: Option<String>,
210 #[prop(optional)] locale: Option<String>,
211 #[prop(optional)] first_day_of_week: Option<u8>,
212) -> impl IntoView {
213 let locale = locale.unwrap_or_else(|| "en-US".to_string());
214 let first_day_of_week = first_day_of_week.unwrap_or(0);
215
216 let class = merge_classes(vec!["calendar-week-header", class.as_deref().unwrap_or("")]);
217
218 view! {
219 <div
220 class=class
221 style=style
222 role="row"
223 aria-label="Week header"
224 data-locale=locale
225 data-first-day-of-week=first_day_of_week
226 >
227 </div>
229 }
230}
231
232#[cfg(test)]
235mod tests {
236 use proptest::prelude::*;
237 use wasm_bindgen_test::*;
238
239 wasm_bindgen_test_configure!(run_in_browser);
240
241 #[test]
243 fn test_calendar_creation() {}
244 #[test]
245 fn test_calendar_with_class() {}
246 #[test]
247 fn test_calendar_with_style() {}
248 #[test]
249 fn test_calendar_with_value() {}
250 #[test]
251 fn test_calendar_min_max_dates() {}
252 #[test]
253 fn test_calendardisabled_dates() {}
254 #[test]
255 fn test_calendar_locale() {}
256 #[test]
257 fn test_calendar_first_day_of_week() {}
258 #[test]
259 fn test_calendar_show_week_numbers() {}
260 #[test]
261 fn test_calendar_on_date_select() {}
262 #[test]
263 fn test_calendar_on_month_change() {}
264
265 #[test]
267 fn test_calendar_header_creation() {}
268 #[test]
269 fn test_calendar_header_with_class() {}
270 #[test]
271 fn test_calendar_header_month_year() {}
272 #[test]
273 fn test_calendar_header_navigation() {}
274
275 #[test]
277 fn test_calendar_navigation_creation() {}
278 #[test]
279 fn test_calendar_navigation_with_class() {}
280 #[test]
281 fn test_calendar_navigation_previous() {}
282 #[test]
283 fn test_calendar_navigation_next() {}
284 #[test]
285 fn test_calendar_navigation_today() {}
286
287 #[test]
289 fn test_calendar_grid_creation() {}
290 #[test]
291 fn test_calendar_grid_with_class() {}
292 #[test]
293 fn test_calendar_grid_month_year() {}
294
295 #[test]
297 fn test_calendar_day_creation() {}
298 #[test]
299 fn test_calendar_day_with_class() {}
300 #[test]
301 fn test_calendar_day_date() {}
302 #[test]
303 fn test_calendar_day_today() {}
304 #[test]
305 fn test_calendar_dayselected() {}
306 #[test]
307 fn test_calendar_daydisabled() {}
308 #[test]
309 fn test_calendar_day_other_month() {}
310 #[test]
311 fn test_calendar_day_on_click() {}
312
313 #[test]
315 fn test_calendar_week_header_creation() {}
316 #[test]
317 fn test_calendar_week_header_with_class() {}
318 #[test]
319 fn test_calendar_week_header_locale() {}
320 #[test]
321 fn test_calendar_week_header_first_day() {}
322
323 #[test]
325 fn test_merge_classes_empty() {}
326 #[test]
327 fn test_merge_classes_single() {}
328 #[test]
329 fn test_merge_classes_multiple() {}
330 #[test]
331 fn test_merge_classes_with_empty() {}
332
333 #[test]
335 fn test_calendar_property_based() {
336 proptest!(|(____class in ".*", __style in ".*")| {
337
338 });
339 }
340
341 #[test]
342 fn test_calendar_date_validation() {
343 proptest!(|(____year in 1900..2100i32, __month in 1..12u8, __day in 1..31u8)| {
344
345 });
346 }
347
348 #[test]
349 fn test_calendar_locale_validation() {
350 proptest!(|(____locale in ".*")| {
351
352 });
353 }
354
355 #[test]
357 fn test_calendar_user_interaction() {}
358 #[test]
359 fn test_calendar_accessibility() {}
360 #[test]
361 fn test_calendar_keyboard_navigation() {}
362 #[test]
363 fn test_calendar_month_navigation() {}
364 #[test]
365 fn test_calendar_date_selection() {}
366
367 #[test]
369 fn test_calendar_large_date_ranges() {}
370 #[test]
371 fn test_calendar_render_performance() {}
372 #[test]
373 fn test_calendar_memory_usage() {}
374 #[test]
375 fn test_calendar_navigation_performance() {}
376}