UNPKG

16 kBJavaScriptView Raw
1import React, { useCallback, useMemo } from "react";
2import { UI, DayFlag, SelectionState } from "./UI.js";
3import { getClassNamesForModifiers } from "./helpers/getClassNamesForModifiers.js";
4import { getComponents } from "./helpers/getComponents.js";
5import { getDataAttributes } from "./helpers/getDataAttributes.js";
6import { getDateLib } from "./helpers/getDateLib.js";
7import { getDefaultClassNames } from "./helpers/getDefaultClassNames.js";
8import { getFormatters } from "./helpers/getFormatters.js";
9import { getMonthOptions } from "./helpers/getMonthOptions.js";
10import { getStyleForModifiers } from "./helpers/getStyleForModifiers.js";
11import { getWeekdays } from "./helpers/getWeekdays.js";
12import { getYearOptions } from "./helpers/getYearOptions.js";
13import * as defaultLabels from "./labels/index.js";
14import { enUS } from "./lib/locales.js";
15import { useCalendar } from "./useCalendar.js";
16import { dayPickerContext } from "./useDayPicker.js";
17import { useFocus } from "./useFocus.js";
18import { useGetModifiers } from "./useGetModifiers.js";
19import { useSelection } from "./useSelection.js";
20import { rangeIncludesDate } from "./utils/rangeIncludesDate.js";
21import { isDateRange } from "./utils/typeguards.js";
22/**
23 * Render the date picker calendar.
24 *
25 * @group DayPicker
26 * @see https://daypicker.dev
27 */
28export function DayPicker(props) {
29 const { components, formatters, labels, dateLib, locale, classNames } = useMemo(() => ({
30 dateLib: getDateLib(props.dateLib),
31 components: getComponents(props.components),
32 formatters: getFormatters(props.formatters),
33 labels: { ...defaultLabels, ...props.labels },
34 locale: { ...enUS, ...props.locale },
35 classNames: { ...getDefaultClassNames(), ...props.classNames }
36 }), [
37 props.classNames,
38 props.components,
39 props.dateLib,
40 props.formatters,
41 props.labels,
42 props.locale
43 ]);
44 const { captionLayout, firstWeekContainsDate, mode, onDayBlur, onDayClick, onDayFocus, onDayKeyDown, onDayMouseEnter, onDayMouseLeave, onNextClick, onPrevClick, showWeekNumber, styles, useAdditionalDayOfYearTokens, useAdditionalWeekYearTokens, weekStartsOn } = props;
45 const formatOptions = {
46 locale,
47 weekStartsOn,
48 firstWeekContainsDate,
49 useAdditionalWeekYearTokens,
50 useAdditionalDayOfYearTokens
51 };
52 const labelOptions = formatOptions;
53 const { formatCaption, formatDay, formatMonthDropdown, formatWeekNumber, formatWeekNumberHeader, formatWeekdayName, formatYearDropdown } = formatters;
54 const calendar = useCalendar(props, dateLib);
55 const { days, months, navStart, navEnd, previousMonth, nextMonth, goToMonth } = calendar;
56 const getModifiers = useGetModifiers(days, props, dateLib);
57 const { isSelected, select, selected: selectedValue } = useSelection(props, dateLib) ?? {};
58 const { blur, focused, isFocusTarget, moveFocus, setFocused } = useFocus(props, calendar, getModifiers, isSelected ?? (() => false), dateLib);
59 const { labelDayButton, labelGridcell, labelGrid, labelMonthDropdown, labelNav, labelNext, labelPrevious, labelWeekday, labelWeekNumber, labelWeekNumberHeader, labelYearDropdown } = labels;
60 const weekdays = useMemo(() => getWeekdays(locale, props.weekStartsOn, props.ISOWeek, dateLib), [dateLib, locale, props.ISOWeek, props.weekStartsOn]);
61 const isInteractive = mode !== undefined || onDayClick !== undefined;
62 const handlePreviousClick = useCallback(() => {
63 if (!previousMonth)
64 return;
65 goToMonth(previousMonth);
66 onPrevClick?.(previousMonth);
67 }, [previousMonth, goToMonth, onPrevClick]);
68 const handleNextClick = useCallback(() => {
69 if (!nextMonth)
70 return;
71 goToMonth(nextMonth);
72 onNextClick?.(nextMonth);
73 }, [goToMonth, nextMonth, onNextClick]);
74 const handleDayClick = useCallback((day, m) => (e) => {
75 e.preventDefault();
76 e.stopPropagation();
77 setFocused(day);
78 select?.(day.date, m, e);
79 onDayClick?.(day.date, m, e);
80 }, [select, onDayClick, setFocused]);
81 const handleDayFocus = useCallback((day, m) => (e) => {
82 setFocused(day);
83 onDayFocus?.(day.date, m, e);
84 }, [onDayFocus, setFocused]);
85 const handleDayBlur = useCallback((day, m) => (e) => {
86 blur();
87 onDayBlur?.(day.date, m, e);
88 }, [blur, onDayBlur]);
89 const handleDayKeyDown = useCallback((day, modifiers) => (e) => {
90 const keyMap = {
91 ArrowLeft: ["day", props.dir === "rtl" ? "after" : "before"],
92 ArrowRight: ["day", props.dir === "rtl" ? "before" : "after"],
93 ArrowDown: ["week", "after"],
94 ArrowUp: ["week", "before"],
95 PageUp: [e.shiftKey ? "year" : "month", "before"],
96 PageDown: [e.shiftKey ? "year" : "month", "after"],
97 Home: ["startOfWeek", "before"],
98 End: ["endOfWeek", "after"]
99 };
100 if (keyMap[e.key]) {
101 e.preventDefault();
102 e.stopPropagation();
103 const [moveBy, moveDir] = keyMap[e.key];
104 moveFocus(moveBy, moveDir);
105 }
106 onDayKeyDown?.(day.date, modifiers, e);
107 }, [moveFocus, onDayKeyDown, props.dir]);
108 const handleDayMouseEnter = useCallback((day, modifiers) => (e) => {
109 onDayMouseEnter?.(day.date, modifiers, e);
110 }, [onDayMouseEnter]);
111 const handleDayMouseLeave = useCallback((day, modifiers) => (e) => {
112 onDayMouseLeave?.(day.date, modifiers, e);
113 }, [onDayMouseLeave]);
114 const { className, style } = useMemo(() => ({
115 className: [classNames[UI.Root], props.className]
116 .filter(Boolean)
117 .join(" "),
118 style: { ...styles?.[UI.Root], ...props.style }
119 }), [classNames, props.className, props.style, styles]);
120 const dataAttributes = getDataAttributes(props);
121 const contextValue = {
122 selected: selectedValue,
123 select: select,
124 isSelected,
125 months,
126 nextMonth,
127 previousMonth,
128 goToMonth,
129 getModifiers
130 };
131 return (React.createElement(dayPickerContext.Provider, { value: contextValue },
132 React.createElement(components.Root, { className: className, style: style, dir: props.dir, id: props.id, lang: props.lang, nonce: props.nonce, title: props.title, ...dataAttributes },
133 React.createElement(components.Months, { className: classNames[UI.Months], style: styles?.[UI.Months] },
134 !props.hideNavigation && (React.createElement(components.Nav, { role: "navigation", className: classNames[UI.Nav], style: styles?.[UI.Nav], "aria-label": labelNav() },
135 React.createElement(components.Button, { type: "button", className: classNames[UI.ButtonPrevious], tabIndex: previousMonth ? undefined : -1, disabled: previousMonth ? undefined : true, "aria-label": labelPrevious(previousMonth, labelOptions), onClick: handlePreviousClick },
136 React.createElement(components.Chevron, { disabled: previousMonth ? undefined : true, className: classNames[UI.Chevron], orientation: "left" })),
137 React.createElement(components.Button, { type: "button", className: classNames[UI.ButtonNext], tabIndex: nextMonth ? undefined : -1, disabled: nextMonth ? undefined : true, "aria-label": labelNext(nextMonth, labelOptions), onClick: handleNextClick },
138 React.createElement(components.Chevron, { disabled: previousMonth ? undefined : true, orientation: "right", className: classNames[UI.Chevron] })))),
139 months.map((calendarMonth, displayIndex) => {
140 const handleMonthChange = (e) => {
141 const selectedMonth = Number(e.target.value);
142 const month = dateLib.setMonth(dateLib.startOfMonth(calendarMonth.date), selectedMonth);
143 goToMonth(month);
144 };
145 const handleYearChange = (e) => {
146 const month = dateLib.setYear(dateLib.startOfMonth(calendarMonth.date), Number(e.target.value));
147 goToMonth(month);
148 };
149 const dropdownMonths = getMonthOptions(calendarMonth.date, navStart, navEnd, formatters, locale, dateLib);
150 const dropdownYears = getYearOptions(months[0].date, navStart, navEnd, formatters, dateLib);
151 return (React.createElement(components.Month, { className: classNames[UI.Month], style: styles?.[UI.Month], key: displayIndex, displayIndex: displayIndex, calendarMonth: calendarMonth },
152 React.createElement(components.MonthCaption, { className: classNames[UI.MonthCaption], style: styles?.[UI.MonthCaption], calendarMonth: calendarMonth, displayIndex: displayIndex }, captionLayout?.startsWith("dropdown") ? (React.createElement(components.DropdownNav, { className: classNames[UI.Dropdowns], style: styles?.[UI.Dropdowns] },
153 captionLayout === "dropdown" ||
154 captionLayout === "dropdown-months" ? (React.createElement(components.Dropdown, { "aria-label": labelMonthDropdown(), classNames: classNames, components: components, disabled: Boolean(props.disableNavigation), onChange: handleMonthChange, options: dropdownMonths, style: styles?.[UI.Dropdown], value: calendarMonth.date.getMonth() })) : (React.createElement("span", { role: "status", "aria-live": "polite" }, formatMonthDropdown(calendarMonth.date.getMonth()))),
155 captionLayout === "dropdown" ||
156 captionLayout === "dropdown-years" ? (React.createElement(components.Dropdown, { "aria-label": labelYearDropdown(labelOptions), classNames: classNames, components: components, disabled: Boolean(props.disableNavigation), onChange: handleYearChange, options: dropdownYears, style: styles?.[UI.Dropdown], value: calendarMonth.date.getFullYear() })) : (React.createElement("span", { role: "status", "aria-live": "polite" }, formatYearDropdown(calendarMonth.date.getFullYear()))))) : (React.createElement(components.CaptionLabel, { className: classNames[UI.CaptionLabel], role: "status", "aria-live": "polite" }, formatCaption(calendarMonth.date, formatOptions, dateLib)))),
157 React.createElement(components.MonthGrid, { role: "grid", "aria-multiselectable": mode === "multiple" || mode === "range", "aria-label": labelGrid(calendarMonth.date, labelOptions, dateLib) ||
158 undefined, className: classNames[UI.MonthGrid], style: styles?.[UI.MonthGrid] },
159 !props.hideWeekdays && (React.createElement(components.Weekdays, { className: classNames[UI.Weekdays], role: "row", style: styles?.[UI.Weekdays] },
160 showWeekNumber && (React.createElement(components.WeekNumberHeader, { "aria-label": labelWeekNumberHeader(labelOptions), className: classNames[UI.WeekNumberHeader], role: "columnheader", style: styles?.[UI.WeekNumberHeader] }, formatWeekNumberHeader())),
161 weekdays.map((weekday, i) => (React.createElement(components.Weekday, { "aria-label": labelWeekday(weekday, labelOptions, dateLib), className: classNames[UI.Weekday], key: i, role: "columnheader", style: styles?.[UI.Weekday] }, formatWeekdayName(weekday, formatOptions, dateLib)))))),
162 React.createElement(components.Weeks, { className: classNames[UI.Weeks], role: "rowgroup", style: styles?.[UI.Weeks] }, calendarMonth.weeks.map((week, weekIndex) => {
163 return (React.createElement(components.Week, { className: classNames[UI.Week], key: week.weekNumber, role: "row", style: styles?.[UI.Week], week: week },
164 showWeekNumber && (React.createElement(components.WeekNumber, { week: week, role: "rowheader", style: styles?.[UI.WeekNumber], "aria-label": labelWeekNumber(week.weekNumber, {
165 locale
166 }), className: classNames[UI.WeekNumber] }, formatWeekNumber(week.weekNumber))),
167 week.days.map((day) => {
168 const { date } = day;
169 const modifiers = getModifiers(day);
170 modifiers[DayFlag.focused] =
171 !modifiers.hidden &&
172 Boolean(focused?.isEqualTo(day));
173 modifiers[SelectionState.selected] =
174 !modifiers.disabled &&
175 (isSelected?.(date) || modifiers.selected);
176 if (isDateRange(selectedValue)) {
177 // add range modifiers
178 const { from, to } = selectedValue;
179 modifiers[SelectionState.range_start] = Boolean(from && to && dateLib.isSameDay(date, from));
180 modifiers[SelectionState.range_end] = Boolean(from && to && dateLib.isSameDay(date, to));
181 modifiers[SelectionState.range_middle] =
182 rangeIncludesDate(selectedValue, date, true, dateLib);
183 }
184 const style = getStyleForModifiers(modifiers, styles, props.modifiersStyles);
185 const className = getClassNamesForModifiers(modifiers, classNames, props.modifiersClassNames);
186 const ariaLabel = !isInteractive
187 ? labelGridcell(date, modifiers, labelOptions, dateLib)
188 : undefined;
189 return (React.createElement(components.Day, { key: `${dateLib.format(date, "yyyy-MM-dd")}_${dateLib.format(day.displayMonth, "yyyy-MM")}`, day: day, modifiers: modifiers, role: "gridcell", className: className.join(" "), style: style, "aria-hidden": modifiers.hidden || undefined, "aria-selected": modifiers.selected || undefined, "aria-label": ariaLabel, "data-day": dateLib.format(date, "yyyy-MM-dd"), "data-month": day.outside
190 ? dateLib.format(date, "yyyy-MM")
191 : undefined, "data-selected": modifiers.selected || undefined, "data-disabled": modifiers.disabled || undefined, "data-hidden": modifiers.hidden || undefined, "data-outside": day.outside || undefined, "data-focused": modifiers.focused || undefined, "data-today": modifiers.today || undefined }, isInteractive ? (React.createElement(components.DayButton, { className: classNames[UI.DayButton], style: styles?.[UI.DayButton], day: day, modifiers: modifiers, disabled: modifiers.disabled || undefined, tabIndex: isFocusTarget(day) ? 0 : -1, "aria-label": labelDayButton(date, modifiers, labelOptions, dateLib), onClick: handleDayClick(day, modifiers), onBlur: handleDayBlur(day, modifiers), onFocus: handleDayFocus(day, modifiers), onKeyDown: handleDayKeyDown(day, modifiers), onMouseEnter: handleDayMouseEnter(day, modifiers), onMouseLeave: handleDayMouseLeave(day, modifiers) }, formatDay(date, formatOptions, dateLib))) : (formatDay(day.date, formatOptions, dateLib))));
192 })));
193 })))));
194 })),
195 props.footer && (React.createElement(components.Footer, { className: classNames[UI.Footer], style: styles?.[UI.Footer], role: "status", "aria-live": "polite" }, props.footer)))));
196}
197//# sourceMappingURL=DayPicker.js.map
\No newline at end of file