Date Picker

Rust UI component that displays a date picker.

  • Rust UI Icons - CopyCopy Demo
MoTuWeThFrSaSu
use icons::{ChevronLeft, ChevronRight};
use leptos::prelude::*;
use time::{Date, Month};

use crate::components::_coming_soon::date_picker::{
    DatePicker, DatePickerCell, DatePickerDay, DatePickerHeader, DatePickerNavButton, DatePickerRow, DatePickerTitle,
    DatePickerWeekDay,
};
use crate::components::_coming_soon::date_picker_state::DatePickerState;

// TODO. Normally, with just aria_current=true, classes should be applied.
const CLASS_CURRENT: &str = "bg-primary text-primary-foreground hover:bg-primary/90 hover:text-primary-foreground/90";

#[component]
pub fn DemoDatePicker() -> impl IntoView {
    let start_date = RwSignal::new(Date::from_calendar_date(2025, Month::May, 5).expect("Invalid date"));
    let end_date = RwSignal::new(Date::from_calendar_date(2025, Month::May, 14).expect("Invalid date"));

    // Function to handle day selection
    let handle_day_click = move |day: u8| {
        if day == 0 {
            return;
        }

        let year = start_date.get().year();
        let month = start_date.get().month();

        // Create new date for the selected day
        let new_date = Date::from_calendar_date(year, month, day).expect("Invalid date");

        // Determine which date to update based on proximity
        let current_start = start_date.get().day();
        let current_end = end_date.get().day();

        if current_start.abs_diff(day) <= current_end.abs_diff(day) {
            start_date.set(new_date);
        } else {
            end_date.set(new_date);
        }
    };

    view! {
        <DatePicker>
            <DatePickerHeader>
                <DatePickerTitle role="presentation">
                    {move || start_date.get().month().to_string()} {move || start_date.get().year()}
                </DatePickerTitle>
                <div class="flex items-center space-x-1">
                    <DatePickerNavButton title="previous-month" aria_label="Go to previous month" class="left-1">
                        <ChevronLeft />
                    </DatePickerNavButton>
                    <DatePickerNavButton title="next-month" aria_label="Go to next month" class="right-1">
                        <ChevronRight />
                    </DatePickerNavButton>
                </div>
            </DatePickerHeader>

            {move || {
                let year = start_date.get().year();
                let month = start_date.get().month();
                let start = start_date.get().day();
                let end = end_date.get().day();
                let days = DatePickerState::calculate_calendar_data(year, month);
                let rows: Vec<_> = days
                    .chunks(7)
                    .map(|week| {
                        let week_cells = week
                            .iter()
                            .map(|&(day, disabled, outside)| {
                                let is_selected = !disabled && !outside && day >= start && day <= end;
                                let class = if !disabled && (day == start || day == end) {
                                    CLASS_CURRENT.to_string()
                                } else {
                                    "".to_string()
                                };

                                view! {
                                    <DatePickerCell>
                                        <DatePickerDay
                                            disabled=disabled
                                            aria_selected=is_selected
                                            class=class
                                            on:click=move |_| {
                                                if !disabled && !outside {
                                                    handle_day_click(day);
                                                }
                                            }
                                        >
                                            {day.to_string()}
                                        </DatePickerDay>
                                    </DatePickerCell>
                                }
                            })
                            .collect_view();
                        view! { <DatePickerRow>{week_cells}</DatePickerRow> }
                    })
                    .collect();

                view! {
                    <table class="space-y-1 w-full border-collapse" role="grid">
                        <thead>
                            <tr class="flex">
                                <DatePickerWeekDay aria_label="Monday">Mo</DatePickerWeekDay>
                                <DatePickerWeekDay aria_label="Tuesday">Tu</DatePickerWeekDay>
                                <DatePickerWeekDay aria_label="Wednesday">We</DatePickerWeekDay>
                                <DatePickerWeekDay aria_label="Thursday">Th</DatePickerWeekDay>
                                <DatePickerWeekDay aria_label="Friday">Fr</DatePickerWeekDay>
                                <DatePickerWeekDay aria_label="Saturday">Sa</DatePickerWeekDay>
                                <DatePickerWeekDay aria_label="Sunday">Su</DatePickerWeekDay>
                            </tr>
                        </thead>
                        <tbody>{rows}</tbody>
                    </table>
                }
            }}
        </DatePicker>
    }
}

Installation

# Coming soon :)

Usage

// Coming soon 🦀