@nuvui/date-picker 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +36 -2
  3. package/dist/components/calendar/calendar.cjs +163 -0
  4. package/dist/components/calendar/calendar.d.cts +17 -0
  5. package/dist/components/calendar/calendar.d.ts +17 -0
  6. package/dist/components/calendar/calendar.js +163 -0
  7. package/dist/components/calendar/index.cjs +3 -0
  8. package/dist/components/calendar/index.d.cts +2 -0
  9. package/dist/components/calendar/index.d.ts +2 -0
  10. package/dist/components/calendar/index.js +2 -0
  11. package/dist/components/date-picker/date-picker.cjs +340 -0
  12. package/dist/components/date-picker/date-picker.d.cts +169 -0
  13. package/dist/components/date-picker/date-picker.d.ts +169 -0
  14. package/dist/components/date-picker/date-picker.js +339 -0
  15. package/dist/components/date-picker/index.cjs +4 -0
  16. package/dist/components/date-picker/index.d.cts +2 -0
  17. package/dist/components/date-picker/index.d.ts +2 -0
  18. package/dist/components/date-picker/index.js +2 -0
  19. package/dist/components/date-picker/popup.cjs +43 -0
  20. package/dist/components/date-picker/popup.js +43 -0
  21. package/dist/css/calendar.css +291 -0
  22. package/dist/css/date-picker.css +24 -0
  23. package/dist/index.cjs +13 -0
  24. package/dist/index.d.cts +6 -0
  25. package/dist/index.d.ts +6 -0
  26. package/dist/index.js +4 -0
  27. package/dist/locale.cjs +9 -0
  28. package/dist/locale.d.cts +1 -0
  29. package/dist/locale.d.ts +1 -0
  30. package/dist/locale.js +1 -0
  31. package/dist/styles.css +314 -0
  32. package/dist/utils/cx.cjs +6 -0
  33. package/dist/utils/cx.js +6 -0
  34. package/dist/utils/dates.cjs +59 -0
  35. package/dist/utils/dates.js +54 -0
  36. package/dist/utils/use-controlled.cjs +31 -0
  37. package/dist/utils/use-controlled.js +31 -0
  38. package/dist/utils/use-date-text.cjs +83 -0
  39. package/dist/utils/use-date-text.js +83 -0
  40. package/dist/utils/use-is-phone.cjs +18 -0
  41. package/dist/utils/use-is-phone.js +18 -0
  42. package/package.json +118 -3
