UNPKG

12.4 kBJavaScriptView Raw
1import { __rest } from "tslib";
2import React, { useEffect } from 'react';
3import { TextInput } from '../TextInput/TextInput';
4import { Button } from '../Button/Button';
5import { Select, SelectOption } from '../Select';
6import ArrowLeftIcon from '@patternfly/react-icons/dist/esm/icons/arrow-left-icon';
7import ArrowRightIcon from '@patternfly/react-icons/dist/esm/icons/arrow-right-icon';
8import { css } from '@patternfly/react-styles';
9import styles from '@patternfly/react-styles/css/components/CalendarMonth/calendar-month';
10import { getUniqueId } from '../../helpers/util';
11export var Weekday;
12(function (Weekday) {
13 Weekday[Weekday["Sunday"] = 0] = "Sunday";
14 Weekday[Weekday["Monday"] = 1] = "Monday";
15 Weekday[Weekday["Tuesday"] = 2] = "Tuesday";
16 Weekday[Weekday["Wednesday"] = 3] = "Wednesday";
17 Weekday[Weekday["Thursday"] = 4] = "Thursday";
18 Weekday[Weekday["Friday"] = 5] = "Friday";
19 Weekday[Weekday["Saturday"] = 6] = "Saturday";
20})(Weekday || (Weekday = {}));
21// Must be numeric given current header design
22const yearFormat = (date) => date.getFullYear();
23const buildCalendar = (year, month, weekStart, validators) => {
24 const defaultDate = new Date(year, month);
25 const firstDayOfWeek = new Date(defaultDate);
26 firstDayOfWeek.setDate(firstDayOfWeek.getDate() - firstDayOfWeek.getDay() + weekStart);
27 // We will always show 6 weeks like google calendar
28 // Assume we just want the numbers for now...
29 const calendarWeeks = [];
30 for (let i = 0; i < 6; i++) {
31 const week = [];
32 for (let j = 0; j < 7; j++) {
33 const date = new Date(firstDayOfWeek);
34 week.push({
35 date,
36 isValid: validators.every(validator => validator(date))
37 });
38 firstDayOfWeek.setDate(firstDayOfWeek.getDate() + 1);
39 }
40 calendarWeeks.push(week);
41 }
42 return calendarWeeks;
43};
44const isSameDate = (d1, d2) => d1.getFullYear() === d2.getFullYear() && d1.getMonth() === d2.getMonth() && d1.getDate() === d2.getDate();
45export const isValidDate = (date) => Boolean(date && !isNaN(date));
46const today = new Date();
47export const CalendarMonth = (_a) => {
48 var { date: dateProp, locale = undefined, monthFormat = date => date.toLocaleDateString(locale, { month: 'long' }), weekdayFormat = date => date.toLocaleDateString(locale, { weekday: 'narrow' }), longWeekdayFormat = date => date.toLocaleDateString(locale, { weekday: 'long' }), dayFormat = date => date.getDate(), weekStart = 0, // Use the American Sunday as a default
49 onChange = () => { }, validators = [() => true], className, onSelectToggle = () => { }, rangeStart, prevMonthAriaLabel = 'Previous month', nextMonthAriaLabel = 'Next month', yearInputAriaLabel = 'Select year', cellAriaLabel } = _a, props = __rest(_a, ["date", "locale", "monthFormat", "weekdayFormat", "longWeekdayFormat", "dayFormat", "weekStart", "onChange", "validators", "className", "onSelectToggle", "rangeStart", "prevMonthAriaLabel", "nextMonthAriaLabel", "yearInputAriaLabel", "cellAriaLabel"]);
50 const longMonths = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11].map(monthNum => new Date(1990, monthNum)).map(monthFormat);
51 const [isSelectOpen, setIsSelectOpen] = React.useState(false);
52 // eslint-disable-next-line prefer-const
53 let [focusedDate, setFocusedDate] = React.useState(new Date(dateProp));
54 if (!isValidDate(focusedDate)) {
55 if (isValidDate(rangeStart)) {
56 focusedDate = rangeStart;
57 }
58 else {
59 focusedDate = today;
60 }
61 }
62 const [hoveredDate, setHoveredDate] = React.useState(new Date(focusedDate));
63 const focusRef = React.useRef();
64 const [hiddenMonthId] = React.useState(getUniqueId('hidden-month-span'));
65 const [shouldFocus, setShouldFocus] = React.useState(false);
66 const isValidated = (date) => validators.every(validator => validator(date));
67 const focusedDateValidated = isValidated(focusedDate);
68 useEffect(() => {
69 if (!(isValidDate(dateProp) && isSameDate(focusedDate, dateProp))) {
70 setFocusedDate(dateProp);
71 }
72 }, [dateProp]);
73 useEffect(() => {
74 // When using header controls don't move focus
75 if (shouldFocus) {
76 if (focusRef.current && focusedDateValidated) {
77 focusRef.current.focus();
78 }
79 }
80 else {
81 setShouldFocus(true);
82 }
83 }, [focusedDate]);
84 const onMonthClick = (newDate) => {
85 setFocusedDate(newDate);
86 setHoveredDate(newDate);
87 setShouldFocus(false);
88 };
89 const onKeyDown = (ev) => {
90 const newDate = new Date(focusedDate);
91 if (ev.key === 'ArrowUp') {
92 newDate.setDate(newDate.getDate() - 7);
93 }
94 else if (ev.key === 'ArrowRight') {
95 newDate.setDate(newDate.getDate() + 1);
96 }
97 else if (ev.key === 'ArrowDown') {
98 newDate.setDate(newDate.getDate() + 7);
99 }
100 else if (ev.key === 'ArrowLeft') {
101 newDate.setDate(newDate.getDate() - 1);
102 }
103 if (newDate.getTime() !== focusedDate.getTime() && isValidated(newDate)) {
104 ev.preventDefault();
105 setFocusedDate(newDate);
106 setHoveredDate(newDate);
107 setShouldFocus(true);
108 }
109 };
110 const addMonth = (toAdd) => {
111 const newDate = new Date(focusedDate);
112 newDate.setMonth(newDate.getMonth() + toAdd);
113 return newDate;
114 };
115 const prevMonth = addMonth(-1);
116 const nextMonth = addMonth(1);
117 const focusedYear = focusedDate.getFullYear();
118 const focusedMonth = focusedDate.getMonth();
119 const calendar = React.useMemo(() => buildCalendar(focusedYear, focusedMonth, weekStart, validators), [
120 focusedYear,
121 focusedMonth,
122 weekStart,
123 validators
124 ]);
125 if (!focusedDateValidated) {
126 const toFocus = calendar
127 .reduce((acc, cur) => [...acc, ...cur], [])
128 .filter(({ date, isValid }) => isValid && date.getMonth() === focusedMonth)
129 .map(({ date }) => ({ date, days: Math.abs(focusedDate.getTime() - date.getTime()) }))
130 .sort((o1, o2) => o1.days - o2.days)
131 .map(({ date }) => date)[0];
132 if (toFocus) {
133 setFocusedDate(toFocus);
134 setHoveredDate(toFocus);
135 }
136 }
137 const isHoveredDateValid = isValidated(hoveredDate);
138 const monthFormatted = monthFormat(focusedDate);
139 const yearFormatted = yearFormat(focusedDate);
140 return (React.createElement("div", Object.assign({ className: css(styles.calendarMonth, className) }, props),
141 React.createElement("div", { className: styles.calendarMonthHeader },
142 React.createElement("div", { className: css(styles.calendarMonthHeaderNavControl, styles.modifiers.prevMonth) },
143 React.createElement(Button, { variant: "plain", "aria-label": prevMonthAriaLabel, onClick: () => onMonthClick(prevMonth) },
144 React.createElement(ArrowLeftIcon, { "aria-hidden": true }))),
145 React.createElement("div", { className: styles.calendarMonthHeaderMonth },
146 React.createElement("span", { id: hiddenMonthId, hidden: true }, "Month"),
147 React.createElement(Select
148 // Max width with "September"
149 , {
150 // Max width with "September"
151 width: "140px", "aria-labelledby": hiddenMonthId, isOpen: isSelectOpen, onToggle: () => {
152 setIsSelectOpen(!isSelectOpen);
153 onSelectToggle(!isSelectOpen);
154 }, onSelect: (_ev, monthNum) => {
155 // When we put CalendarMonth in a Popover we want the Popover's onDocumentClick
156 // to see the SelectOption as a child so it doesn't close the Popover.
157 setTimeout(() => {
158 setIsSelectOpen(false);
159 onSelectToggle(false);
160 const newDate = new Date(focusedDate);
161 newDate.setMonth(Number(monthNum));
162 setFocusedDate(newDate);
163 setHoveredDate(newDate);
164 setShouldFocus(false);
165 }, 0);
166 }, variant: "single", selections: monthFormatted }, longMonths.map((longMonth, index) => (React.createElement(SelectOption, { key: index, value: index, isSelected: longMonth === monthFormatted }, longMonth))))),
167 React.createElement("div", { className: styles.calendarMonthHeaderYear },
168 React.createElement(TextInput, { "aria-label": yearInputAriaLabel, type: "number", value: yearFormatted, onChange: year => {
169 const newDate = new Date(focusedDate);
170 newDate.setFullYear(+year);
171 setFocusedDate(newDate);
172 setHoveredDate(newDate);
173 setShouldFocus(false);
174 } })),
175 React.createElement("div", { className: css(styles.calendarMonthHeaderNavControl, styles.modifiers.nextMonth) },
176 React.createElement(Button, { variant: "plain", "aria-label": nextMonthAriaLabel, onClick: () => onMonthClick(nextMonth) },
177 React.createElement(ArrowRightIcon, { "aria-hidden": true })))),
178 React.createElement("table", { className: styles.calendarMonthCalendar },
179 React.createElement("thead", { className: styles.calendarMonthDays },
180 React.createElement("tr", null, calendar[0].map(({ date }, index) => (React.createElement("th", { key: index, className: styles.calendarMonthDay, scope: "col" },
181 React.createElement("span", { className: "pf-screen-reader" }, longWeekdayFormat(date)),
182 React.createElement("span", { "aria-hidden": true }, weekdayFormat(date))))))),
183 React.createElement("tbody", { onKeyDown: onKeyDown }, calendar.map((week, index) => (React.createElement("tr", { key: index, className: styles.calendarMonthDatesRow }, week.map(({ date, isValid }, index) => {
184 const dayFormatted = dayFormat(date);
185 const isToday = isSameDate(date, today);
186 const isSelected = isValidDate(dateProp) && isSameDate(date, dateProp);
187 const isFocused = isSameDate(date, focusedDate);
188 const isAdjacentMonth = date.getMonth() !== focusedDate.getMonth();
189 const isRangeStart = isValidDate(rangeStart) && isSameDate(date, rangeStart);
190 let isInRange = false;
191 let isRangeEnd = false;
192 if (isValidDate(rangeStart) && isValidDate(dateProp)) {
193 isInRange = date > rangeStart && date < dateProp;
194 isRangeEnd = isSameDate(date, dateProp);
195 }
196 else if (isValidDate(rangeStart) && isHoveredDateValid) {
197 if (hoveredDate > rangeStart || isSameDate(hoveredDate, rangeStart)) {
198 isInRange = date > rangeStart && date < hoveredDate;
199 isRangeEnd = isSameDate(date, hoveredDate);
200 }
201 // Don't handle focused dates before start dates for now.
202 // Core would likely need new styles
203 }
204 return (React.createElement("td", { key: index, className: css(styles.calendarMonthDatesCell, isAdjacentMonth && styles.modifiers.adjacentMonth, isToday && styles.modifiers.current, (isSelected || isRangeStart) && styles.modifiers.selected, !isValid && styles.modifiers.disabled, (isInRange || isRangeStart || isRangeEnd) && styles.modifiers.inRange, isRangeStart && styles.modifiers.startRange, isRangeEnd && styles.modifiers.endRange) },
205 React.createElement("button", Object.assign({ className: css(styles.calendarMonthDate, isRangeEnd && styles.modifiers.hover, !isValid && styles.modifiers.disabled), type: "button", onClick: () => onChange(date), onMouseOver: () => setHoveredDate(date), tabIndex: isFocused ? 0 : -1, disabled: !isValid, "aria-label": cellAriaLabel ? cellAriaLabel(date) : `${dayFormatted} ${monthFormatted} ${yearFormatted}` }, (isFocused && { ref: focusRef })), dayFormatted)));
206 }))))))));
207};
208//# sourceMappingURL=CalendarMonth.js.map
\No newline at end of file