mod panel;
mod rule;
mod types;
pub use rule::*;
pub use types::*;
use crate::{FieldInjection, Icon, Input, InputSuffix, Rule};
use chrono::NaiveDate;
use leptos::{html, prelude::*};
use panel::{Panel, PanelRef};
use thaw_components::{Follower, FollowerPlacement};
use thaw_utils::{
class_list, mount_style, now_date, ComponentRef, OptionModel, OptionModelWithValue, SignalWatch,
};
#[component]
pub fn DatePicker(
#[prop(optional, into)] class: MaybeProp<String>,
#[prop(optional, into)] id: MaybeProp<String>,
#[prop(optional, into)]
name: MaybeProp<String>,
#[prop(optional, into)]
rules: Vec<DatePickerRule>,
#[prop(optional, into)]
value: OptionModel<NaiveDate>,
#[prop(optional, into)]
size: Signal<DatePickerSize>,
) -> impl IntoView {
mount_style("date-picker", include_str!("./date-picker.css"));
let (id, name) = FieldInjection::use_id_and_name(id, name);
let validate = Rule::validate(rules, value, name);
let date_picker_ref = NodeRef::<html::Div>::new();
let is_show_panel = RwSignal::new(false);
let show_date_text = RwSignal::new(String::new());
let show_date_format = "%Y-%m-%d";
let update_show_date_text = move || {
value.with_untracked(move |date| {
let text = match date {
OptionModelWithValue::T(v) => v.format(show_date_format).to_string(),
OptionModelWithValue::Option(v) => v.map_or(String::new(), |date| {
date.format(show_date_format).to_string()
}),
};
show_date_text.set(text);
});
};
update_show_date_text();
Effect::new(move |prev: Option<Option<NaiveDate>>| {
let date = value.get();
if date != prev.flatten() {
update_show_date_text();
}
date
});
let panel_ref = ComponentRef::<PanelRef>::default();
let panel_selected_date = RwSignal::new(None::<NaiveDate>);
_ = panel_selected_date.watch(move |date| {
let text = date.map_or(String::new(), |date| {
date.format(show_date_format).to_string()
});
show_date_text.set(text);
});
let on_input_blur = move |_| {
if let Ok(date) =
NaiveDate::parse_from_str(&show_date_text.get_untracked(), show_date_format)
{
if value.get_untracked() != Some(date) {
value.set(Some(date));
}
} else {
update_show_date_text();
}
validate.run(Some(DatePickerRuleTrigger::Blur));
};
let close_panel = move |date: Option<NaiveDate>| {
if value.get_untracked() != date {
if date.is_some() {
value.set(date);
}
update_show_date_text();
}
is_show_panel.set(false);
};
let open_panel = move || {
if is_show_panel.get() {
return;
}
panel_selected_date.set(value.get_untracked());
if let Some(panel_ref) = panel_ref.get_untracked() {
panel_ref.init_panel(value.get_untracked().unwrap_or(now_date()));
}
is_show_panel.set(true);
};
view! {
<crate::_binder::Binder>
<div
node_ref=date_picker_ref
class=class_list!["thaw-date-picker", class]
on:click=move |_| open_panel()
>
<Input
id
name
value=show_date_text
on_focus=move |_| open_panel()
size=Signal::derive(move || size.get().into())
on_blur=on_input_blur
>
<InputSuffix slot>
<Icon icon=icondata_ai::AiCalendarOutlined style="font-size: 18px" />
</InputSuffix>
</Input>
</div>
<Follower slot show=is_show_panel placement=FollowerPlacement::BottomStart>
<Panel
date_picker_ref
close_panel
selected_date=panel_selected_date
comp_ref=panel_ref
/>
</Follower>
</crate::_binder::Binder>
}
}