shadcn-iced
Menu

Calendar

A calendar component that allows users to select a date or a range of dates.

extra.rs:46
use crate::pages::DemoMessage;
use crate::Message;
use shadcn_iced::calendar::Date;
use shadcn_iced::prelude::*;

fn demo(message: DemoMessage) -> Message {
    Message::Demo(message)
}

let d = &gallery.demo;
let month = |m| demo(DemoMessage::Month(m));
let select = |date| demo(DemoMessage::Date(date));
calendar(d.month, month)
    .selected(d.date)
    .on_select(select)
    .dropdowns(2016, 2036)
    .bordered(true)

Installation

Copy the component, and the components it uses, into src/ui/:

cargo shadcn-iced add calendar

Or use it from the shadcn_iced crate:

use shadcn_iced::calendar::*;
// or everything at once:
use shadcn_iced::prelude::*;

Copy the source of the component into src/ui/calendar.rs (it uses shadcn_iced_core and, sometimes, other components):

crates/shadcn_iced/src/components/calendar.rs
//! A date field component that allows users to enter and edit date.
//!
//! Port of <https://ui.shadcn.com/docs/components/calendar> (react-day-picker).
//! Both the displayed month and the selection live in your state:
//!
//! ```no_run
//! # use shadcn_iced::calendar::{calendar, Date, Month};
//! # #[derive(Clone)] enum Message { Navigate(Month), Select(Date) }
//! # fn view<'a>(month: Month, selected: Option<Date>) -> iced::Element<'a, Message> {
//! calendar(month, Message::Navigate)
//!     .selected(selected)
//!     .on_select(Message::Select)
//!     .into()
//! # }
//! ```
use shadcn_iced_core::icon::lucide;
use shadcn_iced_core::primitive::Pressable;
use shadcn_iced_core::theme::alpha;
use shadcn_iced_core::{Element, font, style, tokens};

use iced::widget::button::{Status, Style};
use iced::widget::{Column, Row, container, text};
use iced::{Background, Length, Theme, border};

use std::fmt;
use std::rc::Rc;

/// A calendar date (proleptic Gregorian).
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Date {
    /// The year.
    pub year: i32,
    /// The month, from 1 to 12.
    pub month: u32,
    /// The day of the month, from 1.
    pub day: u32,
}

impl Date {
    /// Creates a date. Returns `None` if it does not exist.
    pub fn new(year: i32, month: u32, day: u32) -> Option<Self> {
        ((1..=12).contains(&month) && day >= 1 && day <= days_in_month(year, month))
            .then_some(Self { year, month, day })
    }

    /// Today, in UTC.
    pub fn today() -> Self {
        use iced::time::SystemTime;

        // `iced::time` also works on the web, unlike `std::time`.
        let seconds = SystemTime::now()
            .duration_since(SystemTime::UNIX_EPOCH)
            .map(|duration| duration.as_secs() as i64)
            .unwrap_or_default();

        Self::from_days(seconds.div_euclid(86_400))
    }

    /// The number of days since 1970-01-01.
    pub fn to_days(self) -> i64 {
        // Howard Hinnant's `days_from_civil`.
        let y = if self.month <= 2 {
            self.year - 1
        } else {
            self.year
        } as i64;
        let era = y.div_euclid(400);
        let yoe = y - era * 400;
        let m = self.month as i64;
        let doy = (153 * (m + if m > 2 { -3 } else { 9 }) + 2) / 5 + self.day as i64 - 1;
        let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;

        era * 146_097 + doe - 719_468
    }

    /// The date `days` days after 1970-01-01.
    pub fn from_days(days: i64) -> Self {
        // Howard Hinnant's `civil_from_days`.
        let z = days + 719_468;
        let era = z.div_euclid(146_097);
        let doe = z - era * 146_097;
        let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365;
        let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
        let mp = (5 * doy + 2) / 153;
        let day = (doy - (153 * mp + 2) / 5 + 1) as u32;
        let month = if mp < 10 { mp + 3 } else { mp - 9 } as u32;
        let year = (yoe + era * 400 + i64::from(month <= 2)) as i32;

        Self { year, month, day }
    }

