gbs-add-block 1.2.14 → 1.2.16

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 (169) hide show
  1. package/README.md +2 -2
  2. package/index.cjs +219 -21
  3. package/package.json +1 -1
  4. package/source/beta-components/breadcrumb/README.md +34 -0
  5. package/source/beta-components/breadcrumb/__tests__/core.test.ts +58 -0
  6. package/source/beta-components/breadcrumb/core/index.ts +4 -0
  7. package/source/beta-components/breadcrumb/core/trail.ts +42 -0
  8. package/source/beta-components/breadcrumb/core/types.ts +20 -0
  9. package/source/beta-components/breadcrumb/index.ts +5 -0
  10. package/source/beta-components/breadcrumb/react/Breadcrumb.tsx +177 -0
  11. package/source/beta-components/breadcrumb/react/locale.ts +6 -0
  12. package/source/beta-components/breadcrumb/react/props.ts +14 -0
  13. package/source/beta-components/breadcrumb/styles.css +139 -0
  14. package/source/beta-components/button/README.md +32 -0
  15. package/source/beta-components/button/__tests__/core.test.ts +31 -0
  16. package/source/beta-components/button/core/index.ts +3 -0
  17. package/source/beta-components/button/core/state.ts +16 -0
  18. package/source/beta-components/button/core/types.ts +28 -0
  19. package/source/beta-components/button/index.ts +5 -0
  20. package/source/beta-components/button/react/Button.tsx +173 -0
  21. package/source/beta-components/button/react/locale.ts +5 -0
  22. package/source/beta-components/button/react/props.ts +23 -0
  23. package/source/beta-components/button/styles.css +233 -0
  24. package/source/beta-components/checkbox/README.md +36 -0
  25. package/source/beta-components/checkbox/__tests__/core.test.ts +40 -0
  26. package/source/beta-components/checkbox/core/group.ts +30 -0
  27. package/source/beta-components/checkbox/core/index.ts +3 -0
  28. package/source/beta-components/checkbox/core/types.ts +17 -0
  29. package/source/beta-components/checkbox/index.ts +9 -0
  30. package/source/beta-components/checkbox/react/Checkbox.tsx +147 -0
  31. package/source/beta-components/checkbox/react/CheckboxGroup.tsx +149 -0
  32. package/source/beta-components/checkbox/react/context.ts +16 -0
  33. package/source/beta-components/checkbox/react/locale.ts +5 -0
  34. package/source/beta-components/checkbox/react/props.ts +6 -0
  35. package/source/beta-components/checkbox/styles.css +212 -0
  36. package/source/beta-components/combobox/README.md +3 -3
  37. package/source/beta-components/combobox/react/MultiSelect.tsx +265 -263
  38. package/source/beta-components/combobox/react/Popover.tsx +38 -70
  39. package/source/beta-components/combobox/react/Select.tsx +200 -198
  40. package/source/beta-components/combobox/react/icons.tsx +6 -29
  41. package/source/beta-components/combobox/react/props.ts +4 -2
  42. package/source/beta-components/combobox/styles.css +18 -18
  43. package/source/beta-components/data-grid/react/Popover.tsx +35 -0
  44. package/source/beta-components/{datagrid → data-grid}/react/context.ts +2 -2
  45. package/source/beta-components/{datagrid → data-grid}/react/icons.tsx +8 -35
  46. package/source/beta-components/{datagrid → data-grid}/styles.css +24 -24
  47. package/source/beta-components/{datepicker → date-picker}/README.md +3 -3
  48. package/source/beta-components/{datepicker → date-picker}/react/DatePicker.tsx +249 -249
  49. package/source/beta-components/{datepicker → date-picker}/react/DateRangePicker.tsx +291 -291
  50. package/source/beta-components/date-picker/react/Popover.tsx +45 -0
  51. package/source/beta-components/date-picker/react/icons.tsx +15 -0
  52. package/source/beta-components/{datepicker → date-picker}/react/props.ts +2 -2
  53. package/source/beta-components/{datepicker → date-picker}/styles.css +18 -18
  54. package/source/beta-components/dialog/react/icons.tsx +16 -47
  55. package/source/beta-components/dialog/react/props.ts +2 -2
  56. package/source/beta-components/dialog/styles.css +21 -21
  57. package/source/beta-components/{fileuploader → file-uploader}/react/FileUploader.tsx +511 -515
  58. package/source/beta-components/{fileuploader → file-uploader}/react/icons.tsx +7 -27
  59. package/source/beta-components/{fileuploader → file-uploader}/react/props.ts +2 -2
  60. package/source/beta-components/{fileuploader → file-uploader}/styles.css +17 -17
  61. package/source/beta-components/input/core/count.ts +3 -12
  62. package/source/beta-components/input/react/Input.tsx +219 -219
  63. package/source/beta-components/input/react/OtpInput.tsx +256 -256
  64. package/source/beta-components/input/react/icons.tsx +3 -21
  65. package/source/beta-components/input/react/props.ts +2 -2
  66. package/source/beta-components/input/styles.css +15 -15
  67. package/source/beta-components/modal/react/Modal.tsx +242 -238
  68. package/source/beta-components/modal/react/icons.tsx +1 -19
  69. package/source/beta-components/modal/react/props.ts +2 -2
  70. package/source/beta-components/modal/styles.css +17 -13
  71. package/source/beta-components/shared/README.md +54 -0
  72. package/source/beta-components/shared/__tests__/boundaries.test.ts +95 -0
  73. package/source/beta-components/shared/__tests__/core.test.ts +55 -0
  74. package/source/beta-components/shared/core/cx.ts +9 -0
  75. package/source/beta-components/shared/core/field.ts +35 -0
  76. package/source/beta-components/shared/core/text.ts +14 -0
  77. package/source/beta-components/shared/index.ts +7 -0
  78. package/source/beta-components/shared/react/Popover.tsx +147 -0
  79. package/source/beta-components/shared/react/icons.tsx +74 -0
  80. package/source/beta-components/shared/react/useControllableState.ts +42 -0
  81. package/source/beta-components/shared/version.json +4 -0
  82. package/source/beta-components/shared/version.ts +6 -0
  83. package/source/beta-components/spinner/README.md +29 -0
  84. package/source/beta-components/spinner/__tests__/core.test.ts +48 -0
  85. package/source/beta-components/spinner/core/index.ts +3 -0
  86. package/source/beta-components/spinner/core/types.ts +28 -0
  87. package/source/beta-components/spinner/core/visibility.ts +34 -0
  88. package/source/beta-components/spinner/index.ts +6 -0
  89. package/source/beta-components/spinner/react/Spinner.tsx +96 -0
  90. package/source/beta-components/spinner/react/locale.ts +5 -0
  91. package/source/beta-components/spinner/react/props.ts +4 -0
  92. package/source/beta-components/spinner/react/useDelayedLoading.ts +39 -0
  93. package/source/beta-components/spinner/styles.css +167 -0
  94. package/source/beta-components/tabs/README.md +32 -0
  95. package/source/beta-components/tabs/__tests__/core.test.ts +51 -0
  96. package/source/beta-components/tabs/core/index.ts +3 -0
  97. package/source/beta-components/tabs/core/navigation.ts +54 -0
  98. package/source/beta-components/tabs/core/types.ts +16 -0
  99. package/source/beta-components/tabs/index.ts +5 -0
  100. package/source/beta-components/tabs/react/Tabs.tsx +258 -0
  101. package/source/beta-components/tabs/react/context.ts +21 -0
  102. package/source/beta-components/tabs/styles.css +257 -0
  103. package/source/beta-components/textarea/core/count.ts +3 -12
  104. package/source/beta-components/textarea/react/Textarea.tsx +208 -209
  105. package/source/beta-components/textarea/react/props.ts +2 -2
  106. package/source/beta-components/textarea/styles.css +12 -12
  107. package/source/beta-components/toaster/react/icons.tsx +19 -52
  108. package/source/beta-components/toaster/react/props.ts +2 -2
  109. package/source/beta-components/toaster/styles.css +18 -18
  110. package/source/beta-components/datagrid/react/Popover.tsx +0 -79
  111. package/source/beta-components/datepicker/react/Popover.tsx +0 -87
  112. package/source/beta-components/datepicker/react/icons.tsx +0 -32
  113. /package/source/beta-components/{datagrid → data-grid}/README.md +0 -0
  114. /package/source/beta-components/{datagrid → data-grid}/__tests__/core.test.ts +0 -0
  115. /package/source/beta-components/{datagrid → data-grid}/__tests__/export.test.ts +0 -0
  116. /package/source/beta-components/{datagrid → data-grid}/core/columnHelper.ts +0 -0
  117. /package/source/beta-components/{datagrid → data-grid}/core/columns.ts +0 -0
  118. /package/source/beta-components/{datagrid → data-grid}/core/filtering.ts +0 -0
  119. /package/source/beta-components/{datagrid → data-grid}/core/grid.ts +0 -0
  120. /package/source/beta-components/{datagrid → data-grid}/core/index.ts +0 -0
  121. /package/source/beta-components/{datagrid → data-grid}/core/rows.ts +0 -0
  122. /package/source/beta-components/{datagrid → data-grid}/core/sorting.ts +0 -0
  123. /package/source/beta-components/{datagrid → data-grid}/core/state.ts +0 -0
  124. /package/source/beta-components/{datagrid → data-grid}/core/store.ts +0 -0
  125. /package/source/beta-components/{datagrid → data-grid}/core/types.ts +0 -0
  126. /package/source/beta-components/{datagrid → data-grid}/core/values.ts +0 -0
  127. /package/source/beta-components/{datagrid → data-grid}/core/virtual.ts +0 -0
  128. /package/source/beta-components/{datagrid → data-grid}/export/csv.ts +0 -0
  129. /package/source/beta-components/{datagrid → data-grid}/export/download.ts +0 -0
  130. /package/source/beta-components/{datagrid → data-grid}/export/pdf.ts +0 -0
  131. /package/source/beta-components/{datagrid → data-grid}/export/table.ts +0 -0
  132. /package/source/beta-components/{datagrid → data-grid}/export/xlsx.ts +0 -0
  133. /package/source/beta-components/{datagrid → data-grid}/export/zip.ts +0 -0
  134. /package/source/beta-components/{datagrid → data-grid}/index.ts +0 -0
  135. /package/source/beta-components/{datagrid → data-grid}/react/Cell.tsx +0 -0
  136. /package/source/beta-components/{datagrid → data-grid}/react/ColumnMenu.tsx +0 -0
  137. /package/source/beta-components/{datagrid → data-grid}/react/DataGrid.tsx +0 -0
  138. /package/source/beta-components/{datagrid → data-grid}/react/FilterForm.tsx +0 -0
  139. /package/source/beta-components/{datagrid → data-grid}/react/HeaderRow.tsx +0 -0
  140. /package/source/beta-components/{datagrid → data-grid}/react/Pagination.tsx +0 -0
  141. /package/source/beta-components/{datagrid → data-grid}/react/Row.tsx +0 -0
  142. /package/source/beta-components/{datagrid → data-grid}/react/Toolbar.tsx +0 -0
  143. /package/source/beta-components/{datagrid → data-grid}/react/Viewport.tsx +0 -0
  144. /package/source/beta-components/{datagrid → data-grid}/react/hooks.ts +0 -0
  145. /package/source/beta-components/{datagrid → data-grid}/react/keyboard.ts +0 -0
  146. /package/source/beta-components/{datagrid → data-grid}/react/locale.ts +0 -0
  147. /package/source/beta-components/{datepicker → date-picker}/__tests__/core.test.ts +0 -0
  148. /package/source/beta-components/{datepicker → date-picker}/core/calendar.ts +0 -0
  149. /package/source/beta-components/{datepicker → date-picker}/core/format.ts +0 -0
  150. /package/source/beta-components/{datepicker → date-picker}/core/index.ts +0 -0
  151. /package/source/beta-components/{datepicker → date-picker}/core/types.ts +0 -0
  152. /package/source/beta-components/{datepicker → date-picker}/index.ts +0 -0
  153. /package/source/beta-components/{datepicker → date-picker}/react/Calendar.tsx +0 -0
  154. /package/source/beta-components/{datepicker → date-picker}/react/CalendarPanel.tsx +0 -0
  155. /package/source/beta-components/{datepicker → date-picker}/react/MonthYearPanels.tsx +0 -0
  156. /package/source/beta-components/{datepicker → date-picker}/react/locale.ts +0 -0
  157. /package/source/beta-components/{datepicker → date-picker}/react/useDatePicker.ts +0 -0
  158. /package/source/beta-components/{fileuploader → file-uploader}/README.md +0 -0
  159. /package/source/beta-components/{fileuploader → file-uploader}/__tests__/core.test.ts +0 -0
  160. /package/source/beta-components/{fileuploader → file-uploader}/core/format.ts +0 -0
  161. /package/source/beta-components/{fileuploader → file-uploader}/core/index.ts +0 -0
  162. /package/source/beta-components/{fileuploader → file-uploader}/core/store.ts +0 -0
  163. /package/source/beta-components/{fileuploader → file-uploader}/core/transport.ts +0 -0
  164. /package/source/beta-components/{fileuploader → file-uploader}/core/types.ts +0 -0
  165. /package/source/beta-components/{fileuploader → file-uploader}/core/validate.ts +0 -0
  166. /package/source/beta-components/{fileuploader → file-uploader}/index.ts +0 -0
  167. /package/source/beta-components/{fileuploader → file-uploader}/react/FileRow.tsx +0 -0
  168. /package/source/beta-components/{fileuploader → file-uploader}/react/locale.ts +0 -0
  169. /package/source/beta-components/{fileuploader → file-uploader}/react/useFileUploader.ts +0 -0
