impulse_thaw/date_picker/
mod.rs

1mod 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    /// A string specifying a name for the input control.
22    /// This name is submitted along with the control's value when the form data is submitted.
23    #[prop(optional, into)]
24    name: MaybeProp<String>,
25    /// The rules to validate Field.
26    #[prop(optional, into)]
27    rules: Vec<DatePickerRule>,
28    /// Set the date picker value.
29    #[prop(optional, into)]
30    value: OptionModel<NaiveDate>,
31    /// Size of the input.
32    #[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}