    /// Adds a number of days (possibly negative).
    pub fn add_days(self, days: i64) -> Self {
        Self::from_days(self.to_days() + days)
    }

    /// The day of the week, from 0 (Sunday) to 6 (Saturday).
    pub fn weekday(self) -> u32 {
        (self.to_days() + 4).rem_euclid(7) as u32
    }

    /// The month of the date.
    pub fn month_of(self) -> Month {
        Month {
            year: self.year,
            month: self.month,
        }
    }
}

impl fmt::Display for Date {
    /// Formats like `October 16, 2026`.
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "{} {}, {}",
            MONTHS[self.month as usize - 1],
            self.day,
            self.year
        )
    }
}

/// A month of a year.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Month {
    /// The year.
    pub year: i32,
    /// The month, from 1 to 12.
    pub month: u32,
}

impl Month {
    /// The current month, in UTC.
    pub fn current() -> Self {
        Date::today().month_of()
    }

    /// The next month.
    pub fn next(self) -> Self {
        if self.month == 12 {
            Self {
                year: self.year + 1,
                month: 1,
            }
        } else {
            Self {
                month: self.month + 1,
                ..self
            }
        }
    }

    /// The previous month.
    pub fn previous(self) -> Self {
        if self.month == 1 {
            Self {
                year: self.year - 1,
                month: 12,
            }
        } else {
            Self {
                month: self.month - 1,
                ..self
            }
        }
    }

    /// The first day of the month.
    pub fn first(self) -> Date {
        Date {
            year: self.year,
            month: self.month,
            day: 1,
        }
    }
}

impl fmt::Display for Month {
    /// Formats like `October 2026`.
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{} {}", MONTHS[self.month as usize - 1], self.year)
    }
}

const MONTHS: [&str; 12] = [
    "January",
    "February",
    "March",
    "April",
    "May",
    "June",
    "July",
    "August",
    "September",
    "October",
    "November",
    "December",
];

const WEEKDAYS: [&str; 7] = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];

/// The number of days in a month.
pub fn days_in_month(year: i32, month: u32) -> u32 {
    match month {
        1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
        4 | 6 | 9 | 11 => 30,
        2 if (year % 4 == 0 && year % 100 != 0) || year % 400 == 0 => 29,
        2 => 28,
        _ => 0,
    }
}

enum Selection {
    None,
    Single(Option<Date>),
    Range(Option<Date>, Option<Date>),
}

/// A calendar. See the [module documentation](self).
pub struct Calendar<'a, Message> {
    month: Month,
    on_navigate: Rc<dyn Fn(Month) -> Message + 'a>,
    selection: Selection,
    on_select: Option<Box<dyn Fn(Date) -> Message + 'a>>,
    today: Option<Date>,
    disabled: Option<Box<dyn Fn(Date) -> bool + 'a>>,
    struck: Option<Box<dyn Fn(Date) -> bool + 'a>>,
    months: usize,
    outside_days: bool,
    bordered: bool,
    transparent: bool,
    cell: f32,
    padding: f32,
    fixed_weeks: bool,
    week_numbers: bool,
    dropdowns: Option<(i32, i32)>,
}

/// Creates a [`Calendar`] displaying `month`.
pub fn calendar<'a, Message: Clone + 'a>(
    month: Month,
    on_navigate: impl Fn(Month) -> Message + 'a,
) -> Calendar<'a, Message> {
    Calendar {
        month,
        on_navigate: Rc::new(on_navigate),
        selection: Selection::None,
        on_select: None,
        today: Some(Date::today()),
        disabled: None,
        struck: None,
        months: 1,
        outside_days: true,
        bordered: false,
        transparent: false,
        cell: 28.0,
        padding: 8.0,
        fixed_weeks: false,
        week_numbers: false,
        dropdowns: None,
    }
}

