impulse_thaw/date_picker/
mod.rs1mod panel;
2mod rule;
3mod types;
4
5pub use rule::*;
6pub use types::*;
7
8use crate::{FieldInjection, Icon, Input, InputSuffix, Rule};
9use chrono::NaiveDate;
10use leptos::{html, prelude::*};
11use panel::{Panel, PanelRef};
12use thaw_components::{Follower, FollowerPlacement};
13use thaw_utils::{
14 class_list, mount_style, now_date, ComponentRef, OptionModel, OptionModelWithValue, SignalWatch,
15};
16
17#[component]
18pub fn DatePicker(
19 #[prop(optional, into)] class: MaybeProp<String>,
20 #[prop(optional, into)] id: MaybeProp<String>,
21 #[prop(optional, into)]
24 name: MaybeProp<String>,
25 #[prop(optional, into)]
27 rules: Vec<DatePickerRule>,
28 #[prop(optional, into)]
30 value: OptionModel<NaiveDate>,
31 #[prop(optional, into)]
33 size: Signal<DatePickerSize>,
34) -> impl IntoView {
35 mount_style("date-picker", include_str!("./date-picker.css"));
36 let (id, name) = FieldInjection::use_id_and_name(id, name);
37 let validate = Rule::validate(rules, value, name);
38 let date_picker_ref = NodeRef::<html::Div>::new();
39 let is_show_panel = RwSignal::new(false);
40 let show_date_text = RwSignal::new(String::new());
41 let show_date_format = "%Y-%m-%d";
42 let update_show_date_text = move || {
43 value.with_untracked(move |date| {
44 let text = match date {
45 OptionModelWithValue::T(v) => v.format(show_date_format).to_string(),
46 OptionModelWithValue::Option(v) => v.map_or(String::new(), |date| {
47 date.format(show_date_format).to_string()
48 }),
49 };
50
51 show_date_text.set(text);
52 });
53 };
54 update_show_date_text();
55 let panel_ref = ComponentRef::<PanelRef>::default();
56 let panel_selected_date = RwSignal::new(None::<NaiveDate>);
57 _ = panel_selected_date.watch(move |date| {
58 let text = date.map_or(String::new(), |date| {
59 date.format(show_date_format).to_string()
60 });
61 show_date_text.set(text);
62 });
63
64 let on_input_blur = move |_| {
65 if let Ok(date) =
66 NaiveDate::parse_from_str(&show_date_text.get_untracked(), show_date_format)
67 {
68 if value.get_untracked() != Some(date) {
69 value.set(Some(date));
70 update_show_date_text();
71 }
72 } else {
73 update_show_date_text();
74 }
75 validate.run(Some(DatePickerRuleTrigger::Blur));
76 };
77
78 let close_panel = move |date: Option<NaiveDate>| {
79 if value.get_untracked() != date {
80 if date.is_some() {
81 value.set(date);
82 }
83 update_show_date_text();
84 }
85 is_show_panel.set(false);
86 };
87
88 let open_panel = move || {
89 if is_show_panel.get() {
90 return;
91 }
92 panel_selected_date.set(value.get_untracked());
93 if let Some(panel_ref) = panel_ref.get_untracked() {
94 panel_ref.init_panel(value.get_untracked().unwrap_or(now_date()));
95 }
96 is_show_panel.set(true);
97 };
98
99 view! {
100 <crate::_binder::Binder>
101 <div
102 node_ref=date_picker_ref
103 class=class_list!["thaw-date-picker", class]
104 on:click=move |_| open_panel()
105 >
106 <Input
107 id
108 name
109 value=show_date_text
110 on_focus=move |_| open_panel()
111 size=Signal::derive(move || size.get().into())
112 on_blur=on_input_blur
113 >
114 <InputSuffix slot>
115 <Icon icon=icondata_ai::AiCalendarOutlined style="font-size: 18px" />
116 </InputSuffix>
117 </Input>
118 </div>
119 <Follower slot show=is_show_panel placement=FollowerPlacement::BottomStart>
120 <Panel
121 date_picker_ref
122 close_panel
123 selected_date=panel_selected_date
124 comp_ref=panel_ref
125 />
126 </Follower>
127 </crate::_binder::Binder>
128 }
129}