mirror of
https://github.com/adoyle0/thaw.git
synced 2025-01-22 22:09:22 -05:00
Feat/date picker (#53)
* feat: add date picker component * feat: add date panel * feat: add month panel * feat: add month panel logic * feat: add year panel * style: date picker component * feat: optimized date picker style * fix: nightly mode build
This commit is contained in:
parent
89ee294040
commit
6c9e0c397f
17 changed files with 887 additions and 8 deletions
|
@ -35,6 +35,9 @@ icondata = { version = "0.1.0", features = [
|
|||
"AiMinusOutlined",
|
||||
"AiRightOutlined",
|
||||
"AiClockCircleOutlined",
|
||||
"AiCalendarOutlined",
|
||||
"AiArrowLeftOutlined",
|
||||
"AiArrowRightOutlined",
|
||||
] }
|
||||
icondata_core = "0.0.2"
|
||||
uuid = { version = "1.5.0", features = ["v4"] }
|
||||
|
|
|
@ -80,6 +80,7 @@ fn TheRouter(is_routing: RwSignal<bool>) -> impl IntoView {
|
|||
<Route path="/typography" view=TypographyPage/>
|
||||
<Route path="/calendar" view=CalendarPage/>
|
||||
<Route path="/time-picker" view=TimePickerPage/>
|
||||
<Route path="/date-picker" view=DatePickerPage/>
|
||||
</Route>
|
||||
<Route path="/mobile/tabbar" view=TabbarDemoPage/>
|
||||
<Route path="/mobile/nav-bar" view=NavBarDemoPage/>
|
||||
|
|
|
@ -126,13 +126,17 @@ pub(crate) fn gen_menu_data() -> Vec<MenuGroupOption> {
|
|||
value: "checkbox".into(),
|
||||
label: "Checkbox".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
value: "date-picker".into(),
|
||||
label: "Date Picker".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
value: "input".into(),
|
||||
label: "Input".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
value: "input-number".into(),
|
||||
label: "InputNumber".into(),
|
||||
label: "Input Number".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
value: "radio".into(),
|
||||
|
|
58
demo/src/pages/date_picker/mod.rs
Normal file
58
demo/src/pages/date_picker/mod.rs
Normal file
|
@ -0,0 +1,58 @@
|
|||
use crate::components::{Demo, DemoCode};
|
||||
use leptos::*;
|
||||
use prisms::highlight_str;
|
||||
use thaw::chrono::prelude::*;
|
||||
use thaw::*;
|
||||
|
||||
#[component]
|
||||
pub fn DatePickerPage() -> impl IntoView {
|
||||
let value = create_rw_signal(Some(Local::now().date_naive()));
|
||||
view! {
|
||||
<div style="width: 896px; margin: 0 auto;">
|
||||
<h1>"Date Picker"</h1>
|
||||
<Demo>
|
||||
<DatePicker value/>
|
||||
<DemoCode slot>
|
||||
|
||||
{highlight_str!(
|
||||
r#"
|
||||
use thaw::chrono::prelude::*;
|
||||
|
||||
let value = create_rw_signal(Some(Local::now().date_naive()));
|
||||
view! {
|
||||
<DatePicker value/>
|
||||
}
|
||||
"#,
|
||||
"rust"
|
||||
)}
|
||||
|
||||
</DemoCode>
|
||||
</Demo>
|
||||
<h3>"DatePicker Props"</h3>
|
||||
<Table single_column=true>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>"Name"</th>
|
||||
<th>"Type"</th>
|
||||
<th>"Default"</th>
|
||||
<th>"Description"</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>"value"</td>
|
||||
<td>
|
||||
<Text code=true>"RwSignal<Option<NaiveDate>>"</Text>
|
||||
</td>
|
||||
<td>
|
||||
<Text code=true>"Default::default()"</Text>
|
||||
</td>
|
||||
<td>
|
||||
"Set the date picker value"
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</Table>
|
||||
</div>
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ mod card;
|
|||
mod checkbox;
|
||||
mod color_picker;
|
||||
mod components;
|
||||
mod date_picker;
|
||||
mod divider;
|
||||
mod grid;
|
||||
mod guide;
|
||||
|
@ -53,6 +54,7 @@ pub use card::*;
|
|||
pub use checkbox::*;
|
||||
pub use color_picker::*;
|
||||
pub use components::*;
|
||||
pub use date_picker::*;
|
||||
pub use divider::*;
|
||||
pub use grid::*;
|
||||
pub use guide::*;
|
||||
|
|
|
@ -208,21 +208,21 @@ fn CalendarItem(
|
|||
}
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
enum CalendarItemDate {
|
||||
pub(crate) enum CalendarItemDate {
|
||||
Previous(NaiveDate),
|
||||
Current(NaiveDate),
|
||||
Next(NaiveDate),
|
||||
}
|
||||
|
||||
impl CalendarItemDate {
|
||||
fn is_other_month(&self) -> bool {
|
||||
pub fn is_other_month(&self) -> bool {
|
||||
match self {
|
||||
CalendarItemDate::Previous(_) | CalendarItemDate::Next(_) => true,
|
||||
CalendarItemDate::Current(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_today(&self) -> bool {
|
||||
pub fn is_today(&self) -> bool {
|
||||
let date = self.deref();
|
||||
let now_date = now_date();
|
||||
&now_date == date
|
||||
|
@ -241,6 +241,6 @@ impl Deref for CalendarItemDate {
|
|||
}
|
||||
}
|
||||
|
||||
fn now_date() -> NaiveDate {
|
||||
pub(crate) fn now_date() -> NaiveDate {
|
||||
Local::now().date_naive()
|
||||
}
|
||||
|
|
174
src/date_picker/date-picker.css
Normal file
174
src/date_picker/date-picker.css
Normal file
|
@ -0,0 +1,174 @@
|
|||
.thaw-date-picker-panel {
|
||||
width: 300px;
|
||||
background-color: var(--thaw-background-color);
|
||||
border-radius: 3px;
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.12),
|
||||
0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 9px 28px 8px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.thaw-date-picker-date-panel__calendar {
|
||||
padding: 6px 12px 4px;
|
||||
}
|
||||
|
||||
.thaw-date-picker-date-panel__header {
|
||||
display: grid;
|
||||
grid-template-columns: 28px 28px 1fr 28px 28px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.thaw-date-picker-date-panel__header > button {
|
||||
color: var(--thaw-font-color-other-month);
|
||||
}
|
||||
|
||||
.thaw-date-picker-date-panel__header-month-year {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.thaw-date-picker-date-panel__weekdays,
|
||||
.thaw-date-picker-date-panel__dates {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, minmax(0, 1fr));
|
||||
grid-auto-rows: 1fr;
|
||||
}
|
||||
|
||||
.thaw-date-picker-date-panel__weekdays {
|
||||
border-bottom: 1px solid var(--thaw-item-border-color);
|
||||
margin-bottom: 2px;
|
||||
padding: 6px 4px;
|
||||
}
|
||||
|
||||
.thaw-date-picker-date-panel__weekdays span,
|
||||
.thaw-date-picker-date-panel__item {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.thaw-date-picker-date-panel__item {
|
||||
padding: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.thaw-date-picker-date-panel__item--other-month {
|
||||
color: var(--thaw-font-color-other-month);
|
||||
}
|
||||
|
||||
.thaw-date-picker-date-panel__item-day {
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.thaw-date-picker-date-panel__item-sup {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 0;
|
||||
height: 4px;
|
||||
width: 4px;
|
||||
border-radius: 2px;
|
||||
background-color: var(--thaw-background-color-today);
|
||||
}
|
||||
|
||||
.thaw-date-picker-date-panel__item:hover
|
||||
.thaw-date-picker-date-panel__item-day {
|
||||
background-color: var(--thaw-item-background-color-hover);
|
||||
}
|
||||
|
||||
.thaw-date-picker-date-panel__item--selected
|
||||
.thaw-date-picker-date-panel__item-day {
|
||||
background-color: var(--thaw-background-color-today) !important;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.thaw-date-picker-date-panel__footer {
|
||||
padding: 8px 12px;
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-top: 1px solid var(--thaw-item-border-color);
|
||||
}
|
||||
|
||||
.thaw-date-picker-date-panel__header-year {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.thaw-date-picker-month-panel__header {
|
||||
display: grid;
|
||||
grid-template-columns: 28px 1fr 28px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid var(--thaw-item-border-color);
|
||||
padding: 2px 6px;
|
||||
}
|
||||
|
||||
.thaw-date-picker-year-panel__header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid var(--thaw-item-border-color);
|
||||
padding: 4px 6px;
|
||||
}
|
||||
|
||||
.thaw-date-picker-year-panel__header > button,
|
||||
.thaw-date-picker-month-panel__header > button {
|
||||
color: var(--thaw-font-color-other-month);
|
||||
}
|
||||
|
||||
.thaw-date-picker-year-panel__years,
|
||||
.thaw-date-picker-month-panel__months {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
grid-auto-rows: 1fr;
|
||||
padding: 4px 0 6px;
|
||||
}
|
||||
|
||||
.thaw-date-picker-year-panel__item:first-child,
|
||||
.thaw-date-picker-year-panel__item:last-child {
|
||||
color: var(--thaw-font-color-other-month);
|
||||
}
|
||||
|
||||
.thaw-date-picker-year-panel__item,
|
||||
.thaw-date-picker-month-panel__item {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.thaw-date-picker-year-panel__item--selected
|
||||
.thaw-date-picker-year-panel__item-year,
|
||||
.thaw-date-picker-month-panel__item--selected
|
||||
.thaw-date-picker-month-panel__item-month {
|
||||
background-color: var(--thaw-background-color-today) !important;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.thaw-date-picker-year-panel__item:hover
|
||||
.thaw-date-picker-year-panel__item-year,
|
||||
.thaw-date-picker-month-panel__item:hover
|
||||
.thaw-date-picker-month-panel__item-month {
|
||||
background-color: var(--thaw-item-background-color-hover);
|
||||
}
|
||||
|
||||
.thaw-date-picker-year-panel__item-year,
|
||||
.thaw-date-picker-month-panel__item-month {
|
||||
width: 52px;
|
||||
line-height: 24px;
|
||||
height: 24px;
|
||||
text-align: center;
|
||||
border-radius: 3px;
|
||||
}
|
89
src/date_picker/mod.rs
Normal file
89
src/date_picker/mod.rs
Normal file
|
@ -0,0 +1,89 @@
|
|||
mod panel;
|
||||
mod theme;
|
||||
|
||||
use crate::{
|
||||
chrono::NaiveDate,
|
||||
components::{Binder, Follower, FollowerPlacement},
|
||||
utils::{mount_style, now_date, ComponentRef},
|
||||
AiIcon, Icon, Input, InputSuffix, SignalWatch,
|
||||
};
|
||||
use leptos::*;
|
||||
use panel::{Panel, PanelRef};
|
||||
pub use theme::DatePickerTheme;
|
||||
|
||||
#[component]
|
||||
pub fn DatePicker(#[prop(optional, into)] value: RwSignal<Option<NaiveDate>>) -> impl IntoView {
|
||||
mount_style("date-picker", include_str!("./date-picker.css"));
|
||||
let date_picker_ref = create_node_ref::<html::Div>();
|
||||
let is_show_panel = create_rw_signal(false);
|
||||
let show_date_text = create_rw_signal(String::new());
|
||||
let show_date_format = "%Y-%m-%d";
|
||||
let update_show_date_text = move || {
|
||||
value.with_untracked(move |date| {
|
||||
let text = date.as_ref().map_or(String::new(), |date| {
|
||||
date.format(show_date_format).to_string()
|
||||
});
|
||||
show_date_text.set(text);
|
||||
});
|
||||
};
|
||||
update_show_date_text();
|
||||
let panel_ref = ComponentRef::<PanelRef>::default();
|
||||
let panel_selected_date = create_rw_signal(None::<NaiveDate>);
|
||||
_ = panel_selected_date.watch(move |date| {
|
||||
let text = date.as_ref().map_or(String::new(), |date| {
|
||||
date.format(show_date_format).to_string()
|
||||
});
|
||||
show_date_text.set(text);
|
||||
});
|
||||
|
||||
let on_input_blur = Callback::new(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));
|
||||
update_show_date_text();
|
||||
}
|
||||
} else {
|
||||
update_show_date_text();
|
||||
}
|
||||
});
|
||||
|
||||
let close_panel = Callback::new(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 = Callback::new(move |_| {
|
||||
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! {
|
||||
<Binder target_ref=date_picker_ref>
|
||||
<div ref=date_picker_ref>
|
||||
<Input value=show_date_text on_focus=open_panel on_blur=on_input_blur>
|
||||
<InputSuffix slot>
|
||||
<Icon icon=Icon::from(AiIcon::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>
|
||||
</Binder>
|
||||
}
|
||||
}
|
194
src/date_picker/panel/date_panel.rs
Normal file
194
src/date_picker/panel/date_panel.rs
Normal file
|
@ -0,0 +1,194 @@
|
|||
use super::PanelVariant;
|
||||
use crate::{
|
||||
chrono::{Datelike, Days, Month, Months, NaiveDate},
|
||||
utils::now_date,
|
||||
AiIcon, Button, ButtonSize, ButtonVariant, CalendarItemDate,
|
||||
};
|
||||
use leptos::*;
|
||||
use std::ops::Deref;
|
||||
|
||||
#[component]
|
||||
pub fn DatePanel(
|
||||
value: RwSignal<Option<NaiveDate>>,
|
||||
show_date: RwSignal<NaiveDate>,
|
||||
close_panel: Callback<Option<NaiveDate>>,
|
||||
panel_variant: RwSignal<PanelVariant>,
|
||||
) -> impl IntoView {
|
||||
let dates = create_memo(move |_| {
|
||||
let show_date = show_date.get();
|
||||
let show_date_month = show_date.month();
|
||||
let mut dates = vec![];
|
||||
|
||||
let mut current_date = show_date;
|
||||
let mut current_weekday_number = None::<u32>;
|
||||
loop {
|
||||
let date = current_date - Days::new(1);
|
||||
if date.month() != show_date_month {
|
||||
if current_weekday_number.is_none() {
|
||||
current_weekday_number = Some(current_date.weekday().num_days_from_sunday());
|
||||
}
|
||||
let weekday_number = current_weekday_number.unwrap();
|
||||
if weekday_number == 0 {
|
||||
break;
|
||||
}
|
||||
current_weekday_number = Some(weekday_number - 1);
|
||||
|
||||
dates.push(CalendarItemDate::Previous(date));
|
||||
} else {
|
||||
dates.push(CalendarItemDate::Current(date));
|
||||
}
|
||||
current_date = date;
|
||||
}
|
||||
dates.reverse();
|
||||
dates.push(CalendarItemDate::Current(show_date));
|
||||
current_date = show_date;
|
||||
current_weekday_number = None;
|
||||
loop {
|
||||
let date = current_date + Days::new(1);
|
||||
if date.month() != show_date_month {
|
||||
if current_weekday_number.is_none() {
|
||||
current_weekday_number = Some(current_date.weekday().num_days_from_sunday());
|
||||
}
|
||||
let weekday_number = current_weekday_number.unwrap();
|
||||
if weekday_number == 6 {
|
||||
break;
|
||||
}
|
||||
current_weekday_number = Some(weekday_number + 1);
|
||||
dates.push(CalendarItemDate::Next(date));
|
||||
} else {
|
||||
dates.push(CalendarItemDate::Current(date));
|
||||
}
|
||||
current_date = date;
|
||||
}
|
||||
dates
|
||||
});
|
||||
let previous_year = move |_| {
|
||||
show_date.update(|date| {
|
||||
*date = *date - Months::new(12);
|
||||
});
|
||||
};
|
||||
let next_year = move |_| {
|
||||
show_date.update(|date| {
|
||||
*date = *date + Months::new(12);
|
||||
});
|
||||
};
|
||||
let previous_month = move |_| {
|
||||
show_date.update(|date| {
|
||||
*date = *date - Months::new(1);
|
||||
});
|
||||
};
|
||||
let next_month = move |_| {
|
||||
show_date.update(|date| {
|
||||
*date = *date + Months::new(1);
|
||||
});
|
||||
};
|
||||
let now = Callback::new(move |_| {
|
||||
close_panel.call(Some(now_date()));
|
||||
});
|
||||
view! {
|
||||
<div>
|
||||
<div class="thaw-date-picker-date-panel__calendar">
|
||||
<div class="thaw-date-picker-date-panel__header">
|
||||
<Button
|
||||
variant=ButtonVariant::Link
|
||||
size=ButtonSize::Small
|
||||
icon=AiIcon::AiArrowLeftOutlined
|
||||
on_click=previous_year
|
||||
/>
|
||||
<Button
|
||||
variant=ButtonVariant::Link
|
||||
size=ButtonSize::Small
|
||||
icon=AiIcon::AiLeftOutlined
|
||||
on_click=previous_month
|
||||
/>
|
||||
<div class="thaw-date-picker-date-panel__header-month-year">
|
||||
<Button
|
||||
variant=ButtonVariant::Text
|
||||
size=ButtonSize::Small
|
||||
on_click=move |_| panel_variant.set(PanelVariant::Month)
|
||||
>
|
||||
{move || Month::try_from(show_date.get().month() as u8).unwrap().name()}
|
||||
</Button>
|
||||
<Button
|
||||
variant=ButtonVariant::Text
|
||||
size=ButtonSize::Small
|
||||
on_click=move |_| panel_variant.set(PanelVariant::Year)
|
||||
>
|
||||
{move || show_date.get().year()}
|
||||
</Button>
|
||||
</div>
|
||||
<Button
|
||||
variant=ButtonVariant::Link
|
||||
size=ButtonSize::Small
|
||||
icon=AiIcon::AiRightOutlined
|
||||
on_click=next_month
|
||||
/>
|
||||
<Button
|
||||
variant=ButtonVariant::Link
|
||||
size=ButtonSize::Small
|
||||
icon=AiIcon::AiArrowRightOutlined
|
||||
on_click=next_year
|
||||
/>
|
||||
</div>
|
||||
<div class="thaw-date-picker-date-panel__weekdays">
|
||||
<span>"Su"</span>
|
||||
<span>"Mo"</span>
|
||||
<span>"Tu"</span>
|
||||
<span>"We"</span>
|
||||
<span>"Th"</span>
|
||||
<span>"Fr"</span>
|
||||
<span>"Sa"</span>
|
||||
</div>
|
||||
<div class="thaw-date-picker-date-panel__dates">
|
||||
{move || {
|
||||
dates
|
||||
.get()
|
||||
.into_iter()
|
||||
.map(|date| {
|
||||
let on_click = {
|
||||
let date = date.clone();
|
||||
move |_| {
|
||||
close_panel.call(Some(*date.deref()));
|
||||
}
|
||||
};
|
||||
view! { <DatePanelItem value date=date on:click=on_click/> }
|
||||
})
|
||||
.collect_view()
|
||||
}}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="thaw-date-picker-date-panel__footer">
|
||||
<Button variant=ButtonVariant::Solid size=ButtonSize::Tiny on_click=now>
|
||||
"Now"
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn DatePanelItem(value: RwSignal<Option<NaiveDate>>, date: CalendarItemDate) -> impl IntoView {
|
||||
let is_selected = create_memo({
|
||||
let date = date.clone();
|
||||
move |_| value.with(|value_date| value_date.as_ref() == Some(date.deref()))
|
||||
});
|
||||
|
||||
view! {
|
||||
<div
|
||||
class="thaw-date-picker-date-panel__item"
|
||||
class=("thaw-date-picker-date-panel__item--other-month", date.is_other_month())
|
||||
class=("thaw-date-picker-date-panel__item--selected", move || is_selected.get())
|
||||
>
|
||||
<div class="thaw-date-picker-date-panel__item-day">
|
||||
{date.day()}
|
||||
{if date.is_today() {
|
||||
view! { <div class="thaw-date-picker-date-panel__item-sup"></div> }.into()
|
||||
} else {
|
||||
None
|
||||
}}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
135
src/date_picker/panel/mod.rs
Normal file
135
src/date_picker/panel/mod.rs
Normal file
|
@ -0,0 +1,135 @@
|
|||
mod date_panel;
|
||||
mod month_panel;
|
||||
mod year_panel;
|
||||
|
||||
use crate::{
|
||||
chrono::NaiveDate,
|
||||
use_theme,
|
||||
utils::{now_date, ComponentRef},
|
||||
Theme,
|
||||
};
|
||||
use date_panel::DatePanel;
|
||||
use leptos::*;
|
||||
use month_panel::MonthPanel;
|
||||
use year_panel::YearPanel;
|
||||
|
||||
#[component]
|
||||
pub fn Panel(
|
||||
selected_date: RwSignal<Option<NaiveDate>>,
|
||||
date_picker_ref: NodeRef<html::Div>,
|
||||
close_panel: Callback<Option<NaiveDate>>,
|
||||
#[prop(optional)] comp_ref: ComponentRef<PanelRef>,
|
||||
) -> impl IntoView {
|
||||
let theme = use_theme(Theme::light);
|
||||
let css_vars = create_memo(move |_| {
|
||||
let mut css_vars = String::new();
|
||||
theme.with(|theme| {
|
||||
css_vars.push_str(&format!(
|
||||
"--thaw-background-color-today: {};",
|
||||
theme.common.color_primary
|
||||
));
|
||||
css_vars.push_str(&format!(
|
||||
"--thaw-font-color-other-month: {};",
|
||||
theme.date_picker.panel_other_month_font_color,
|
||||
));
|
||||
css_vars.push_str(&format!(
|
||||
"--thaw-background-color: {};",
|
||||
theme.date_picker.panel_background_color
|
||||
));
|
||||
css_vars.push_str(&format!(
|
||||
"--thaw-item-background-color-hover: {};",
|
||||
theme.date_picker.panel_date_item_background_color_hover
|
||||
));
|
||||
css_vars.push_str(&format!(
|
||||
"--thaw-item-border-color: {};",
|
||||
theme.date_picker.panel_border_color
|
||||
));
|
||||
});
|
||||
css_vars
|
||||
});
|
||||
|
||||
let panel_ref = create_node_ref::<html::Div>();
|
||||
#[cfg(any(feature = "csr", feature = "hydrate"))]
|
||||
{
|
||||
use leptos::wasm_bindgen::__rt::IntoJsResult;
|
||||
let handle = window_event_listener(ev::click, move |ev| {
|
||||
let el = ev.target();
|
||||
let mut el: Option<web_sys::Element> =
|
||||
el.into_js_result().map_or(None, |el| Some(el.into()));
|
||||
let body = document().body().unwrap();
|
||||
loop {
|
||||
let Some(current_el) = el else {
|
||||
return;
|
||||
};
|
||||
if current_el == *body {
|
||||
break;
|
||||
};
|
||||
if panel_ref.get().is_none() {
|
||||
return;
|
||||
}
|
||||
if current_el == ***panel_ref.get_untracked().unwrap()
|
||||
|| current_el == ***date_picker_ref.get_untracked().unwrap()
|
||||
{
|
||||
return;
|
||||
}
|
||||
el = current_el.parent_element();
|
||||
}
|
||||
close_panel.call(None);
|
||||
});
|
||||
on_cleanup(move || handle.remove());
|
||||
}
|
||||
#[cfg(not(any(feature = "csr", feature = "hydrate")))]
|
||||
{
|
||||
_ = date_picker_ref;
|
||||
_ = panel_ref;
|
||||
}
|
||||
let panel_variant = create_rw_signal(PanelVariant::Date);
|
||||
let show_date = create_rw_signal(selected_date.get_untracked().unwrap_or(now_date()));
|
||||
comp_ref.load(PanelRef {
|
||||
show_date,
|
||||
variant: panel_variant,
|
||||
});
|
||||
|
||||
view! {
|
||||
<div class="thaw-date-picker-panel" style=move || css_vars.get() ref=panel_ref>
|
||||
|
||||
{move || {
|
||||
match panel_variant.get() {
|
||||
PanelVariant::Date => {
|
||||
view! {
|
||||
<DatePanel value=selected_date show_date close_panel panel_variant/>
|
||||
}
|
||||
}
|
||||
PanelVariant::Month => {
|
||||
view! { <MonthPanel date_panel_show_date=show_date panel_variant/> }
|
||||
}
|
||||
PanelVariant::Year => {
|
||||
view! { <YearPanel date_panel_show_date=show_date panel_variant/> }
|
||||
}
|
||||
}
|
||||
}}
|
||||
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PanelRef {
|
||||
show_date: RwSignal<NaiveDate>,
|
||||
variant: RwSignal<PanelVariant>,
|
||||
}
|
||||
|
||||
impl PanelRef {
|
||||
pub fn init_panel(&self, show_date: NaiveDate) {
|
||||
self.show_date.set(show_date);
|
||||
self.variant.set(PanelVariant::Date);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub enum PanelVariant {
|
||||
#[default]
|
||||
Date,
|
||||
Month,
|
||||
Year,
|
||||
}
|
89
src/date_picker/panel/month_panel.rs
Normal file
89
src/date_picker/panel/month_panel.rs
Normal file
|
@ -0,0 +1,89 @@
|
|||
use super::PanelVariant;
|
||||
use crate::{
|
||||
chrono::{Datelike, Month, Months, NaiveDate},
|
||||
AiIcon, Button, ButtonSize, ButtonVariant,
|
||||
};
|
||||
use leptos::*;
|
||||
|
||||
#[component]
|
||||
pub fn MonthPanel(
|
||||
date_panel_show_date: RwSignal<NaiveDate>,
|
||||
panel_variant: RwSignal<PanelVariant>,
|
||||
) -> impl IntoView {
|
||||
let show_date = create_rw_signal(date_panel_show_date.get_untracked());
|
||||
let previous_year = move |_| {
|
||||
show_date.update(|date| {
|
||||
*date = *date - Months::new(12);
|
||||
});
|
||||
};
|
||||
let next_year = move |_| {
|
||||
show_date.update(|date| {
|
||||
*date = *date + Months::new(12);
|
||||
});
|
||||
};
|
||||
view! {
|
||||
<div class="thaw-date-picker-month-panel">
|
||||
<div class="thaw-date-picker-month-panel__header">
|
||||
<Button
|
||||
variant=ButtonVariant::Link
|
||||
size=ButtonSize::Small
|
||||
icon=AiIcon::AiArrowLeftOutlined
|
||||
on_click=previous_year
|
||||
/>
|
||||
<div class="thaw-date-picker-date-panel__header-year">
|
||||
<Button
|
||||
variant=ButtonVariant::Text
|
||||
size=ButtonSize::Small
|
||||
on_click=move |_| panel_variant.set(PanelVariant::Year)
|
||||
>
|
||||
{move || show_date.get().year()}
|
||||
</Button>
|
||||
</div>
|
||||
<Button
|
||||
variant=ButtonVariant::Link
|
||||
size=ButtonSize::Small
|
||||
icon=AiIcon::AiArrowRightOutlined
|
||||
on_click=next_year
|
||||
/>
|
||||
</div>
|
||||
<div class="thaw-date-picker-month-panel__months">
|
||||
|
||||
{(1..=12)
|
||||
.map(|index| {
|
||||
let month = Month::try_from(index).unwrap();
|
||||
let on_click = move |_| {
|
||||
date_panel_show_date
|
||||
.update(|date| {
|
||||
let show_date = show_date.get_untracked();
|
||||
*date = date
|
||||
.with_month(index.into())
|
||||
.unwrap()
|
||||
.with_year(show_date.year())
|
||||
.unwrap();
|
||||
});
|
||||
panel_variant.set(PanelVariant::Date);
|
||||
};
|
||||
view! { <MonthPanelItem date_panel_show_date month on:click=on_click/> }
|
||||
})
|
||||
.collect_view()}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn MonthPanelItem(date_panel_show_date: RwSignal<NaiveDate>, month: Month) -> impl IntoView {
|
||||
let is_selected = create_memo(move |_| {
|
||||
date_panel_show_date.with(|date| date.month() == month.number_from_month())
|
||||
});
|
||||
|
||||
view! {
|
||||
<div
|
||||
class="thaw-date-picker-month-panel__item"
|
||||
class=("thaw-date-picker-month-panel__item--selected", move || is_selected.get())
|
||||
>
|
||||
<div class="thaw-date-picker-month-panel__item-month">{month.name().split_at(3).0}</div>
|
||||
</div>
|
||||
}
|
||||
}
|
89
src/date_picker/panel/year_panel.rs
Normal file
89
src/date_picker/panel/year_panel.rs
Normal file
|
@ -0,0 +1,89 @@
|
|||
use super::PanelVariant;
|
||||
use crate::{
|
||||
chrono::{Datelike, NaiveDate},
|
||||
AiIcon, Button, ButtonSize, ButtonVariant,
|
||||
};
|
||||
use leptos::*;
|
||||
|
||||
const MAX_YEAR: i32 = (i32::MAX >> 13) / 10 - 1;
|
||||
const MIN_YEAR: i32 = (i32::MIN >> 13) / 10 + 1;
|
||||
|
||||
#[component]
|
||||
pub fn YearPanel(
|
||||
date_panel_show_date: RwSignal<NaiveDate>,
|
||||
panel_variant: RwSignal<PanelVariant>,
|
||||
) -> impl IntoView {
|
||||
let show_min_year = create_rw_signal(date_panel_show_date.get_untracked().year() / 10);
|
||||
let previous_year_range = move |_| {
|
||||
show_min_year.update(|year| {
|
||||
if *year > MIN_YEAR {
|
||||
*year -= 1;
|
||||
}
|
||||
});
|
||||
};
|
||||
let next_year_range = move |_| {
|
||||
show_min_year.update(|year| {
|
||||
if *year < MAX_YEAR {
|
||||
*year += 1;
|
||||
}
|
||||
});
|
||||
};
|
||||
view! {
|
||||
<div>
|
||||
<div class="thaw-date-picker-year-panel__header">
|
||||
<Button
|
||||
variant=ButtonVariant::Link
|
||||
size=ButtonSize::Small
|
||||
icon=AiIcon::AiArrowLeftOutlined
|
||||
on_click=previous_year_range
|
||||
/>
|
||||
<div>
|
||||
{move || {
|
||||
let year = show_min_year.get();
|
||||
format!("{}0 - {}9", year, year)
|
||||
}}
|
||||
|
||||
</div>
|
||||
<Button
|
||||
variant=ButtonVariant::Link
|
||||
size=ButtonSize::Small
|
||||
icon=AiIcon::AiArrowRightOutlined
|
||||
on_click=next_year_range
|
||||
/>
|
||||
</div>
|
||||
<div class="thaw-date-picker-year-panel__years">
|
||||
|
||||
{move || {
|
||||
(-1..=10)
|
||||
.map(|index| {
|
||||
let year = show_min_year.get() * 10 + index;
|
||||
let on_click = move |_| {
|
||||
date_panel_show_date
|
||||
.update(|date| {
|
||||
*date = date.with_year(year).unwrap();
|
||||
});
|
||||
panel_variant.set(PanelVariant::Month);
|
||||
};
|
||||
view! { <YearPanelItem date_panel_show_date year on:click=on_click/> }
|
||||
})
|
||||
.collect_view()
|
||||
}}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn YearPanelItem(date_panel_show_date: RwSignal<NaiveDate>, year: i32) -> impl IntoView {
|
||||
let is_selected = create_memo(move |_| date_panel_show_date.with(|date| date.year() == year));
|
||||
|
||||
view! {
|
||||
<div
|
||||
class="thaw-date-picker-year-panel__item"
|
||||
class=("thaw-date-picker-year-panel__item--selected", move || is_selected.get())
|
||||
>
|
||||
<div class="thaw-date-picker-year-panel__item-year">{year}</div>
|
||||
</div>
|
||||
}
|
||||
}
|
29
src/date_picker/theme.rs
Normal file
29
src/date_picker/theme.rs
Normal file
|
@ -0,0 +1,29 @@
|
|||
use crate::theme::ThemeMethod;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DatePickerTheme {
|
||||
pub panel_background_color: String,
|
||||
pub panel_date_item_background_color_hover: String,
|
||||
pub panel_border_color: String,
|
||||
pub panel_other_month_font_color: String,
|
||||
}
|
||||
|
||||
impl ThemeMethod for DatePickerTheme {
|
||||
fn light() -> Self {
|
||||
Self {
|
||||
panel_background_color: "#fff".into(),
|
||||
panel_date_item_background_color_hover: "#f1f3f5".into(),
|
||||
panel_border_color: "#e0e0e6".into(),
|
||||
panel_other_month_font_color: "#c2c2c2".into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn dark() -> Self {
|
||||
Self {
|
||||
panel_background_color: "#48484e".into(),
|
||||
panel_date_item_background_color_hover: "#ffffff1a".into(),
|
||||
panel_border_color: "#ffffff3d".into(),
|
||||
panel_other_month_font_color: "#ffffff61".into(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@ mod checkbox;
|
|||
mod code;
|
||||
mod color_picker;
|
||||
mod components;
|
||||
mod date_picker;
|
||||
mod divider;
|
||||
mod global_style;
|
||||
mod grid;
|
||||
|
@ -52,6 +53,7 @@ pub use checkbox::*;
|
|||
pub use chrono;
|
||||
pub use code::*;
|
||||
pub use color_picker::*;
|
||||
pub use date_picker::*;
|
||||
pub use divider::*;
|
||||
pub use global_style::*;
|
||||
pub use grid::*;
|
||||
|
|
|
@ -4,9 +4,9 @@ use self::common::CommonTheme;
|
|||
use crate::{
|
||||
mobile::{NavBarTheme, TabbarTheme},
|
||||
AlertTheme, AutoCompleteTheme, AvatarTheme, BreadcrumbTheme, ButtonTheme, CalendarTheme,
|
||||
ColorPickerTheme, InputTheme, MenuTheme, MessageTheme, ProgressTheme, SelectTheme,
|
||||
SkeletionTheme, SliderTheme, SpinnerTheme, SwitchTheme, TableTheme, TagTheme, TimePickerTheme,
|
||||
TypographyTheme, UploadTheme,
|
||||
ColorPickerTheme, DatePickerTheme, InputTheme, MenuTheme, MessageTheme, ProgressTheme,
|
||||
SelectTheme, SkeletionTheme, SliderTheme, SpinnerTheme, SwitchTheme, TableTheme, TagTheme,
|
||||
TimePickerTheme, TypographyTheme, UploadTheme,
|
||||
};
|
||||
use leptos::*;
|
||||
|
||||
|
@ -42,6 +42,7 @@ pub struct Theme {
|
|||
pub typograph: TypographyTheme,
|
||||
pub calendar: CalendarTheme,
|
||||
pub time_picker: TimePickerTheme,
|
||||
pub date_picker: DatePickerTheme,
|
||||
}
|
||||
|
||||
impl Theme {
|
||||
|
@ -72,6 +73,7 @@ impl Theme {
|
|||
typograph: TypographyTheme::light(),
|
||||
calendar: CalendarTheme::light(),
|
||||
time_picker: TimePickerTheme::light(),
|
||||
date_picker: DatePickerTheme::light(),
|
||||
}
|
||||
}
|
||||
pub fn dark() -> Self {
|
||||
|
@ -101,6 +103,7 @@ impl Theme {
|
|||
typograph: TypographyTheme::dark(),
|
||||
calendar: CalendarTheme::dark(),
|
||||
time_picker: TimePickerTheme::dark(),
|
||||
date_picker: DatePickerTheme::dark(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ mod event_listener;
|
|||
mod mount_style;
|
||||
mod signal;
|
||||
mod stored_maybe_signal;
|
||||
mod time;
|
||||
|
||||
// pub use callback::AsyncCallback;
|
||||
pub use component_ref::{create_component_ref, ComponentRef};
|
||||
|
@ -13,6 +14,7 @@ pub(crate) use event_listener::*;
|
|||
pub(crate) use mount_style::mount_style;
|
||||
pub use signal::SignalWatch;
|
||||
pub(crate) use stored_maybe_signal::*;
|
||||
pub(crate) use time::*;
|
||||
|
||||
pub(crate) fn with_hydration_off<T>(f: impl FnOnce() -> T) -> T {
|
||||
#[cfg(feature = "hydrate")]
|
||||
|
|
5
src/utils/time.rs
Normal file
5
src/utils/time.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
use crate::chrono::{Local, NaiveDate};
|
||||
|
||||
pub fn now_date() -> NaiveDate {
|
||||
Local::now().date_naive()
|
||||
}
|
Loading…
Add table
Reference in a new issue