impl<'a, Message: Clone + 'a> Calendar<'a, Message> {
    /// Highlights a single selected date (`mode="single"`).
    pub fn selected(mut self, date: Option<Date>) -> Self {
        self.selection = Selection::Single(date);
        self
    }

    /// Highlights a range of dates (`mode="range"`).
    pub fn range(mut self, start: Option<Date>, end: Option<Date>) -> Self {
        self.selection = Selection::Range(start, end);
        self
    }

    /// Sets the message produced when a day is pressed.
    pub fn on_select(mut self, f: impl Fn(Date) -> Message + 'a) -> Self {
        self.on_select = Some(Box::new(f));
        self
    }

    /// Sets the date highlighted as today, if any.
    pub fn today(mut self, today: Option<Date>) -> Self {
        self.today = today;
        self
    }

    /// Disables the days for which `f` returns `true`
    /// (`text-muted-foreground opacity-50`).
    pub fn disabled(mut self, f: impl Fn(Date) -> bool + 'a) -> Self {
        self.disabled = Some(Box::new(f));
        self
    }

    /// Strikes through the days for which `f` returns `true`, e.g. booked
    /// dates (`[&>button]:line-through`).
    pub fn struck(mut self, f: impl Fn(Date) -> bool + 'a) -> Self {
        self.struck = Some(Box::new(f));
        self
    }

    /// Sets how many months are displayed side by side
    /// (`numberOfMonths`).
    pub fn months(mut self, months: usize) -> Self {
        self.months = months.max(1);
        self
    }

    /// Shows the days of the adjacent months. Defaults to `true`.
    pub fn outside_days(mut self, show: bool) -> Self {
        self.outside_days = show;
        self
    }

    /// Removes the background, e.g. inside a popover or a card
    /// (`in-data-[slot=card-content]:bg-transparent`).
    pub fn transparent(mut self, transparent: bool) -> Self {
        self.transparent = transparent;
        self
    }

    /// Draws a border around the calendar (`rounded-lg border`).
    pub fn bordered(mut self, bordered: bool) -> Self {
        self.bordered = bordered;
        self
    }

    /// Sets the size of a day (`--cell-size`, `--spacing(7)` by default).
    pub fn cell_size(mut self, size: f32) -> Self {
        self.cell = size;
        self
    }

    /// Sets the padding around the months (`p-2` by default).
    pub fn padding(mut self, padding: f32) -> Self {
        self.padding = padding;
        self
    }

    /// Always shows six weeks (`fixedWeeks`).
    pub fn fixed_weeks(mut self, fixed: bool) -> Self {
        self.fixed_weeks = fixed;
        self
    }

    /// Shows the number of each week (`showWeekNumber`).
    pub fn week_numbers(mut self, show: bool) -> Self {
        self.week_numbers = show;
        self
    }

    /// Replaces the caption with month and year dropdowns covering the
    /// given years (`captionLayout="dropdown"`).
    pub fn dropdowns(mut self, from_year: i32, to_year: i32) -> Self {
        self.dropdowns = Some((from_year.min(to_year), from_year.max(to_year)));
        self
    }
}

#[derive(Clone, Copy, PartialEq)]
enum DayKind {
    Normal,
    Today,
    Selected,
    RangeMiddle,
    RangeStart,
    RangeEnd,
}

/// Which corners of a day are rounded (`rounded-(--cell-radius)`).
#[derive(Clone, Copy, PartialEq)]
struct Corners {
    left: bool,
    right: bool,
}

impl Corners {
    const ALL: Self = Self {
        left: true,
        right: true,
    };

    fn radius(self, radius: f32) -> border::Radius {
        let left = if self.left { radius } else { 0.0 };
        let right = if self.right { radius } else { 0.0 };

        border::radius(0)
            .top_left(left)
            .bottom_left(left)
            .top_right(right)
            .bottom_right(right)
    }
}