@@ -11,30 +11,30 @@
11
11
 
12
12
  @layer components {
13
13
  .dg-root {
14
- --dg-font-size: 13px;
15
- --dg-bg: light-dark(#ffffff, #0b0b0e);
16
- --dg-fg: light-dark(#18181b, #f4f4f5);
17
- --dg-muted: light-dark(#71717a, #a1a1aa);
18
- --dg-border: light-dark(#e4e4e7, #27272a);
19
- --dg-border-subtle: light-dark(#f0f0f2, #1c1c20);
20
- --dg-header-bg: light-dark(#fafafa, #111114);
21
- --dg-header-fg: light-dark(#3f3f46, #d4d4d8);
22
- --dg-row-alt: light-dark(#fcfcfd, #0e0e12);
23
- --dg-row-hover: light-dark(#f4f4f5, #18181c);
24
- --dg-row-selected: light-dark(#eef4ff, #14213d);
25
- --dg-row-selected-hover: light-dark(#e2ecff, #1a2a4d);
26
- --dg-hover: light-dark(#f4f4f5, #1f1f23);
27
- --dg-input-bg: light-dark(#ffffff, #121216);
28
- --dg-accent: light-dark(#2563eb, #60a5fa);
29
- --dg-accent-fg: light-dark(#ffffff, #0b1220);
30
- --dg-accent-soft: light-dark(#eff6ff, #172554);
31
- --dg-accent-strong: light-dark(#1d4ed8, #bfdbfe);
32
- --dg-focus: light-dark(#2563eb, #60a5fa);
33
- --dg-danger: light-dark(#dc2626, #f87171);
34
- --dg-pin-shadow: light-dark(rgb(0 0 0 / 0.08), rgb(0 0 0 / 0.5));
35
- --dg-shadow: 0 10px 30px -8px light-dark(rgb(0 0 0 / 0.18), rgb(0 0 0 / 0.6));
36
- --dg-radius: 8px;
37
- --dg-cell-px: 12px;
14
+ --dg-font-size: var(--gbs-font-size, 13px);
15
+ --dg-bg: var(--gbs-bg, light-dark(#ffffff, #0b0b0e));
16
+ --dg-fg: var(--gbs-fg, light-dark(#18181b, #f4f4f5));
17
+ --dg-muted: var(--gbs-muted, light-dark(#71717a, #a1a1aa));
18
+ --dg-border: var(--gbs-border, light-dark(#e4e4e7, #27272a));
19
+ --dg-border-subtle: var(--gbs-border-subtle, light-dark(#f0f0f2, #1c1c20));
20
+ --dg-header-bg: var(--gbs-header-bg, light-dark(#fafafa, #111114));
21
+ --dg-header-fg: var(--gbs-header-fg, light-dark(#3f3f46, #d4d4d8));
22
+ --dg-row-alt: var(--gbs-row-alt, light-dark(#fcfcfd, #0e0e12));
23
+ --dg-row-hover: var(--gbs-row-hover, light-dark(#f4f4f5, #18181c));
24
+ --dg-row-selected: var(--gbs-row-selected, light-dark(#eef4ff, #14213d));
25
+ --dg-row-selected-hover: var(--gbs-row-selected-hover, light-dark(#e2ecff, #1a2a4d));
26
+ --dg-hover: var(--gbs-hover, light-dark(#f4f4f5, #1f1f23));
27
+ --dg-input-bg: var(--gbs-input-bg, light-dark(#ffffff, #121216));
28
+ --dg-accent: var(--gbs-accent, light-dark(#2563eb, #60a5fa));
29
+ --dg-accent-fg: var(--gbs-accent-fg, light-dark(#ffffff, #0b1220));
30
+ --dg-accent-soft: var(--gbs-accent-soft, light-dark(#eff6ff, #172554));
31
+ --dg-accent-strong: var(--gbs-accent-strong, light-dark(#1d4ed8, #bfdbfe));
32
+ --dg-focus: var(--gbs-focus, light-dark(#2563eb, #60a5fa));
33
+ --dg-danger: var(--gbs-danger, light-dark(#dc2626, #f87171));
34
+ --dg-pin-shadow: var(--gbs-pin-shadow, light-dark(rgb(0 0 0 / 0.08), rgb(0 0 0 / 0.5)));
35
+ --dg-shadow: var(--gbs-shadow, 0 10px 30px -8px light-dark(rgb(0 0 0 / 0.18), rgb(0 0 0 / 0.6)));
36
+ --dg-radius: var(--gbs-radius, 8px);
37
+ --dg-cell-px: var(--gbs-cell-px, 12px);
38
38
 
39
39
  color-scheme: inherit;
40
40
  position: relative;
@@ -17,9 +17,9 @@ import { DatePicker, DateRangePicker } from "@/components/date-picker";
17
17
  import "@/components/date-picker/styles.css";
18
18
  ```
19
19
 
20
- The stylesheet reuses the DataGrid's `--dg-*` variables when that stylesheet is
21
- loaded, so every component shares a theme, and falls back to the same palette
22
- when used on its own.
20
+ The stylesheet reads the shared `--gbs-*` variables when you set them on
21
+ `:root`, so every component in the library shares one theme, and falls back to
22
+ the same default palette otherwise.
23
23
 
24
24
  ## Basic usage
25
25
 
@@ -1,249 +1,249 @@
1
- "use client";
2
-
3
- import {
4
- useId,
5
- useImperativeHandle,
6
- useMemo,
7
- useRef,
8
- useState,
9
- type KeyboardEvent,
10
- type Ref,
11
- } from "react";
12
- import { toDate, toISODate } from "../core/calendar";
13
- import { inputPlaceholder } from "../core/format";
14
- import type { DateInput, DateRange } from "../core/types";
15
- import { CalendarPanel } from "./CalendarPanel";
16
- import { CalendarIcon, XIcon } from "./icons";
17
- import { defaultDatePickerText } from "./locale";
18
- import { DatePickerPopover } from "./Popover";
19
- import { cx, type DatePickerHandle, type DatePickerSharedProps } from "./props";
20
- import { useDatePicker } from "./useDatePicker";
21
-
22
- export interface DatePickerProps extends DatePickerSharedProps {
23
- value?: DateInput | null;
24
- defaultValue?: DateInput | null;
25
- onChange?(date: Date | null): void;
26
- /** Close the calendar after choosing a date. Default true. */
27
- closeOnSelect?: boolean;
28
- ref?: Ref<DatePickerHandle<Date | null>>;
29
- }
30
-
31
- /** A single date field with a calendar, typed entry and full keyboard control. */
32
- export function DatePicker(props: DatePickerProps) {
33
- const {
34
- ref,
35
- value,
36
- defaultValue,
37
- onChange,
38
- closeOnSelect = true,
39
- min,
40
- max,
41
- isDateDisabled,
42
- locale,
43
- weekStartsOn,
44
- format,
45
- numberOfMonths = 1,
46
- showWeekNumbers = false,
47
- showToday = true,
48
- allowInput = true,
49
- fixedWeeks = true,
50
- label,
51
- description,
52
- error,
53
- placeholder,
54
- required = false,
55
- disabled = false,
56
- readOnly = false,
57
- clearable = true,
58
- size = "md",
59
- name,
60
- id: idProp,
61
- className,
62
- classNames,
63
- style,
64
- localeText,
65
- onOpenChange,
66
- } = props;
67
-
68
- const reactId = useId();
69
- const id = idProp ?? reactId;
70
- const inputId = `${id}-input`;
71
- const popoverId = `${id}-popover`;
72
- const text = useMemo(() => ({ ...defaultDatePickerText, ...localeText }), [localeText]);
73
-
74
- const controlRef = useRef<HTMLDivElement>(null);
75
- const triggerRef = useRef<HTMLButtonElement>(null);
76
- const inputRef = useRef<HTMLInputElement>(null);
77
-
78
- const minDate = useMemo(() => toDate(min), [min]);
79
- const maxDate = useMemo(() => toDate(max), [max]);
80
- const selection = useMemo<DateRange | undefined>(
81
- () => (value === undefined ? undefined : { start: toDate(value), end: null }),
82
- [value],
83
- );
84
- // Read once: later changes to `defaultValue` are ignored, as for any uncontrolled input.
85
- const [initial] = useState<DateRange>(() => ({ start: toDate(defaultValue), end: null }));
86
-
87
- const picker = useDatePicker({
88
- range: false,
89
- value: selection,
90
- defaultValue: initial,
91
- onSelectionChange: (next) => onChange?.(next.start),
92
- min: minDate,
93
- max: maxDate,
94
- isDateDisabled,
95
- locale,
96
- weekStartsOn,
97
- numberOfMonths,
98
- fixedWeeks,
99
- disabled,
100
- readOnly,
101
- closeOnSelect,
102
- format,
103
- text,
104
- controlRef,
105
- triggerRef,
106
- onOpenChange,
107
- });
108
-
109
- const selected = picker.selection.start;
110
- const message = error ?? picker.inputError;
111
- const invalid = Boolean(message);
112
-
113
- useImperativeHandle(
114
- ref,
115
- () => ({
116
- open: () => picker.setOpen(true),
117
- close: () => picker.setOpen(false),
118
- toggle: () => picker.setOpen(!picker.open),
119
- focus: () => inputRef.current?.focus({ preventScroll: true }),
120
- clear: picker.clear,
121
- getValue: () => picker.selection.start,
122
- }),
123
- [picker],
124
- );
125
-
126
- const onInputKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
127
- if (event.key === "ArrowDown" || (event.key === "Enter" && !picker.open)) {
128
- event.preventDefault();
129
- picker.setOpen(true);
130
- } else if (event.key === "Escape" && picker.open) {
131
- picker.setOpen(false);
132
- }
133
- };
134
-
135
- const describedBy =
136
- cx(description ? `${id}-description` : "", message ? `${id}-error` : "") || undefined;
137
-
138
- return (
139
- <div
140
- className={cx("dp-root", classNames?.root, className)}
141
- style={style}
142
- data-size={size}
143
- data-state={picker.open ? "open" : "closed"}
144
- data-invalid={invalid ? "" : undefined}
145
- >
146
- {label && (
147
- <label
148
- htmlFor={inputId}
149
- className={cx("dp-label", classNames?.label)}
150
- data-required={required || undefined}
151
- >
152
- {label}
153
- </label>
154
- )}
155
-
156
- <div
157
- ref={controlRef}
158
- className={cx("dp-control", classNames?.control)}
159
- data-disabled={disabled || undefined}
160
- data-invalid={invalid ? "" : undefined}
161
- >
162
- <input
163
- ref={inputRef}
164
- id={inputId}
165
- className={cx("dp-input", classNames?.input)}
166
- value={picker.inputValue("start")}
167
- placeholder={placeholder ?? inputPlaceholder(locale)}
168
- disabled={disabled}
169
- readOnly={readOnly || !allowInput}
170
- required={required}
171
- autoComplete="off"
172
- spellCheck={false}
173
- aria-invalid={invalid || undefined}
174
- aria-describedby={describedBy}
175
- onChange={(event) => picker.onInputChange("start", event.target.value)}
176
- onBlur={() => picker.onInputBlur("start")}
177
- onKeyDown={onInputKeyDown}
178
- onClick={allowInput ? undefined : () => !disabled && picker.setOpen(true)}
179
- />
180
-
181
- <span className="dp-actions">
182
- {clearable && selected && !disabled && !readOnly && (
183
- <button
184
- type="button"
185
- tabIndex={-1}
186
- className="dp-icon-button"
187
- aria-label={text.clear}
188
- onClick={picker.clear}
189
- >
190
- <XIcon width={14} height={14} />
191
- </button>
192
- )}
193
- <button
194
- ref={triggerRef}
195
- type="button"
196
- className="dp-icon-button dp-trigger"
197
- aria-label={text.openCalendar}
198
- aria-haspopup="dialog"
199
- aria-expanded={picker.open}
200
- aria-controls={picker.open ? popoverId : undefined}
201
- disabled={disabled}
202
- onPointerDown={picker.onTriggerPointerDown}
203
- onClick={picker.onTriggerClick}
204
- >
205
- <CalendarIcon />
206
- </button>
207
- </span>
208
- </div>
209
-
210
- {name && <input type="hidden" name={name} value={selected ? toISODate(selected) : ""} />}
211
- {description && (
212
- <span id={`${id}-description`} className="dp-description">
213
- {description}
214
- </span>
215
- )}
216
- {message && (
217
- <span id={`${id}-error`} className="dp-error" role="alert">
218
- {message}
219
- </span>
220
- )}
221
-
222
- {picker.anchor && (
223
- <DatePickerPopover
224
- anchor={picker.anchor}
225
- onClose={picker.onPopoverClose}
226
- label={text.calendarLabel}
227
- id={popoverId}
228
- autoFocus={picker.gridFocus}
229
- className={classNames?.popover}
230
- >
231
- <CalendarPanel
232
- picker={picker}
233
- text={text}
234
- locale={locale}
235
- showWeekNumbers={showWeekNumbers}
236
- showToday={showToday}
237
- clearable={clearable}
238
- hasValue={selected !== null}
239
- classNames={classNames}
240
- />
241
- </DatePickerPopover>
242
- )}
243
-
244
- <span className="dp-sr-only" role="status">
245
- {selected ? text.selectedDate(picker.formatValue(selected)) : ""}
246
- </span>
247
- </div>
248
- );
249
- }
1
+ "use client";
2
+
3
+ import {
4
+ useId,
5
+ useImperativeHandle,
6
+ useMemo,
7
+ useRef,
8
+ useState,
9
+ type KeyboardEvent,
10
+ type Ref,
11
+ } from "react";
12
+ import { describeField } from "../../shared/core/field";
13
+ import { toDate, toISODate } from "../core/calendar";
14
+ import { inputPlaceholder } from "../core/format";
15
+ import type { DateInput, DateRange } from "../core/types";
16
+ import { CalendarPanel } from "./CalendarPanel";
17
+ import { CalendarIcon, XIcon } from "./icons";
18
+ import { defaultDatePickerText } from "./locale";
19
+ import { DatePickerPopover } from "./Popover";
20
+ import { cx, type DatePickerHandle, type DatePickerSharedProps } from "./props";
21
+ import { useDatePicker } from "./useDatePicker";
22
+
23
+ export interface DatePickerProps extends DatePickerSharedProps {
24
+ value?: DateInput | null;
25
+ defaultValue?: DateInput | null;
26
+ onChange?(date: Date | null): void;
27
+ /** Close the calendar after choosing a date. Default true. */
28
+ closeOnSelect?: boolean;
29
+ ref?: Ref<DatePickerHandle<Date | null>>;
30
+ }
31
+
32
+ /** A single date field with a calendar, typed entry and full keyboard control. */
33
+ export function DatePicker(props: DatePickerProps) {
34
+ const {
35
+ ref,
36
+ value,
37
+ defaultValue,
38
+ onChange,
39
+ closeOnSelect = true,
40
+ min,
41
+ max,
42
+ isDateDisabled,
43
+ locale,
44
+ weekStartsOn,
45
+ format,
46
+ numberOfMonths = 1,
47
+ showWeekNumbers = false,
48
+ showToday = true,
49
+ allowInput = true,
50
+ fixedWeeks = true,
51
+ label,
52
+ description,
53
+ error,
54
+ placeholder,
55
+ required = false,
56
+ disabled = false,
57
+ readOnly = false,
58
+ clearable = true,
59
+ size = "md",
60
+ name,
61
+ id: idProp,
62
+ className,
63
+ classNames,
64
+ style,
65
+ localeText,
66
+ onOpenChange,
67
+ } = props;
68
+
69
+ const reactId = useId();
70
+ const id = idProp ?? reactId;
71
+ const inputId = `${id}-input`;
72
+ const popoverId = `${id}-popover`;
73
+ const text = useMemo(() => ({ ...defaultDatePickerText, ...localeText }), [localeText]);
74
+
75
+ const controlRef = useRef<HTMLDivElement>(null);
76
+ const triggerRef = useRef<HTMLButtonElement>(null);
77
+ const inputRef = useRef<HTMLInputElement>(null);
78
+
79
+ const minDate = useMemo(() => toDate(min), [min]);
80
+ const maxDate = useMemo(() => toDate(max), [max]);
81
+ const selection = useMemo<DateRange | undefined>(
82
+ () => (value === undefined ? undefined : { start: toDate(value), end: null }),
83
+ [value],
84
+ );
85
+ // Read once: later changes to `defaultValue` are ignored, as for any uncontrolled input.
86
+ const [initial] = useState<DateRange>(() => ({ start: toDate(defaultValue), end: null }));
87
+
88
+ const picker = useDatePicker({
89
+ range: false,
90
+ value: selection,
91
+ defaultValue: initial,
92
+ onSelectionChange: (next) => onChange?.(next.start),
93
+ min: minDate,
94
+ max: maxDate,
95
+ isDateDisabled,
96
+ locale,
97
+ weekStartsOn,
98
+ numberOfMonths,
99
+ fixedWeeks,
100
+ disabled,
101
+ readOnly,
102
+ closeOnSelect,
103
+ format,
104
+ text,
105
+ controlRef,
106
+ triggerRef,
107
+ onOpenChange,
108
+ });
109
+
110
+ const selected = picker.selection.start;
111
+ const message = error ?? picker.inputError;
112
+ const invalid = Boolean(message);
113
+
114
+ useImperativeHandle(
115
+ ref,
116
+ () => ({
117
+ open: () => picker.setOpen(true),
118
+ close: () => picker.setOpen(false),
119
+ toggle: () => picker.setOpen(!picker.open),
120
+ focus: () => inputRef.current?.focus({ preventScroll: true }),
121
+ clear: picker.clear,
122
+ getValue: () => picker.selection.start,
123
+ }),
124
+ [picker],
125
+ );
126
+
127
+ const onInputKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
128
+ if (event.key === "ArrowDown" || (event.key === "Enter" && !picker.open)) {
129
+ event.preventDefault();
130
+ picker.setOpen(true);
131
+ } else if (event.key === "Escape" && picker.open) {
132
+ picker.setOpen(false);
133
+ }
134
+ };
135
+
136
+ const describedBy = describeField(id, { description, error: message });
137
+
138
+ return (
139
+ <div
140
+ className={cx("dp-root", classNames?.root, className)}
141
+ style={style}
142
+ data-size={size}
143
+ data-state={picker.open ? "open" : "closed"}
144
+ data-invalid={invalid ? "" : undefined}
145
+ >
146
+ {label && (
147
+ <label
148
+ htmlFor={inputId}
149
+ className={cx("dp-label", classNames?.label)}
150
+ data-required={required || undefined}
151
+ >
152
+ {label}
153
+ </label>
154
+ )}
155
+
156
+ <div
157
+ ref={controlRef}
158
+ className={cx("dp-control", classNames?.control)}
159
+ data-disabled={disabled || undefined}
160
+ data-invalid={invalid ? "" : undefined}
161
+ >
162
+ <input
163
+ ref={inputRef}
164
+ id={inputId}
165
+ className={cx("dp-input", classNames?.input)}
166
+ value={picker.inputValue("start")}
167
+ placeholder={placeholder ?? inputPlaceholder(locale)}
168
+ disabled={disabled}
169
+ readOnly={readOnly || !allowInput}
170
+ required={required}
171
+ autoComplete="off"
172
+ spellCheck={false}
173
+ aria-invalid={invalid || undefined}
174
+ aria-describedby={describedBy}
175
+ onChange={(event) => picker.onInputChange("start", event.target.value)}
176
+ onBlur={() => picker.onInputBlur("start")}
177
+ onKeyDown={onInputKeyDown}
178
+ onClick={allowInput ? undefined : () => !disabled && picker.setOpen(true)}
179
+ />
180
+
181
+ <span className="dp-actions">
182
+ {clearable && selected && !disabled && !readOnly && (
183
+ <button
184
+ type="button"
185
+ tabIndex={-1}
186
+ className="dp-icon-button"
187
+ aria-label={text.clear}
188
+ onClick={picker.clear}
189
+ >
190
+ <XIcon width={14} height={14} />
191
+ </button>
192
+ )}
193
+ <button
194
+ ref={triggerRef}
195
+ type="button"
196
+ className="dp-icon-button dp-trigger"
197
+ aria-label={text.openCalendar}
198
+ aria-haspopup="dialog"
199
+ aria-expanded={picker.open}
200
+ aria-controls={picker.open ? popoverId : undefined}
201
+ disabled={disabled}
202
+ onPointerDown={picker.onTriggerPointerDown}
203
+ onClick={picker.onTriggerClick}
204
+ >
205
+ <CalendarIcon />
206
+ </button>
207
+ </span>
208
+ </div>
209
+
210
+ {name && <input type="hidden" name={name} value={selected ? toISODate(selected) : ""} />}
211
+ {description && (
212
+ <span id={`${id}-description`} className="dp-description">
213
+ {description}
214
+ </span>
215
+ )}
216
+ {message && (
217
+ <span id={`${id}-error`} className="dp-error" role="alert">
218
+ {message}
219
+ </span>
220
+ )}
221
+
222
+ {picker.anchor && (
223
+ <DatePickerPopover
224
+ anchor={picker.anchor}
225
+ onClose={picker.onPopoverClose}
226
+ label={text.calendarLabel}
227
+ id={popoverId}
228
+ autoFocus={picker.gridFocus}
229
+ className={classNames?.popover}
230
+ >
231
+ <CalendarPanel
232
+ picker={picker}
233
+ text={text}
234
+ locale={locale}
235
+ showWeekNumbers={showWeekNumbers}
236
+ showToday={showToday}
237
+ clearable={clearable}
238
+ hasValue={selected !== null}
239
+ classNames={classNames}
240
+ />
241
+ </DatePickerPopover>
242
+ )}
243
+
244
+ <span className="dp-sr-only" role="status">
245
+ {selected ? text.selectedDate(picker.formatValue(selected)) : ""}
246
+ </span>
247
+ </div>
248
+ );
249
+ }