Skip to content

Commit 015725f

Browse files
committed
[*] DatePicker: support disabled ranges
1 parent 700d138 commit 015725f

File tree

5 files changed

+86
-7
lines changed

5 files changed

+86
-7
lines changed

preview/src/components/date_picker/component.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ pub fn DatePicker(props: DatePickerProps) -> Element {
2020
selected_date: props.selected_date,
2121
disabled: props.disabled,
2222
read_only: props.read_only,
23+
min_date: props.min_date,
24+
max_date: props.max_date,
25+
disabled_ranges: props.disabled_ranges,
26+
roving_loop: props.roving_loop,
2327
attributes: props.attributes,
2428
date_picker::DatePickerPopover {
2529
popover_root: PopoverRoot,
@@ -41,6 +45,10 @@ pub fn DateRangePicker(props: DateRangePickerProps) -> Element {
4145
selected_range: props.selected_range,
4246
disabled: props.disabled,
4347
read_only: props.read_only,
48+
min_date: props.min_date,
49+
max_date: props.max_date,
50+
disabled_ranges: props.disabled_ranges,
51+
roving_loop: props.roving_loop,
4452
attributes: props.attributes,
4553
date_picker::DatePickerPopover {
4654
popover_root: PopoverRoot,
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
use super::super::component::*;
2+
use dioxus::prelude::*;
3+
4+
use dioxus_primitives::calendar::DateRange;
5+
use time::{ext::NumericalDuration, UtcDateTime};
6+
7+
#[component]
8+
pub fn Demo() -> Element {
9+
let mut selected_range = use_signal(|| None::<DateRange>);
10+
11+
let now = UtcDateTime::now().date();
12+
let disabled_ranges = use_signal(|| {
13+
vec![
14+
DateRange::new(now, now.saturating_add(3.days())),
15+
DateRange::new(now.saturating_add(15.days()), now.saturating_add(18.days())),
16+
DateRange::new(now.saturating_add(22.days()), now.saturating_add(23.days())),
17+
]
18+
});
19+
20+
rsx! {
21+
div {
22+
DateRangePicker {
23+
selected_range: selected_range(),
24+
on_range_change: move |range| {
25+
tracing::info!("Selected range: {:?}", range);
26+
selected_range.set(range);
27+
},
28+
disabled_ranges: disabled_ranges,
29+
DateRangePickerInput {}
30+
}
31+
}
32+
}
33+
}

preview/src/components/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ examples!(
6666
checkbox,
6767
collapsible,
6868
context_menu,
69-
date_picker[internationalized, range],
69+
date_picker[internationalized, range, unavailable_dates],
7070
dialog,
7171
dropdown_menu,
7272
hover_card,

primitives/src/calendar.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -201,14 +201,16 @@ impl Display for DateRange {
201201
}
202202
}
203203

204+
/// Calendar available dates
204205
#[derive(Debug, Clone, PartialEq)]
205-
struct AvailableRanges {
206+
pub struct AvailableRanges {
206207
/// A sorted list of dates. Values after an odd number of elements are disabled.
207208
changes: Vec<Date>,
208209
}
209210

210211
impl AvailableRanges {
211-
fn new(disabled_ranges: &[DateRange]) -> Self {
212+
/// Create a new available dates
213+
pub fn new(disabled_ranges: &[DateRange]) -> Self {
212214
let mut sorted_range: Vec<_> = disabled_ranges
213215
.iter()
214216
.enumerate()
@@ -236,7 +238,8 @@ impl AvailableRanges {
236238
}
237239
}
238240

239-
fn valid_interval(&self, date: Date) -> bool {
241+
/// Check the availability of given date
242+
pub fn valid_interval(&self, date: Date) -> bool {
240243
match self.changes.binary_search(&date) {
241244
Ok(_) => false,
242245
Err(index) => index % 2 == 0,
@@ -266,6 +269,14 @@ impl AvailableRanges {
266269

267270
Some(DateRange::new(start, end))
268271
}
272+
273+
/// Get disabled ranges
274+
pub fn to_disabled_ranges(&self) -> Vec<DateRange> {
275+
self.changes
276+
.chunks(2)
277+
.map(|d| DateRange::new(d[0], d[1]))
278+
.collect()
279+
}
269280
}
270281

271282
/// The base context provided by the [`Calendar`] and the [`RangeCalendar`] component to its children.

primitives/src/date_picker.rs

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
//! Defines the [`DatePicker`] and [`DateRangePicker`] components and its subcomponents, which allowing users to enter or select a date value
22
33
use crate::{
4-
calendar::{weekday_abbreviation, CalendarProps, DateRange, RangeCalendarProps},
4+
calendar::{
5+
weekday_abbreviation, AvailableRanges, CalendarProps, DateRange, RangeCalendarProps,
6+
},
57
dioxus_core::Properties,
68
focus::{use_focus_controlled_item, use_focus_provider, FocusState},
79
popover::*,
@@ -24,6 +26,7 @@ struct BaseDatePickerContext {
2426
disabled: ReadSignal<bool>,
2527
focus: FocusState,
2628
enabled_date_range: DateRange,
29+
available_ranges: Memo<AvailableRanges>,
2730
}
2831

2932
/// The context provided by the [`DatePicker`] component to its children.
@@ -69,6 +72,10 @@ pub struct DatePickerProps {
6972
#[props(default = date!(2050-12-31))]
7073
pub max_date: Date,
7174

75+
/// Unavailable dates
76+
#[props(default)]
77+
pub disabled_ranges: ReadSignal<Vec<DateRange>>,
78+
7279
/// Whether focus should loop around when reaching the end.
7380
#[props(default = ReadSignal::new(Signal::new(false)))]
7481
pub roving_loop: ReadSignal<bool>,
@@ -128,6 +135,7 @@ pub struct DatePickerProps {
128135
pub fn DatePicker(props: DatePickerProps) -> Element {
129136
let open = use_signal(|| false);
130137
let focus = use_focus_provider(props.roving_loop);
138+
let available_ranges = use_memo(move || AvailableRanges::new(&props.disabled_ranges.read()));
131139

132140
// Create context provider for child components
133141
use_context_provider(|| BaseDatePickerContext {
@@ -136,6 +144,7 @@ pub fn DatePicker(props: DatePickerProps) -> Element {
136144
disabled: props.disabled,
137145
focus,
138146
enabled_date_range: DateRange::new(props.min_date, props.max_date),
147+
available_ranges,
139148
});
140149

141150
use_context_provider(|| DatePickerContext {
@@ -199,6 +208,10 @@ pub struct DateRangePickerProps {
199208
#[props(default = date!(2050-12-31))]
200209
pub max_date: Date,
201210

211+
/// Unavailable dates
212+
#[props(default)]
213+
pub disabled_ranges: ReadSignal<Vec<DateRange>>,
214+
202215
/// Whether focus should loop around when reaching the end.
203216
#[props(default = ReadSignal::new(Signal::new(false)))]
204217
pub roving_loop: ReadSignal<bool>,
@@ -258,13 +271,16 @@ pub fn DateRangePicker(props: DateRangePickerProps) -> Element {
258271
let open = use_signal(|| false);
259272
let focus = use_focus_provider(props.roving_loop);
260273

274+
let available_ranges = use_memo(move || AvailableRanges::new(&props.disabled_ranges.read()));
275+
261276
// Create context provider for child components
262277
use_context_provider(|| BaseDatePickerContext {
263278
open,
264279
read_only: props.read_only,
265280
disabled: props.disabled,
266281
focus,
267282
enabled_date_range: DateRange::new(props.min_date, props.max_date),
283+
available_ranges,
268284
});
269285

270286
let date_range = use_signal(|| (props.selected_range)());
@@ -410,6 +426,10 @@ pub struct DatePickerCalendarProps<T: Properties + PartialEq> {
410426
#[props(default = date!(2050-12-31))]
411427
pub max_date: Date,
412428

429+
/// Unavailable dates
430+
#[props(default)]
431+
pub disabled_ranges: ReadSignal<Vec<DateRange>>,
432+
413433
/// Additional attributes to extend the calendar element
414434
#[props(extends = GlobalAttributes)]
415435
pub attributes: Vec<Attribute>,
@@ -484,6 +504,7 @@ pub fn DatePickerCalendar(props: DatePickerCalendarProps<CalendarProps>) -> Elem
484504
ctx.set_date(date);
485505
base_ctx.open.set(false);
486506
},
507+
disabled_ranges: base_ctx.available_ranges.read().to_disabled_ranges(),
487508
on_format_weekday: props.on_format_weekday,
488509
on_format_month: props.on_format_month,
489510
view_date: view_date(),
@@ -561,6 +582,7 @@ pub fn DateRangePickerCalendar(props: DatePickerCalendarProps<RangeCalendarProps
561582
ctx.set_range(range);
562583
base_ctx.open.set(false);
563584
},
585+
disabled_ranges: base_ctx.available_ranges.read().to_disabled_ranges(),
564586
on_format_weekday: props.on_format_weekday,
565587
on_format_month: props.on_format_month,
566588
view_date: view_date(),
@@ -857,8 +879,13 @@ fn DateElement(props: DateElementProps) -> Element {
857879
.ok()
858880
.filter(|date| ctx.enabled_date_range.contains(*date))
859881
{
882+
let new_date = if ctx.available_ranges.read().valid_interval(date) {
883+
Some(date)
884+
} else {
885+
None
886+
};
860887
tracing::info!("Parsed date: {date:?}");
861-
props.on_date_change.call(Some(date));
888+
props.on_date_change.call(new_date);
862889
}
863890
}
864891
});
@@ -1069,7 +1096,7 @@ pub fn DateRangePickerInput(props: DatePickerInputProps) -> Element {
10691096
use_effect(move || {
10701097
if let (Some(start), Some(end)) = (start_date(), end_date()) {
10711098
let range = Some(DateRange::new(start, end));
1072-
tracing::info!("set range {range:?}");
1099+
10731100
ctx.set_range(range);
10741101
};
10751102
});

0 commit comments

Comments
 (0)