/// The style of a day button.
fn day_style(
    theme: &Theme,
    status: Status,
    kind: DayKind,
    outside: bool,
    corners: Corners,
) -> Style {
    let t = tokens(theme);
    let p = &t.palette;
    let dark = t.is_dark();
    let hovered = matches!(status, Status::Hovered | Status::Pressed);
    // `--cell-radius: var(--radius-md)`
    let radius = t.radius_md();

    // Ghost button: `hover:bg-muted dark:hover:bg-muted/50`
    let hover = if dark { alpha(p.muted, 0.5) } else { p.muted };

    let (background, text_color) = match kind {
        DayKind::Selected | DayKind::RangeStart | DayKind::RangeEnd => {
            (Some(p.primary), p.primary_foreground)
        }
        DayKind::RangeMiddle => (Some(p.muted), p.foreground),
        DayKind::Today => (Some(p.muted), p.foreground),
        DayKind::Normal if hovered && status != Status::Disabled => (Some(hover), p.foreground),
        DayKind::Normal => (None, p.foreground),
    };

    let mut text_color = if outside && kind != DayKind::Selected {
        p.muted_foreground
    } else {
        text_color
    };

    if status == Status::Disabled {
        text_color = alpha(p.muted_foreground, style::DISABLED_OPACITY);
    }

    Style {
        background: background.map(Background::Color),
        text_color,
        border: iced::Border {
            radius: corners.radius(radius),
            ..iced::Border::default()
        },
        ..Style::default()
    }
}

fn nav_button<'a, Message: Clone + 'a>(
    icon: shadcn_iced_core::Icon,
    message: Message,
    cell: f32,
) -> Element<'a, Message> {
    // `buttonVariants({ variant: "ghost" }) size-(--cell-size) p-0`
    super::button::icon_button(icon)
        .variant(super::button::Variant::Ghost)
        .size(super::button::Size::IconSm)
        .width(cell)
        .on_press(message)
        .into()
}

fn muted(theme: &Theme) -> text::Style {
    text::Style {
        color: Some(tokens(theme).palette.muted_foreground),
    }
}

/// The week number of the week ending with `saturday`: the week holding
/// January 1st is the first one.
fn week_number(saturday: Date) -> i64 {
    let jan1 = Date {
        year: saturday.year,
        month: 1,
        day: 1,
    };
    let first_sunday = jan1.to_days() - jan1.weekday() as i64;

    (saturday.to_days() - first_sunday) / 7 + 1
}

