Skip to content

Commit f3fa6e0

Browse files
feat(datepicker): DP-175969 add min and max date props (#1053)
Co-authored-by: francisrupert <francis.rupert@dialpad.com>
1 parent a4890ae commit f3fa6e0

File tree

11 files changed

+370
-16
lines changed

11 files changed

+370
-16
lines changed

apps/dialtone-documentation/docs/components/datepicker.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,35 @@ vueCode='
335335
'
336336
/>
337337

338+
### With min/max date
339+
340+
Constrain the selectable date range by providing `min-date` and/or `max-date` props. Days outside the range are disabled and navigation buttons are disabled when the target month is fully out of range.
341+
342+
<code-well-header>
343+
<dt-datepicker
344+
:selected-date="currentSelectedDate"
345+
:min-date="minDate"
346+
:max-date="maxDate"
347+
@selected-date="currentSelectedDate = $event;"
348+
/>
349+
</code-well-header>
350+
351+
<code-example-tabs
352+
vueCode='
353+
<script setup>
354+
const today = new Date();
355+
const minDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() - 5);
356+
const maxDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 15);
357+
</script>
358+
359+
<dt-datepicker
360+
:selected-date="new Date()"
361+
:min-date="minDate"
362+
:max-date="maxDate"
363+
/>
364+
'
365+
showHtmlWarning />
366+
338367
## Vue API
339368

340369
<component-vue-api component-name="datepicker"></component-vue-api>
@@ -529,6 +558,10 @@ const { formatLong, formatMedium, formatShort, formatNoYear, formatNumerical } =
529558
const currentSelectedDate = ref(new Date());
530559
const datepickerOpened = ref(false);
531560

561+
const today = new Date();
562+
const minDate = ref(new Date(today.getFullYear(), today.getMonth(), today.getDate() - 5));
563+
const maxDate = ref(new Date(today.getFullYear(), today.getMonth(), today.getDate() + 15));
564+
532565
const toggleDatepicker = () => {
533566
datepickerOpened.value = !datepickerOpened.value;
534567
};

packages/dialtone-css/lib/build/less/components/datepicker.less

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
padding: 0 var(--dt-space-300);
2323
}
2424

25-
2625
&__month-year {
2726
justify-content: space-between;
2827
width: 100%;
@@ -40,6 +39,11 @@
4039
&__nav-btn {
4140
width: var(--datepicker-button-size);
4241
height: var(--datepicker-button-size);
42+
43+
&:disabled {
44+
background-color: var(--dt-color-neutral-transparent);
45+
opacity: var(--dt-opacity-900);
46+
}
4347
}
4448

4549
&__weekday {
@@ -76,8 +80,8 @@
7680
}
7781

7882
&--disabled {
79-
color: var(--dt-color-foreground-muted);
8083
background-color: var(--dt-color-neutral-transparent);
84+
opacity: var(--dt-opacity-900);
8185
}
8286
}
8387
}

packages/dialtone-vue/components/datepicker/composables/useCalendar.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export function useCalendar (props, emits) {
2525
}
2626

