use crate::components::shared::{CONTROL_MOTION, FOCUS_RING};
use crate::icons::{icondata, Icon};
use leptos::prelude::*;
fn today() -> (i32, i32, i32) {
let d = js_sys::Date::new_0();
(
d.get_full_year() as i32,
d.get_month() as i32,
d.get_date() as i32,
)
}
fn days_in_month(year: i32, month: i32) -> i32 {
let next = if month == 11 { 0 } else { month + 1 };
let y = if month == 11 { year + 1 } else { year };
js_sys::Date::new_with_year_month_day(y as u32, next as i32, 0).get_date() as i32
}
fn weekday_of_first(year: i32, month: i32) -> i32 {
js_sys::Date::new_with_year_month_day(year as u32, month as i32, 1).get_day() as i32
}
const MONTH_NAMES: [&str; 12] = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
];
const DAY_HEADERS: [&str; 7] = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
#[component]
pub fn DatePicker(
#[prop(into)] value: RwSignal<Option<String>>,
#[prop(optional, into)] placeholder: String,
) -> impl IntoView {
let open = RwSignal::new(false);
let (ty, tm, _td) = today();
let view_year = RwSignal::new(ty);
let view_month = RwSignal::new(tm);
let placeholder = StoredValue::new(if placeholder.is_empty() {
"Pick a date".to_string()
} else {
placeholder
});
let trigger_label = move || value.get().unwrap_or_else(|| placeholder.get_value());
let has_value = move || value.get().is_some();
let trigger_class = format!(
"flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 text-sm text-foreground shadow-elev-sm hover:border-ring/60 {} {}",
CONTROL_MOTION, FOCUS_RING
);
view! {
<div class="relative w-full">
<button
type="button"
class=trigger_class
on:click=move |_| open.update(|v| *v = !*v)
>
<span class=move || if has_value() { "text-foreground" } else { "text-muted-foreground" }>
{trigger_label}
</span>
<Icon icon=Signal::derive(|| icondata::LuCalendar) width="16" height="16" />
</button>
<Show when=move || open.get()>
<div class="fixed inset-0 z-40" on:click=move |_| open.set(false) />
<div class="absolute z-50 mt-2 w-72 rounded-md border border-border bg-card p-3 shadow-md animate-scale-in">
<div class="flex items-center justify-between mb-3">
<button
type="button"
class="flex h-7 w-7 items-center justify-center rounded hover:bg-accent hover:text-accent-foreground text-foreground"
on:click=move |_| {
let m = view_month.get();
if m == 0 {
view_month.set(11);
view_year.update(|y| *y -= 1);
} else {
view_month.set(m - 1);
}
}
>
<Icon icon=Signal::derive(|| icondata::LuChevronLeft) width="14" height="14" />
</button>
<span class="text-sm font-medium text-foreground">
{move || format!("{} {}", MONTH_NAMES[view_month.get() as usize], view_year.get())}
</span>
<button
type="button"
class="flex h-7 w-7 items-center justify-center rounded hover:bg-accent hover:text-accent-foreground text-foreground"
on:click=move |_| {
let m = view_month.get();
if m == 11 {
view_month.set(0);
view_year.update(|y| *y += 1);
} else {
view_month.set(m + 1);
}
}
>
<Icon icon=Signal::derive(|| icondata::LuChevronRight) width="14" height="14" />
</button>
</div>
<div class="grid grid-cols-7 mb-1">
{DAY_HEADERS.iter().map(|h| view! {
<div class="flex h-8 items-center justify-center text-xs text-muted-foreground">{*h}</div>
}).collect::<Vec<_>>()}
</div>
{move || {
let year = view_year.get();
let month = view_month.get();
let first_dow = weekday_of_first(year, month);
let days = days_in_month(year, month);
let (ty2, tm2, td2) = today();
let sel = value.get();
let mut cells: Vec<_> = Vec::new();
for _ in 0..first_dow {
cells.push(view! { <div /> }.into_any());
}
for day in 1..=days {
let iso = format!("{:04}-{:02}-{:02}", year, month + 1, day);
let iso_clone = iso.clone();
let is_today = year == ty2 && month == tm2 && day == td2;
let is_selected = sel.as_deref() == Some(iso.as_str());
let base = "flex h-8 w-8 mx-auto items-center justify-center rounded text-sm cursor-pointer";
let style_class = if is_selected {
format!("{} bg-primary text-primary-foreground", base)
} else if is_today {
format!("{} text-primary font-semibold hover:bg-accent hover:text-accent-foreground", base)
} else {
format!("{} text-foreground hover:bg-accent hover:text-accent-foreground", base)
};
cells.push(view! {
<div
class=style_class
on:click=move |_| {
value.set(Some(iso_clone.clone()));
open.set(false);
}
>
{day}
</div>
}.into_any());
}
view! {
<div class="grid grid-cols-7 gap-y-1">
{cells}
</div>
}
}}
</div>
</Show>
</div>
}
}