Skip to content

Commit 5e69c04

Browse files
committed
[*] Calendar: date range selection
- Add selection style for last and next month - Fix keyboard selection - Update docs for RangeCalendarDay
1 parent 441a4e9 commit 5e69c04

File tree

2 files changed

+89
-9
lines changed

2 files changed

+89
-9
lines changed

preview/src/components/calendar/style.css

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,21 +104,26 @@
104104
cursor: not-allowed;
105105
}
106106

107+
.calendar-grid-cell[data-month="last"][data-selected="true"],
108+
.calendar-grid-cell[data-month="next"][data-selected="true"] {
109+
background-color: var(--secondary-color-6);
110+
}
111+
107112
.calendar-grid-cell[data-month="current"][data-selected="true"] {
108113
background-color: var(--secondary-color-2);
109114
color: var(--primary-color);
110115
}
111116

112-
.calendar-grid-cell[data-month="current"][date-selection-start="true"] {
117+
.calendar-grid-cell[date-selection-start="true"] {
113118
border-top-right-radius: 0;
114119
border-bottom-right-radius: 0;
115120
}
116121

117-
.calendar-grid-cell[data-month="current"][date-selection-center="true"] {
122+
.calendar-grid-cell[date-selection-between="true"] {
118123
border-radius: 0;
119124
}
120125

121-
.calendar-grid-cell[data-month="current"][date-selection-end="true"] {
126+
.calendar-grid-cell[date-selection-end="true"] {
122127
border-top-left-radius: 0;
123128
border-bottom-left-radius: 0;
124129
}

primitives/src/calendar.rs

Lines changed: 81 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -487,7 +487,7 @@ impl RangeCalendarContext {
487487
}
488488
}
489489

490-
/// The props for the [`Calendar`] component.
490+
/// The props for the [`RangeCalendar`] component.
491491
#[derive(Props, Clone, PartialEq)]
492492
pub struct RangeCalendarProps {
493493
/// The selected range
@@ -578,7 +578,11 @@ pub struct RangeCalendarProps {
578578
/// }
579579
/// }
580580
/// }
581-
/// CalendarGrid {}
581+
/// CalendarGrid {
582+
/// render_day: Callback::new(|date: Date| {
583+
/// rsx! { RangeCalendarDay { date } }
584+
/// })
585+
/// }
582586
/// }
583587
/// }
584588
/// }
@@ -624,9 +628,19 @@ pub fn RangeCalendar(props: RangeCalendarProps) -> Element {
624628
aria_label: "Calendar",
625629
"data-disabled": (props.disabled)(),
626630
onkeydown: move |e| {
627-
let Some(focused_date) = (base_ctx.focused_date)() else {
631+
let Some(mut focused_date) = (base_ctx.focused_date)() else {
628632
return;
629633
};
634+
635+
// force hover day as focus
636+
if let (Some(range), Some(date)) = ((ctx.highlighted_range)(), (ctx.anchor_date)()) {
637+
if date != range.start {
638+
focused_date = range.start
639+
} else {
640+
focused_date = range.end
641+
}
642+
};
643+
630644
let mut set_focused_date = |new_date: Option<Date>| {
631645
// Make sure the view date month is the same as the focused date
632646
let mut view_date = (base_ctx.view_date)();
@@ -1607,6 +1621,63 @@ fn CalendarDay(props: CalendarDayProps) -> Element {
16071621
}
16081622
}
16091623

1624+
/// # RangeCalendarDay
1625+
///
1626+
/// The [`RangeCalendarDay`] component provides an accessible calendar interface for date
1627+
///
1628+
/// This must be used inside a [`CalendarGrid`] component.
1629+
///
1630+
/// ## Example
1631+
/// ```rust
1632+
/// use dioxus::prelude::*;
1633+
/// use dioxus_primitives::calendar::*;
1634+
/// use time::{Date, Month, UtcDateTime};
1635+
/// #[component]
1636+
/// fn Demo() -> Element {
1637+
/// let mut selected_range = use_signal(|| None::<DateRange>);
1638+
/// let mut view_date = use_signal(|| UtcDateTime::now().date());
1639+
/// rsx! {
1640+
/// RangeCalendar {
1641+
/// selected_range: selected_range(),
1642+
/// on_range_change: move |range| {
1643+
/// tracing::info!("Selected range: {:?}", range);
1644+
/// selected_range.set(range);
1645+
/// },
1646+
/// view_date: view_date(),
1647+
/// on_view_change: move |new_view: Date| {
1648+
/// tracing::info!("View changed to: {}-{}", new_view.year(), new_view.month());
1649+
/// view_date.set(new_view);
1650+
/// },
1651+
/// CalendarHeader {
1652+
/// CalendarNavigation {
1653+
/// CalendarPreviousMonthButton {
1654+
/// "<"
1655+
/// }
1656+
/// CalendarMonthTitle {}
1657+
/// CalendarNextMonthButton {
1658+
/// ">"
1659+
/// }
1660+
/// }
1661+
/// }
1662+
/// CalendarGrid {
1663+
/// render_day: Callback::new(|date: Date| {
1664+
/// rsx! { RangeCalendarDay { date } }
1665+
/// })
1666+
/// }
1667+
/// }
1668+
/// }
1669+
/// }
1670+
/// ```
1671+
///
1672+
/// # Styling
1673+
///
1674+
/// The [`RangeCalendarDay`] component defines the following data attributes you can use to control styling:
1675+
/// - `data-disabled`: Indicates if the calendar is disabled. Possible values are `true` or `false`.
1676+
/// - `data-today`: Indicates if the cell is today. Possible values are `true` or `false`.
1677+
/// - `data-selected`: Indicates if the cell is selected. Possible values are `true` or `false`.
1678+
/// - `date-selection-start`: Indicates if cell is the first date in a range selection. Possible values are `true` or `false`.
1679+
/// - `date-selection-between`: Indicates if a date interval contains a cell. Possible values are `true` or `false`.
1680+
/// - `date-selection-end`: Indicates if cell is the last date in a range selection. Possible values are `true` or `false`.
16101681
#[component]
16111682
pub fn RangeCalendarDay(props: CalendarDayProps) -> Element {
16121683
let CalendarDayProps { date, attributes } = props;
@@ -1631,7 +1702,7 @@ pub fn RangeCalendarDay(props: CalendarDayProps) -> Element {
16311702
};
16321703
let in_current_month = month == RelativeMonth::Current;
16331704
let is_selected = move || (ctx.highlighted_range)().is_some_and(|r| r.contains(date));
1634-
let is_center =
1705+
let is_between =
16351706
move || (ctx.highlighted_range)().is_some_and(|r| r.contained_in_interval(date));
16361707
let is_start =
16371708
move || (ctx.highlighted_range)().is_some_and(|d| d.start == date && date != d.end);
@@ -1685,7 +1756,7 @@ pub fn RangeCalendarDay(props: CalendarDayProps) -> Element {
16851756
"data-today": is_today,
16861757
"data-selected": is_selected(),
16871758
"date-selection-start": if is_start() { true },
1688-
"date-selection-center": if is_center() { true },
1759+
"date-selection-between": if is_between() { true },
16891760
"date-selection-end": if is_end() { true },
16901761
"data-month": "{month}",
16911762
onclick: move |e| {
@@ -1699,7 +1770,11 @@ pub fn RangeCalendarDay(props: CalendarDayProps) -> Element {
16991770
base_ctx.focused_date.set(Some(date));
17001771
}
17011772
},
1702-
onmouseover: move |_| ctx.set_hovered_date(date),
1773+
onmouseover: move |_| {
1774+
if in_current_month {
1775+
ctx.set_hovered_date(date)
1776+
}
1777+
},
17031778
onmounted: move |e| day_ref.set(Some(e.data())),
17041779
..attributes,
17051780
{day.to_string()}

0 commit comments

Comments
 (0)