Skip to content
This repository has been archived by the owner on Sep 2, 2024. It is now read-only.

Commit

Permalink
Merge pull request #52 from bseber/provide-a-way-to-disable-days
Browse files Browse the repository at this point in the history
  • Loading branch information
WickyNilliams authored Apr 30, 2021
2 parents dcf45af + 1000f33 commit 1ebd1a2
Show file tree
Hide file tree
Showing 11 changed files with 508 additions and 19 deletions.
66 changes: 66 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -599,6 +599,72 @@ Duet Date Picker offers full support for localization. This includes the text la

Please note that you must provide the entirety of the localization properties in the object when overriding with your coustom localization.

## Control which days are selectable

Duet Date Picker allows you to disable the selection of specific days. Below is an example of a date picker that is disabling weekends.

```html
<label for="date">Choose a date</label>
<duet-date-picker identifier="date"></duet-date-picker>

<script>
const pickerDisableWeekend = document.querySelector("duet-date-picker")
const DATE_FORMAT = /^(\d{4})-(\d{1,2})-(\d{1,2})$/
pickerDisableWeekend.dateAdapter = {
parse() {
var value = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ""
var createDate = arguments.length > 1 ? arguments[1] : undefined
var matches = value.match(DATE_FORMAT)
if (matches) {
return createDate(matches[3], matches[2], matches[1])
}
},
format(date) {
return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`
},
isDateDisabled(date, focusedDay) {
return (
date.getDay() === 0 ||
date.getDay() === 6 ||
// disable next and previous month (default behaviour)
!(date.getFullYear() === focusedDay.getFullYear() && date.getMonth() === focusedDay.getMonth())
)
},
}
pickerDisableWeekend.localization = {
buttonLabel: "Choose date",
placeholder: "YYYY-MM-DD",
selectedDateMessage: "Selected date is",
prevMonthLabel: "Previous month",
nextMonthLabel: "Next month",
monthSelectLabel: "Month",
yearSelectLabel: "Year",
closeLabel: "Close window",
keyboardInstruction: "You can use arrow keys to navigate dates",
calendarHeading: "Choose a date",
dayNames: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
monthNames: [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
],
monthNamesShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
}
</script>
```

## Server side rendering

Duet Date Picker package includes a hydrate app that is a bundle of the same components, but compiled so that they can be hydrated on a NodeJS server and generate static HTML and CSS. To get started, import the hydrate app into your server’s code like so:
Expand Down
123 changes: 123 additions & 0 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,129 @@ <h2>Required atrribute</h2>
alert("Submitted")
})
&lt;/script&gt;</code></pre>

<h2>Disable selectable days</h2>
<label for="dateDisabledWeekend">Choose a date</label>
<duet-date-picker class="picker-disabled-weekend" identifier="dateDisabledWeekend"></duet-date-picker>
<script>
const pickerDisableWeekend = document.querySelector(".picker-disabled-weekend")
const PICKER_DISABLED_DATE_FORMAT = /^(\d{4})-(\d{1,2})-(\d{1,2})$/

pickerDisableWeekend.dateAdapter = {
parse: function parse() {
var value = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ""
var createDate = arguments.length > 1 ? arguments[1] : undefined
var matches = value.match(PICKER_DISABLED_DATE_FORMAT)

if (matches) {
return createDate(matches[3], matches[2], matches[1])
}
},
format: function format(date) {
return ""
.concat(date.getFullYear(), "-")
.concat(date.getMonth() + 1, "-")
.concat(date.getDate())
},
isDateDisabled: function isDateDisabled(date, focusedDay) {
return (
date.getDay() === 0 ||
date.getDay() === 6 ||
!(date.getFullYear() === focusedDay.getFullYear() && date.getMonth() === focusedDay.getMonth())
)
},
}

pickerDisableWeekend.localization = {
buttonLabel: "Choose date",
placeholder: "YYYY-MM-DD",
selectedDateMessage: "Selected date is",
prevMonthLabel: "Previous month",
nextMonthLabel: "Next month",
monthSelectLabel: "Month",
yearSelectLabel: "Year",
closeLabel: "Close window",
keyboardInstruction: "You can use arrow keys to navigate dates",
calendarHeading: "Choose a date",
dayNames: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
monthNames: [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
],
monthNamesShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
}
</script>
<pre><code class="html">&lt;label for="date"&gt;Choose a date&lt;/label&gt;
&lt;duet-date-picker first-day-of-week="0" identifier="date"&gt;&lt;/duet-date-picker&gt;

&lt;script&gt;
const pickerDisableWeekend = document.querySelector(".picker-disabled-weekend")
const PICKER_DISABLED_DATE_FORMAT = /^(\d{4})-(\d{1,2})-(\d{1,2})$/

pickerDisableWeekend.dateAdapter = {
parse: function parse() {
var value = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ""
var createDate = arguments.length > 1 ? arguments[1] : undefined
var matches = value.match(PICKER_DISABLED_DATE_FORMAT)

if (matches) {
return createDate(matches[3], matches[2], matches[1])
}
},
format: function format(date) {
return ""
.concat(date.getFullYear(), "-")
.concat(date.getMonth() + 1, "-")
.concat(date.getDate())
},
isDateDisabled: function isDateDisabled(date, focusedDay) {
return (
date.getDay() === 0 ||
date.getDay() === 6 ||
!(date.getFullYear() === focusedDay.getFullYear() && date.getMonth() === focusedDay.getMonth())
)
},
}

picker.localization = {
buttonLabel: "Choose date",
placeholder: "YYYY-MM-DD",
selectedDateMessage: "Selected date is",
prevMonthLabel: "Previous month",
nextMonthLabel: "Next month",
monthSelectLabel: "Month",
yearSelectLabel: "Year",
closeLabel: "Close window",
keyboardInstruction: "You can use arrow keys to navigate dates",
calendarHeading: "Choose a date",
dayNames: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
monthNames: [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
],
monthNamesShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
}
&lt;/script&gt;</code></pre>
<br />
<p>
© 2020 LocalTapiola Services Ltd /
Expand Down
1 change: 1 addition & 0 deletions src/components/duet-date-picker/date-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export type DuetDateFormatter = (date: Date) => string
export interface DuetDateAdapter {
parse: DuetDateParser
format: DuetDateFormatter
isDateDisabled?: (date: Date, focusedDate: Date) => boolean
}

const isoAdapter: DuetDateAdapter = { parse: parseISODate, format: printISODate }
Expand Down
39 changes: 34 additions & 5 deletions src/components/duet-date-picker/date-picker-day.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import { h, FunctionalComponent } from "@stencil/core"
import { isEqual } from "./date-utils"
import { isEqual, isEqualMonth } from "./date-utils"

export type DatePickerDayProps = {
focusedDay: Date
today: Date
day: Date
disabled: boolean
inRange: boolean
isSelected: boolean
dateFormatter: Intl.DateTimeFormat
onDaySelect: (event: MouseEvent, day: Date) => void
onKeyboardNavigation: (event: KeyboardEvent) => void
focusedDayRef?: (element: HTMLButtonElement) => void
focusedDayRef?: (element: HTMLElement) => void
}

export const DatePickerDay: FunctionalComponent<DatePickerDayProps> = ({
Expand All @@ -20,31 +21,59 @@ export const DatePickerDay: FunctionalComponent<DatePickerDayProps> = ({
onDaySelect,
onKeyboardNavigation,
focusedDayRef,
disabled,
inRange,
isSelected,
dateFormatter,
}) => {
const isToday = isEqual(day, today)
const isMonth = isEqualMonth(day, focusedDay)
const isFocused = isEqual(day, focusedDay)
const isDisabled = day.getMonth() !== focusedDay.getMonth()
const isOutsideRange = !inRange

function handleClick(e) {
onDaySelect(e, day)
}

if (disabled) {
return (
<span
class={{
"duet-date__day": true,
"is-outside": isOutsideRange,
"is-disabled": disabled,
"is-today": isToday,
"is-month": isMonth,
}}
role="button"
tabIndex={isFocused ? 0 : -1}
onKeyDown={onKeyboardNavigation}
aria-pressed="false"
aria-disabled="true"
ref={el => {
if (isFocused && el && focusedDayRef) {
focusedDayRef(el)
}
}}
>
<span aria-hidden="true">{day.getDate()}</span>
<span class="duet-date__vhidden">{dateFormatter.format(day)}</span>
</span>
)
}

return (
<button
class={{
"duet-date__day": true,
"is-outside": isOutsideRange,
"is-disabled": isDisabled,
"is-today": isToday,
"is-month": isMonth,
}}
tabIndex={isFocused ? 0 : -1}
onClick={handleClick}
onKeyDown={onKeyboardNavigation}
disabled={isOutsideRange || isDisabled}
disabled={isOutsideRange}
type="button"
aria-pressed={isSelected ? "true" : "false"}
ref={el => {
Expand Down
5 changes: 4 additions & 1 deletion src/components/duet-date-picker/date-picker-month.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,10 @@ type DatePickerMonthProps = {
min?: Date
max?: Date
dateFormatter: Intl.DateTimeFormat
isDateDisabled: (date: Date) => boolean
onDateSelect: DatePickerDayProps["onDaySelect"]
onKeyboardNavigation: DatePickerDayProps["onKeyboardNavigation"]
focusedDayRef: (element: HTMLButtonElement) => void
focusedDayRef: (element: HTMLElement) => void
}

export const DatePickerMonth: FunctionalComponent<DatePickerMonthProps> = ({
Expand All @@ -43,6 +44,7 @@ export const DatePickerMonth: FunctionalComponent<DatePickerMonthProps> = ({
min,
max,
dateFormatter,
isDateDisabled,
onDateSelect,
onKeyboardNavigation,
focusedDayRef,
Expand Down Expand Up @@ -72,6 +74,7 @@ export const DatePickerMonth: FunctionalComponent<DatePickerMonthProps> = ({
today={today}
focusedDay={focusedDate}
isSelected={isEqual(day, selectedDate)}
disabled={isDateDisabled(day)}
inRange={inRange(day, min, max)}
onDaySelect={onDateSelect}
dateFormatter={dateFormatter}
Expand Down
15 changes: 15 additions & 0 deletions src/components/duet-date-picker/date-utils.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
isEqual,
isEqualMonth,
addDays,
addMonths,
addYears,
Expand Down Expand Up @@ -67,6 +68,20 @@ describe("duet-date-picker/date-utils", () => {
})
})

describe("isEqualMonth", () => {
it("compares dates", () => {
expect(isEqualMonth(new Date(2020, 0, 1), new Date(2020, 0, 1))).toBe(true)
expect(isEqualMonth(new Date(2020, 0, 1), new Date(2020, 0, 31))).toBe(true)

expect(isEqualMonth(new Date(2020, 0, 1), new Date(2020, 1, 1))).toBe(false)
expect(isEqualMonth(new Date(2020, 0, 1), new Date(2021, 0, 1))).toBe(false)

expect(isEqualMonth(null, new Date(2020, 0, 1))).toBe(false)
expect(isEqualMonth(new Date(2020, 0, 1), null)).toBe(false)
expect(isEqualMonth(null, null)).toBe(false)
})
})

describe("printISODate", () => {
it("should print in format dd.mm.yyyy", () => {
expect(printISODate(new Date(2020, 0, 1))).toBe("2020-01-01")
Expand Down
13 changes: 12 additions & 1 deletion src/components/duet-date-picker/date-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,18 @@ export function isEqual(a: Date, b: Date): boolean {
return false
}

return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate()
return isEqualMonth(a, b) && a.getDate() === b.getDate()
}

/**
* Compare if two dates are in the same month of the same year.
*/
export function isEqualMonth(a: Date, b: Date): boolean {
if (a == null || b == null) {
return false
}

return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth()
}

export function addDays(date: Date, days: number): Date {
Expand Down
Loading

0 comments on commit 1ebd1a2

Please sign in to comment.