@@ -0,0 +1,339 @@
1
+ "use client";
2
+ import { cx } from "../../utils/cx.js";
3
+ import { Calendar } from "../calendar/calendar.js";
4
+ import { disabledDays, formatDate, isDisabled, isoDate, shortPattern } from "../../utils/dates.js";
5
+ import { useControlled } from "../../utils/use-controlled.js";
6
+ import { useDateText } from "../../utils/use-date-text.js";
7
+ import { useIsPhone } from "../../utils/use-is-phone.js";
8
+ import { Popup } from "./popup.js";
9
+ import { forwardRef, useCallback, useEffect, useMemo, useRef } from "react";
10
+ import { jsx, jsxs } from "react/jsx-runtime";
11
+ import { Input } from "@nuvui/react/input";
12
+ import { InputGroup, InputGroupButton } from "@nuvui/react/input-group";
13
+ import { Popover, PopoverAnchor, PopoverTrigger } from "@nuvui/react/popover";
14
+ //#region src/components/date-picker/date-picker.tsx
15
+ function CalendarIcon() {
16
+ return /* @__PURE__ */ jsxs("svg", {
17
+ "aria-hidden": "true",
18
+ viewBox: "0 0 16 16",
19
+ width: "16",
20
+ height: "16",
21
+ fill: "none",
22
+ stroke: "currentColor",
23
+ strokeWidth: "1.5",
24
+ strokeLinecap: "round",
25
+ strokeLinejoin: "round",
26
+ children: [/* @__PURE__ */ jsx("rect", {
27
+ x: "2.5",
28
+ y: "3.5",
29
+ width: "11",
30
+ height: "10",
31
+ rx: "1.5"
32
+ }), /* @__PURE__ */ jsx("path", { d: "M2.5 6.5h11M5.5 2v3m5-3v3" })]
33
+ });
34
+ }
35
+ function hint(format, locale) {
36
+ return (format === "P" ? shortPattern(locale) : format).toLowerCase();
37
+ }
38
+ function useFormReset(input, onReset) {
39
+ const latest = useRef(onReset);
40
+ latest.current = onReset;
41
+ useEffect(() => {
42
+ const form = input.current?.form;
43
+ if (!form) return;
44
+ const reset = () => latest.current();
45
+ form.addEventListener("reset", reset);
46
+ return () => form.removeEventListener("reset", reset);
47
+ }, [input]);
48
+ }
49
+ function useMergedRef(outer, inner) {
50
+ return useCallback((node) => {
51
+ for (const ref of [outer, inner]) if (typeof ref === "function") ref(node);
52
+ else if (ref) ref.current = node;
53
+ }, [outer, inner]);
54
+ }
55
+ function openWithAltDown(event, open) {
56
+ if (event.key === "ArrowDown" && event.altKey) {
57
+ event.preventDefault();
58
+ open();
59
+ }
60
+ }
61
+ const DatePicker = forwardRef(function DatePicker({ value: valueProp, defaultValue = null, onValueChange, open: openProp, defaultOpen = false, onOpenChange, min, max, disabledDates, locale, weekStartsOn, timeZone, format = "P", invalidMessage = "Enter a valid date.", calendarLabel = "Choose date", title = "Choose date", closeLabel = "Close", calendar, name, form, container, className, style, disabled, readOnly, placeholder, onBlur, onKeyDown, "aria-invalid": ariaInvalid, ...props }, ref) {
62
+ const [value, setValue] = useControlled(valueProp !== void 0, valueProp ?? null, defaultValue, onValueChange);
63
+ const [open, setOpen] = useControlled(openProp !== void 0, openProp ?? false, defaultOpen, onOpenChange);
64
+ const phone = useIsPhone();
65
+ const input = useRef(null);
66
+ const trigger = useRef(null);
67
+ const matchers = useMemo(() => disabledDays(min, max, disabledDates, timeZone), [
68
+ min,
69
+ max,
70
+ disabledDates,
71
+ timeZone
72
+ ]);
73
+ const field = useDateText({
74
+ value,
75
+ onCommit: setValue,
76
+ problem: (date) => isDisabled(date, matchers) ? invalidMessage : void 0,
77
+ invalidMessage,
78
+ pattern: format,
79
+ locale,
80
+ timeZone,
81
+ input
82
+ });
83
+ useFormReset(input, () => {
84
+ field.clearDraft();
85
+ setValue(defaultValue);
86
+ });
87
+ return /* @__PURE__ */ jsxs(Popover, {
88
+ open: open && !phone,
89
+ onOpenChange: setOpen,
90
+ children: [/* @__PURE__ */ jsx(PopoverAnchor, {
91
+ asChild: true,
92
+ children: /* @__PURE__ */ jsxs(InputGroup, {
93
+ className: cx("nuv-date-picker", className),
94
+ style,
95
+ children: [
96
+ /* @__PURE__ */ jsx(Input, {
97
+ ref: useMergedRef(ref, input),
98
+ autoComplete: "off",
99
+ placeholder: placeholder ?? hint(format, locale),
100
+ disabled,
101
+ readOnly,
102
+ "aria-invalid": field.error ? true : ariaInvalid,
103
+ ...props,
104
+ value: field.text,
105
+ onChange: field.onChange,
106
+ onBlur: (event) => {
107
+ field.onBlur();
108
+ onBlur?.(event);
109
+ },
110
+ onKeyDown: (event) => {
111
+ onKeyDown?.(event);
112
+ if (event.defaultPrevented) return;
113
+ field.onKeyDown(event);
114
+ if (!disabled && !readOnly) openWithAltDown(event, () => setOpen(true));
115
+ }
116
+ }),
117
+ /* @__PURE__ */ jsx(PopoverTrigger, {
118
+ asChild: true,
119
+ children: /* @__PURE__ */ jsx(InputGroupButton, {
120
+ ref: trigger,
121
+ className: "nuv-date-picker__button",
122
+ "aria-label": value ? `${calendarLabel}, ${formatDate(value, "PPPP", locale, timeZone)}` : calendarLabel,
123
+ disabled: disabled || readOnly,
124
+ children: /* @__PURE__ */ jsx(CalendarIcon, {})
125
+ })
126
+ }),
127
+ name ? /* @__PURE__ */ jsx("input", {
128
+ type: "hidden",
129
+ name,
130
+ form,
131
+ value: isoDate(value ?? void 0, timeZone),
132
+ disabled
133
+ }) : null
134
+ ]
135
+ })
136
+ }), /* @__PURE__ */ jsx(Popup, {
137
+ open,
138
+ onOpenChange: setOpen,
139
+ phone,
140
+ title,
141
+ closeLabel,
142
+ container,
143
+ trigger,
144
+ children: /* @__PURE__ */ jsx(Calendar, {
145
+ ...calendar,
146
+ mode: "single",
147
+ required: true,
148
+ selected: value ?? void 0,
149
+ onSelect: (date) => {
150
+ field.clearDraft();
151
+ setValue(date);
152
+ setOpen(false);
153
+ },
154
+ defaultMonth: value ?? calendar?.defaultMonth,
155
+ startMonth: min,
156
+ endMonth: max,
157
+ disabled: matchers,
158
+ locale,
159
+ weekStartsOn,
160
+ timeZone
161
+ })
162
+ })]
163
+ });
164
+ });
165
+ function before(a, b, timeZone) {
166
+ return isoDate(a, timeZone) < isoDate(b, timeZone);
167
+ }
168
+ const DateRangePicker = forwardRef(function DateRangePicker({ value: valueProp, defaultValue = null, onValueChange, open: openProp, defaultOpen = false, onOpenChange, min, max, disabledDates, locale, weekStartsOn, timeZone, format = "P", invalidMessage = "Enter a valid date.", orderMessage = "The end date is before the start date.", calendarLabel = "Choose dates", title = "Choose dates", closeLabel = "Close", startLabel = "Start date", endLabel = "End date", numberOfMonths = 2, calendar, startName, endName, form, container, className, style, disabled, readOnly, required, placeholder, id, onBlur, onKeyDown, "aria-invalid": ariaInvalid, "aria-labelledby": ariaLabelledBy, "aria-label": ariaLabel, "aria-describedby": ariaDescribedBy, ...props }, ref) {
169
+ const [value, setValue] = useControlled(valueProp !== void 0, valueProp ?? null, defaultValue, onValueChange);
170
+ const [open, setOpen] = useControlled(openProp !== void 0, openProp ?? false, defaultOpen, onOpenChange);
171
+ const phone = useIsPhone();
172
+ const startInput = useRef(null);
173
+ const endInput = useRef(null);
174
+ const trigger = useRef(null);
175
+ const from = value?.from ?? null;
176
+ const to = value?.to ?? null;
177
+ const matchers = useMemo(() => disabledDays(min, max, disabledDates, timeZone), [
178
+ min,
179
+ max,
180
+ disabledDates,
181
+ timeZone
182
+ ]);
183
+ function setRange(nextFrom, nextTo) {
184
+ setValue(nextFrom || nextTo ? {
185
+ from: nextFrom ?? void 0,
186
+ to: nextTo ?? void 0
187
+ } : null);
188
+ }
189
+ const start = useDateText({
190
+ value: from,
191
+ onCommit: (date) => setRange(date, to),
192
+ problem: (date) => {
193
+ if (isDisabled(date, matchers)) return invalidMessage;
194
+ return to && before(to, date, timeZone) ? orderMessage : void 0;
195
+ },
196
+ invalidMessage,
197
+ pattern: format,
198
+ locale,
199
+ timeZone,
200
+ input: startInput
201
+ });
202
+ const end = useDateText({
203
+ value: to,
204
+ onCommit: (date) => setRange(from, date),
205
+ problem: (date) => {
206
+ if (isDisabled(date, matchers)) return invalidMessage;
207
+ return from && before(date, from, timeZone) ? orderMessage : void 0;
208
+ },
209
+ invalidMessage,
210
+ pattern: format,
211
+ locale,
212
+ timeZone,
213
+ input: endInput
214
+ });
215
+ useFormReset(startInput, () => {
216
+ start.clearDraft();
217
+ end.clearDraft();
218
+ setValue(defaultValue);
219
+ });
220
+ const shared = {
221
+ autoComplete: "off",
222
+ placeholder: placeholder ?? hint(format, locale),
223
+ disabled,
224
+ readOnly,
225
+ required,
226
+ "aria-describedby": ariaDescribedBy,
227
+ ...props
228
+ };
229
+ function keyDown(event, field) {
230
+ onKeyDown?.(event);
231
+ if (event.defaultPrevented) return;
232
+ field.onKeyDown(event);
233
+ if (!disabled && !readOnly) openWithAltDown(event, () => setOpen(true));
234
+ }
235
+ const picked = [from, to].filter((date) => date !== null).map((date) => formatDate(date, "PPPP", locale, timeZone)).join(", ");
236
+ return /* @__PURE__ */ jsxs(Popover, {
237
+ open: open && !phone,
238
+ onOpenChange: setOpen,
239
+ children: [/* @__PURE__ */ jsx(PopoverAnchor, {
240
+ asChild: true,
241
+ children: /* @__PURE__ */ jsxs(InputGroup, {
242
+ role: "group",
243
+ "aria-labelledby": ariaLabelledBy,
244
+ "aria-label": ariaLabel,
245
+ className: cx("nuv-date-picker nuv-date-picker--range", className),
246
+ style,
247
+ children: [
248
+ /* @__PURE__ */ jsx(Input, {
249
+ ref: useMergedRef(ref, startInput),
250
+ id,
251
+ "aria-label": startLabel,
252
+ "aria-invalid": start.error ? true : ariaInvalid,
253
+ ...shared,
254
+ value: start.text,
255
+ onChange: start.onChange,
256
+ onBlur: (event) => {
257
+ start.onBlur();
258
+ onBlur?.(event);
259
+ },
260
+ onKeyDown: (event) => keyDown(event, start)
261
+ }),
262
+ /* @__PURE__ */ jsx("span", {
263
+ "aria-hidden": "true",
264
+ className: "nuv-date-picker__separator",
265
+ children: "–"
266
+ }),
267
+ /* @__PURE__ */ jsx(Input, {
268
+ ref: endInput,
269
+ "aria-label": endLabel,
270
+ "aria-invalid": end.error ? true : ariaInvalid,
271
+ ...shared,
272
+ value: end.text,
273
+ onChange: end.onChange,
274
+ onBlur: (event) => {
275
+ end.onBlur();
276
+ onBlur?.(event);
277
+ },
278
+ onKeyDown: (event) => keyDown(event, end)
279
+ }),
280
+ /* @__PURE__ */ jsx(PopoverTrigger, {
281
+ asChild: true,
282
+ children: /* @__PURE__ */ jsx(InputGroupButton, {
283
+ ref: trigger,
284
+ className: "nuv-date-picker__button",
285
+ "aria-label": picked ? `${calendarLabel}, ${picked}` : calendarLabel,
286
+ disabled: disabled || readOnly,
287
+ children: /* @__PURE__ */ jsx(CalendarIcon, {})
288
+ })
289
+ }),
290
+ startName ? /* @__PURE__ */ jsx("input", {
291
+ type: "hidden",
292
+ name: startName,
293
+ form,
294
+ value: isoDate(from ?? void 0, timeZone),
295
+ disabled
296
+ }) : null,
297
+ endName ? /* @__PURE__ */ jsx("input", {
298
+ type: "hidden",
299
+ name: endName,
300
+ form,
301
+ value: isoDate(to ?? void 0, timeZone),
302
+ disabled
303
+ }) : null
304
+ ]
305
+ })
306
+ }), /* @__PURE__ */ jsx(Popup, {
307
+ open,
308
+ onOpenChange: setOpen,
309
+ phone,
310
+ title,
311
+ closeLabel,
312
+ container,
313
+ trigger,
314
+ children: /* @__PURE__ */ jsx(Calendar, {
315
+ numberOfMonths: phone ? 1 : numberOfMonths,
316
+ ...calendar,
317
+ mode: "range",
318
+ required: true,
319
+ resetOnSelect: true,
320
+ selected: value ?? void 0,
321
+ onSelect: (range) => {
322
+ start.clearDraft();
323
+ end.clearDraft();
324
+ setValue(range);
325
+ if (range.from && range.to) setOpen(false);
326
+ },
327
+ defaultMonth: from ?? calendar?.defaultMonth,
328
+ startMonth: min,
329
+ endMonth: max,
330
+ disabled: matchers,
331
+ locale,
332
+ weekStartsOn,
333
+ timeZone
334
+ })
335
+ })]
336
+ });
337
+ });
338
+ //#endregion
339
+ export { DatePicker, DateRangePicker };
@@ -0,0 +1,4 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_date_picker = require("./date-picker.cjs");
3
+ exports.DatePicker = require_date_picker.DatePicker;
4
+ exports.DateRangePicker = require_date_picker.DateRangePicker;
@@ -0,0 +1,2 @@
1
+ import { DatePicker, DatePickerCalendarProps, DatePickerOwnProps, DatePickerProps, DateRangePicker, DateRangePickerCalendarProps, DateRangePickerOwnProps, DateRangePickerProps } from "./date-picker.cjs";
2
+ export { DatePicker, type DatePickerCalendarProps, type DatePickerOwnProps, type DatePickerProps, DateRangePicker, type DateRangePickerCalendarProps, type DateRangePickerOwnProps, type DateRangePickerProps };
@@ -0,0 +1,2 @@
1
+ import { DatePicker, DatePickerCalendarProps, DatePickerOwnProps, DatePickerProps, DateRangePicker, DateRangePickerCalendarProps, DateRangePickerOwnProps, DateRangePickerProps } from "./date-picker.js";
2
+ export { DatePicker, type DatePickerCalendarProps, type DatePickerOwnProps, type DatePickerProps, DateRangePicker, type DateRangePickerCalendarProps, type DateRangePickerOwnProps, type DateRangePickerProps };
@@ -0,0 +1,2 @@
1
+ import { DatePicker, DateRangePicker } from "./date-picker.js";
2
+ export { DatePicker, DateRangePicker };
@@ -0,0 +1,43 @@
1
+ "use client";
2
+ let react_jsx_runtime = require("react/jsx-runtime");
3
+ let _nuvui_react_popover = require("@nuvui/react/popover");
4
+ let _nuvui_react_dialog = require("@nuvui/react/dialog");
5
+ //#region src/components/date-picker/popup.tsx
6
+ function focusDay(event) {
7
+ event.preventDefault();
8
+ const popup = event.currentTarget;
9
+ (popup.querySelector("[role=\"gridcell\"] button[tabindex=\"0\"]") ?? popup).focus();
10
+ }
11
+ /**
12
+ * What the calendar opens in. The popover half is rendered inside the
13
+ * `Popover` that DatePicker wraps its field in. The sheet is a dialog of its
14
+ * own, with no trigger part, so it's told where to send focus back to.
15
+ */
16
+ function Popup({ open, onOpenChange, phone, title, closeLabel, container, trigger, children }) {
17
+ if (phone) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nuvui_react_dialog.Dialog, {
18
+ open,
19
+ onOpenChange,
20
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_nuvui_react_dialog.DialogContent, {
21
+ className: "nuv-date-picker__sheet",
22
+ closeLabel,
23
+ container,
24
+ "aria-describedby": void 0,
25
+ onOpenAutoFocus: focusDay,
26
+ onCloseAutoFocus: (event) => {
27
+ event.preventDefault();
28
+ trigger.current?.focus();
29
+ },
30
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nuvui_react_dialog.DialogTitle, { children: title }), children]
31
+ })
32
+ });
33
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nuvui_react_popover.PopoverContent, {
34
+ className: "nuv-date-picker__popover",
35
+ "aria-label": title,
36
+ align: "start",
37
+ container,
38
+ onOpenAutoFocus: focusDay,
39
+ children
40
+ });
41
+ }
42
+ //#endregion
43
+ exports.Popup = Popup;
@@ -0,0 +1,43 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { PopoverContent } from "@nuvui/react/popover";
4
+ import { Dialog, DialogContent, DialogTitle } from "@nuvui/react/dialog";
5
+ //#region src/components/date-picker/popup.tsx
6
+ function focusDay(event) {
7
+ event.preventDefault();
8
+ const popup = event.currentTarget;
9
+ (popup.querySelector("[role=\"gridcell\"] button[tabindex=\"0\"]") ?? popup).focus();
10
+ }
11
+ /**
12
+ * What the calendar opens in. The popover half is rendered inside the
13
+ * `Popover` that DatePicker wraps its field in. The sheet is a dialog of its
14
+ * own, with no trigger part, so it's told where to send focus back to.
15
+ */
16
+ function Popup({ open, onOpenChange, phone, title, closeLabel, container, trigger, children }) {
17
+ if (phone) return /* @__PURE__ */ jsx(Dialog, {
18
+ open,
19
+ onOpenChange,
20
+ children: /* @__PURE__ */ jsxs(DialogContent, {
21
+ className: "nuv-date-picker__sheet",
22
+ closeLabel,
23
+ container,
24
+ "aria-describedby": void 0,
25
+ onOpenAutoFocus: focusDay,
26
+ onCloseAutoFocus: (event) => {
27
+ event.preventDefault();
28
+ trigger.current?.focus();
29
+ },
30
+ children: [/* @__PURE__ */ jsx(DialogTitle, { children: title }), children]
31
+ })
32
+ });
33
+ return /* @__PURE__ */ jsx(PopoverContent, {
34
+ className: "nuv-date-picker__popover",
35
+ "aria-label": title,
36
+ align: "start",
37
+ container,
38
+ onOpenAutoFocus: focusDay,
39
+ children
40
+ });
41
+ }
42
+ //#endregion
43
+ export { Popup };