2727
function setDayRef (el, day) {
28-
if (!daysRef.value.some(day => day.el === el) && day.currentMonth) {
28+
if (!daysRef.value.some(day => day.el === el) && !day.disabled) {
2929
daysRef.value.push({ el, day });
3030
}
3131
}
@@ -116,7 +116,7 @@ export function useCalendar (props, emits) {
116116
}
117117

118118
function selectDay (day) {
119-
if (!day.currentMonth) { return; }
119+
if (day.disabled) { return; }
120120

121121
// local selectedDay is updated when a day is selected
122122
selectedDay.value = day.text;

packages/dialtone-vue/components/datepicker/composables/useMonthYearPicker.js

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { computed, ref, watch } from 'vue';
2-
import { addMonths, getDate, getMonth, getYear, set, subMonths } from 'date-fns';
2+
import { addMonths, endOfMonth, getDate, getMonth, getYear, set, startOfDay, startOfMonth, subMonths } from 'date-fns';
33
import { formatMonth, getCalendarDays } from '../utils.js';
44
import { INTL_MONTH_FORMAT } from '../datepicker_constants';
55
import { returnFirstEl } from '@/common/utils';
@@ -14,7 +14,31 @@ export function useMonthYearPicker (props, emits) {
1414
const i18n = new DialtoneLocalization();
1515

1616
const calendarDays = computed(() => {
17-
return getCalendarDays(selectMonth.value, selectYear.value, highlightedDay.value);
17+
return getCalendarDays(selectMonth.value, selectYear.value, highlightedDay.value, props.minDate, props.maxDate);
18+
});
19+
20+
const isPrevMonthDisabled = computed(() => {
21+
if (!props.minDate) return false;
22+
const prevMonth = subMonths(new Date(selectYear.value, selectMonth.value, 1), 1);
23+
return endOfMonth(prevMonth) < startOfDay(props.minDate);
24+
});
25+
26+
const isNextMonthDisabled = computed(() => {
27+
if (!props.maxDate) return false;
28+
const nextMonth = addMonths(new Date(selectYear.value, selectMonth.value, 1), 1);
29+
return startOfMonth(nextMonth) > startOfDay(props.maxDate);
30+
});
31+
32+
const isPrevYearDisabled = computed(() => {
33+
if (!props.minDate) return false;
34+
const prevYearMonth = new Date(selectYear.value - 1, selectMonth.value, 1);
35+
return endOfMonth(prevYearMonth) < startOfDay(props.minDate);
36+
});
37+
38+
const isNextYearDisabled = computed(() => {
39+
if (!props.maxDate) return false;
40+
const nextYearMonth = new Date(selectYear.value + 1, selectMonth.value, 1);
41+
return startOfMonth(nextYearMonth) > startOfDay(props.maxDate);
1842
});
1943

2044
watch(selectMonth, () => {
@@ -27,6 +51,14 @@ export function useMonthYearPicker (props, emits) {
2751
emits('calendar-days', calendarDays.value);
2852
}, { immediate: true });
2953

54+
watch(() => props.minDate, () => {
55+
emits('calendar-days', calendarDays.value);
56+
});
57+
58+
watch(() => props.maxDate, () => {
59+
emits('calendar-days', calendarDays.value);
60+
});
61+
3062
function formattedMonth (month) {
3163
return formatMonth(month, INTL_MONTH_FORMAT, i18n.currentLocale);
3264
}
@@ -93,6 +125,9 @@ export function useMonthYearPicker (props, emits) {
93125
}
94126

95127
function changeMonth (value) {
128+
if (value === -1 && isPrevMonthDisabled.value) return;
129+
if (value === 1 && isNextMonthDisabled.value) return;
130+
96131
// Adjust year when changing from January to December or vice versa
97132
if ((selectMonth.value === 0 && value === -1) || (selectMonth.value === 11 && value === 1)) {
98133
selectYear.value += value;
@@ -107,6 +142,9 @@ export function useMonthYearPicker (props, emits) {
107142
}
108143

109144
function changeYear (value) {
145+
if (value === -1 && isPrevYearDisabled.value) return;
146+
if (value === 1 && isNextYearDisabled.value) return;
147+
110148
selectYear.value = selectYear.value + value;
111149
}
112150

@@ -145,6 +183,10 @@ export function useMonthYearPicker (props, emits) {
145183
changeYear,
146184
goToNextMonth,
147185
goToPrevMonth,
186+
isPrevMonthDisabled,
187+
isNextMonthDisabled,
188+
isPrevYearDisabled,
189+
isNextYearDisabled,
148190
previousYearAriaLabel,
149191
previousMonthAriaLabel,
150192
nextYearAriaLabel,

packages/dialtone-vue/components/datepicker/datepicker.stories.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ export const argsData = {
1111
onCloseDatepicker: action('close-datepicker'),
1212
date: new Date(),
1313
opened: false,
14+
minDate: null,
15+
maxDate: null,
1416
};
1517

1618
export const argTypesData = {

0 commit comments

Comments
 (0)