impl<'a, Message: Clone + 'a> Calendar<'a, Message> {
    fn month_grid(&self, month: Month) -> Element<'a, Message> {
        let cell = self.cell;

        // `text-[0.8rem] font-normal text-muted-foreground`
        let weekdays = Row::new()
            .push(
                self.week_numbers
                    .then(|| iced::widget::space().width(cell).height(1)),
            )
            .extend(WEEKDAYS.iter().map(|day| {
                container(text(*day).size(12.8).style(muted))
                    .center_x(cell)
                    .into()
            }));

        let first = month.first();
        let start = first.add_days(-(first.weekday() as i64));
        // `mt-2` on every week
        let mut weeks = Column::new().push(weekdays).spacing(8);

        let (range_start, range_end) = match self.selection {
            Selection::Range(start, end) => (start, end.or(start)),
            _ => (None, None),
        };

        for week in 0..6 {
            let mut row = Row::new();
            let mut any_inside = false;

            if self.week_numbers {
                row = row.push(
                    container(
                        text(week_number(start.add_days(week * 7 + 6)).to_string())
                            .size(12.8)
                            .style(muted),
                    )
                    .center(cell),
                );
            }

            for weekday in 0..7 {
                let date = start.add_days(week * 7 + weekday);
                let outside = date.month_of() != month;
                any_inside |= !outside;

                if outside && !self.outside_days {
                    row = row.push(iced::widget::space().width(cell).height(cell));
                    continue;
                }

                let disabled = self.disabled.as_ref().is_some_and(|f| f(date));
                let struck = self.struck.as_ref().is_some_and(|f| f(date));

                let kind = match self.selection {
                    Selection::Single(Some(selected)) if selected == date => DayKind::Selected,
                    Selection::Range(..) if Some(date) == range_start => DayKind::RangeStart,
                    Selection::Range(..) if Some(date) == range_end => DayKind::RangeEnd,
                    Selection::Range(..)
                        if range_start
                            .zip(range_end)
                            .is_some_and(|(a, b)| date > a && date < b) =>
                    {
                        DayKind::RangeMiddle
                    }
                    _ if Some(date) == self.today => DayKind::Today,
                    _ => DayKind::Normal,
                };

                // Middle days are square, except at the ends of a week
                // (`[&:first-child[data-selected=true]_button]:rounded-l`).
                let corners = if kind == DayKind::RangeMiddle {
                    Corners {
                        left: weekday == 0,
                        right: weekday == 6,
                    }
                } else {
                    Corners::ALL
                };

                let on_select = (!disabled)
                    .then(|| self.on_select.as_ref().map(|f| f(date)))
                    .flatten();

                // `text-sm font-normal leading-none`
                let label = text(date.day.to_string())
                    .size(style::text::SM)
                    .font(font::sans())
                    .line_height(1.0);

                let label: Element<'a, Message> = if struck {
                    let span: iced::widget::text::Span<'a, (), iced::Font> =
                        iced::widget::span(date.day.to_string()).strikethrough(true);

                    iced::widget::rich_text([span])
                        .size(style::text::SM)
                        .line_height(1.0)
                        .into()
                } else {
                    label.into()
                };

                let day = Pressable::new(container(label).center(cell))
                    .on_press_maybe(on_select)
                    .disabled(disabled)
                    .style(move |theme, status| day_style(theme, status, kind, outside, corners));

                // The range continues behind its ends
                // (`range_start: rounded-l-(--cell-radius) bg-muted`).
                let day: Element<'a, Message> = match kind {
                    DayKind::RangeStart | DayKind::RangeEnd if range_start != range_end => {
                        let start = kind == DayKind::RangeStart;
                        container(day)
                            .style(move |theme: &Theme| {
                                let t = tokens(theme);
                                container::Style {
                                    background: Some(Background::Color(t.palette.muted)),
                                    border: iced::Border {
                                        radius: Corners {
                                            left: start || weekday == 0,
                                            right: !start || weekday == 6,
                                        }
                                        .radius(t.radius_md()),
                                        ..iced::Border::default()
                                    },
                                    ..container::Style::default()
                                }
                            })
                            .into()
                    }
                    _ => day.into(),
                };

                row = row.push(day);
            }

            if any_inside || self.fixed_weeks {
                weeks = weeks.push(row);
            }
        }

        weeks.into()
    }

    fn caption(&self, month: Month) -> Element<'a, Message> {
        let Some((from, to)) = self.dropdowns else {
            // `text-sm font-medium`
            return text(month.to_string())
                .size(style::text::SM)
                .font(font::medium())
                .into();
        };

        let year = month.year;
        let current = month.month;
        let on_month = self.on_navigate.clone();
        let on_year = self.on_navigate.clone();

        let months = (1..=12).fold(
            super::select::select(Some(current), move |month| on_month(Month { year, month })),
            |select, m| select.option(m, &MONTHS[m as usize - 1][..3]),
        );
        let years = (from..=to).fold(
            super::select::select(Some(year), move |year| {
                on_year(Month {
                    year,
                    month: current,
                })
            }),
            |select, y| select.option(y, y.to_string()),
        );

        // `gap-1.5`
        Row::new()
            .push(months.size(super::select::Size::Sm))
            .push(years.size(super::select::Size::Sm))
            .spacing(6)
            .into()
    }
}

impl<'a, Message: Clone + 'a> From<Calendar<'a, Message>> for Element<'a, Message> {
    fn from(calendar: Calendar<'a, Message>) -> Self {
        let cell = calendar.cell;
        let count = calendar.months;
        let grid_width = cell * if calendar.week_numbers { 8.0 } else { 7.0 };
        let mut months = Row::new().spacing(16);
        let mut month = calendar.month;

        for index in 0..count {
            // `month_caption: h-(--cell-size) px-(--cell-size)`, with the
            // navigation at both ends of the months (`nav: absolute`).
            let header = Row::new()
                .push(if index == 0 {
                    nav_button(
                        lucide::CHEVRON_LEFT,
                        (calendar.on_navigate)(calendar.month.previous()),
                        cell,
                    )
                } else {
                    iced::widget::space().width(cell).into()
                })
                .push(
                    container(calendar.caption(month))
                        .center_x(Length::Fill)
                        .center_y(cell),
                )
                .push(if index + 1 == count {
                    nav_button(
                        lucide::CHEVRON_RIGHT,
                        (calendar.on_navigate)(calendar.month.next()),
                        cell,
                    )
                } else {
                    iced::widget::space().width(cell).into()
                })
                .width(grid_width)
                .height(cell)
                .align_y(iced::Alignment::Center);

            // `month: gap-4`
            months = months.push(
                Column::new()
                    .push(header)
                    .push(calendar.month_grid(month))
                    .spacing(16),
            );
            month = month.next();
        }

        let bordered = calendar.bordered;
        let transparent = calendar.transparent;

        container(months)
            .padding(calendar.padding)
            .style(move |theme: &Theme| {
                let t = tokens(theme);
                container::Style {
                    background: (!transparent).then_some(Background::Color(t.palette.background)),
                    border: if bordered {
                        style::border(t.palette.border, t.radius_lg())
                    } else {
                        style::rounded(t.radius_lg())
                    },
                    ..container::Style::default()
                }
            })
            .into()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn days_round_trip() {
        for days in [-1_000_000, -1, 0, 1, 19_000, 20_742, 1_000_000] {
            assert_eq!(Date::from_days(days).to_days(), days);
        }
    }

    #[test]
    fn known_dates() {
        let date = Date::new(2026, 10, 16).unwrap();
        assert_eq!(date.weekday(), 5); // Friday
        assert_eq!(Date::from_days(0), Date::new(1970, 1, 1).unwrap());
        assert_eq!(
            Date::new(2024, 2, 29).map(|d| d.add_days(1)),
            Date::new(2024, 3, 1)
        );
        assert!(Date::new(2023, 2, 29).is_none());
        // The week of January 1st is the first one.
        assert_eq!(week_number(Date::new(2026, 1, 3).unwrap()), 1);
        assert_eq!(week_number(Date::new(2026, 1, 10).unwrap()), 2);
        assert_eq!(date.to_string(), "October 16, 2026");
    }

    #[test]
    fn month_navigation() {
        let month = Month {
            year: 2026,
            month: 12,
        };
        assert_eq!(
            month.next(),
            Month {
                year: 2027,
                month: 1
            }
        );
        assert_eq!(month.next().previous(), month);
    }
}

View it on GitHub

Examples

Basic

extra.rs:54
use crate::pages::DemoMessage;
use crate::Message;
use shadcn_iced::prelude::*;

fn demo(message: DemoMessage) -> Message {
    Message::Demo(message)
}

let d = &gallery.demo;
let month = |m| demo(DemoMessage::Month(m));
calendar(d.month, month).bordered(true).today(None)

Range Calendar

extra.rs:55
use crate::pages::DemoMessage;
use crate::Message;
use shadcn_iced::prelude::*;

fn demo(message: DemoMessage) -> Message {
    Message::Demo(message)
}

let d = &gallery.demo;
let month = |m| demo(DemoMessage::Month(m));
calendar(d.month, month)
    .range(d.range.0, d.range.1)
    .on_select(|date| demo(DemoMessage::Range(date)))
    .months(2)
    .bordered(true)

Month and Year Selector

extra.rs:63
use crate::pages::DemoMessage;
use crate::Message;
use shadcn_iced::calendar::Date;
use shadcn_iced::prelude::*;

fn demo(message: DemoMessage) -> Message {
    Message::Demo(message)
}

let d = &gallery.demo;
let month = |m| demo(DemoMessage::Month(m));
let select = |date| demo(DemoMessage::Date(date));
calendar(d.month, month)
    .dropdowns(2016, 2036)
    .on_select(select)
    .bordered(true)
    .today(None)

Presets

extra.rs:71
use crate::pages::DemoMessage;
use crate::Message;
use shadcn_iced::button::{Size as ButtonSize, Variant as ButtonVariant};
use shadcn_iced::calendar::Date;
use shadcn_iced::prelude::*;
use iced::widget::row;

fn demo(message: DemoMessage) -> Message {
    Message::Demo(message)
}

fn preset<'a>(label: &'a str, days: i64) -> iced::Element<'a, Message> {
    button(label)
        .variant(ButtonVariant::Outline)
        .size(ButtonSize::Sm)
        .on_press(demo(DemoMessage::Preset(days)))
        .into()
}

let d = &gallery.demo;
let month = |m| demo(DemoMessage::Month(m));
let select = |date| demo(DemoMessage::Date(date));
let presets = row![
    preset("Today", 0),
    preset("Tomorrow", 1),
    preset("In 3 days", 3),
    preset("In a week", 7),
    preset("In 2 weeks", 14),
]
.spacing(8)
.wrap();
card()
    .content(
        calendar(d.month, month)
            .selected(d.date)
            .on_select(select)
            .fixed_weeks(true)
            .cell_size(38.0)
            .padding(0.0)
            .transparent(true)
    )
    .footer(presets)
    .width(7.0 * 38.0 + 48.0)

Date and Time Picker

extra.rs:86
use crate::pages::DemoMessage;
use crate::Message;
use shadcn_iced::calendar::Date;
use shadcn_iced::prelude::*;
use iced::widget::column;
use iced::Fill;

fn demo(message: DemoMessage) -> Message {
    Message::Demo(message)
}

let d = &gallery.demo;
let month = |m| demo(DemoMessage::Month(m));
let select = |date| demo(DemoMessage::Date(date));
card()
    .content(
        calendar(d.month, month)
            .selected(d.date)
            .on_select(select)
            .padding(0.0)
            .transparent(true)
    )
    .footer(
        column![
            field("Start Time", input("", "10:30:00").icon(lucide::CLOCK_2)),
            field("End Time", input("", "12:30:00").icon(lucide::CLOCK_2)),
        ]
        .spacing(16)
        .width(Fill)
    )
    .width(7.0 * 28.0 + 48.0)

Booked dates

extra.rs:106
use crate::pages::DemoMessage;
use crate::Message;
use shadcn_iced::calendar::Date;
use shadcn_iced::prelude::*;
use iced::Length;

fn demo(message: DemoMessage) -> Message {
    Message::Demo(message)
}

let d = &gallery.demo;
let month = |m| demo(DemoMessage::Month(m));
let select = |date| demo(DemoMessage::Date(date));
let booked = |date: Date| {
    date.year == d.month.year
        && date.month == d.month.month
        && (17..=31).contains(&date.day)
};
card()
    .content(
        calendar(d.month, month)
            .selected(d.date)
            .on_select(select)
            .disabled(booked)
            .struck(booked)
            .transparent(true)
    )
    .width(Length::Shrink)

Week Numbers

extra.rs:119
use crate::pages::DemoMessage;
use crate::Message;
use shadcn_iced::calendar::Date;
use shadcn_iced::prelude::*;
use iced::Length;

fn demo(message: DemoMessage) -> Message {
    Message::Demo(message)
}

let d = &gallery.demo;
let month = |m| demo(DemoMessage::Month(m));
let select = |date| demo(DemoMessage::Date(date));
card()
    .content(
        calendar(d.month, month)
            .selected(d.date)
            .on_select(select)
            .week_numbers(true)
            .transparent(true)
    )
    .width(Length::Shrink)