@godxjp/ui 21.0.0 → 23.0.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 (154) hide show
  1. package/README.md +4 -4
  2. package/dist/app/app-provider.js +2 -1
  3. package/dist/components/data-display/badge.d.ts +28 -1
  4. package/dist/components/data-display/badge.js +2 -0
  5. package/dist/components/data-display/data-table.d.ts +18 -2
  6. package/dist/components/data-display/data-table.js +4 -0
  7. package/dist/components/data-display/index.d.ts +1 -1
  8. package/dist/components/data-display/popover.d.ts +19 -1
  9. package/dist/components/data-display/popover.js +9 -1
  10. package/dist/components/data-display/progress.d.ts +37 -0
  11. package/dist/components/data-display/progress.js +57 -5
  12. package/dist/components/data-display/scroll-area.d.ts +24 -4
  13. package/dist/components/data-display/scroll-area.js +34 -72
  14. package/dist/components/data-entry/cascader.js +8 -6
  15. package/dist/components/data-entry/checkbox.js +2 -0
  16. package/dist/components/data-entry/choice-hit-target.d.ts +24 -0
  17. package/dist/components/data-entry/choice-hit-target.js +12 -0
  18. package/dist/components/data-entry/control-surface.d.ts +8 -0
  19. package/dist/components/data-entry/control-surface.js +10 -1
  20. package/dist/components/data-entry/date-picker.d.ts +10 -3
  21. package/dist/components/data-entry/date-picker.js +503 -238
  22. package/dist/components/data-entry/form-field.d.ts +1 -1
  23. package/dist/components/data-entry/form-field.js +7 -3
  24. package/dist/components/data-entry/form.js +10 -1
  25. package/dist/components/data-entry/index.d.ts +0 -6
  26. package/dist/components/data-entry/index.js +0 -6
  27. package/dist/components/data-entry/label.d.ts +14 -1
  28. package/dist/components/data-entry/label.js +6 -1
  29. package/dist/components/data-entry/radio.d.ts +27 -6
  30. package/dist/components/data-entry/radio.js +89 -43
  31. package/dist/components/data-entry/search-select.js +226 -89
  32. package/dist/components/data-entry/select.d.ts +76 -14
  33. package/dist/components/data-entry/select.js +497 -163
  34. package/dist/components/data-entry/slider.d.ts +3 -11
  35. package/dist/components/data-entry/slider.js +582 -93
  36. package/dist/components/data-entry/switch.d.ts +6 -3
  37. package/dist/components/data-entry/switch.js +36 -11
  38. package/dist/components/layout/mobile-shell.d.ts +1 -1
  39. package/dist/components/layout/mobile-shell.js +2 -0
  40. package/dist/components/navigation/dropdown-menu.d.ts +11 -3
  41. package/dist/components/navigation/dropdown-menu.js +190 -15
  42. package/dist/components/navigation/index.d.ts +0 -3
  43. package/dist/components/navigation/index.js +0 -78
  44. package/dist/components/navigation/steps.d.ts +1 -1
  45. package/dist/components/navigation/steps.js +3 -1
  46. package/dist/components/navigation/tabs-scroll.d.ts +27 -0
  47. package/dist/components/navigation/tabs-scroll.js +52 -1
  48. package/dist/components/navigation/tabs.d.ts +2 -2
  49. package/dist/components/navigation/tabs.js +88 -23
  50. package/dist/components/ui/index.d.ts +0 -4
  51. package/dist/components/ui/index.js +0 -4
  52. package/dist/components/ui/input-otp.d.ts +1 -1
  53. package/dist/components/ui/segmented.d.ts +15 -1
  54. package/dist/components/ui/segmented.js +15 -6
  55. package/dist/components/ui/tag-input.js +3 -5
  56. package/dist/i18n/messages/en.json +3 -2
  57. package/dist/i18n/messages/ja.json +3 -2
  58. package/dist/i18n/messages/vi.json +3 -2
  59. package/dist/lib/datetime/picker-format.d.ts +18 -0
  60. package/dist/lib/datetime/picker-format.js +35 -1
  61. package/dist/lib/field-a11y.d.ts +12 -0
  62. package/dist/lib/field-a11y.js +14 -1
  63. package/dist/lib/select-options.d.ts +21 -0
  64. package/dist/lib/select-options.js +46 -0
  65. package/dist/props/components/data-display.prop.d.ts +16 -6
  66. package/dist/props/components/data-entry.prop.d.ts +503 -129
  67. package/dist/props/components/index.d.ts +2 -2
  68. package/dist/props/components/layout.prop.d.ts +12 -1
  69. package/dist/props/components/navigation.prop.d.ts +93 -1
  70. package/dist/props/registry.d.ts +79 -16
  71. package/dist/props/registry.js +85 -15
  72. package/dist/props/vocabulary/data.prop.d.ts +12 -0
  73. package/dist/props/vocabulary/index.d.ts +2 -2
  74. package/dist/props/vocabulary/layout.prop.d.ts +11 -0
  75. package/dist/styles/badge-layout.css +4 -0
  76. package/dist/styles/card-layout.css +6 -6
  77. package/dist/styles/control.css +220 -29
  78. package/dist/styles/data-display-layout.css +94 -23
  79. package/dist/styles/focus-ring.css +6 -4
  80. package/dist/styles/fonts.css +88 -4
  81. package/dist/styles/navigation-layout.css +36 -236
  82. package/dist/styles/shell-layout.css +149 -139
  83. package/dist/styles/table-layout.css +41 -3
  84. package/dist/tokens/components/control.css +4 -0
  85. package/dist/tokens/components/data-display.css +9 -5
  86. package/dist/tokens/components/navigation.css +9 -5
  87. package/dist/tokens/components/scroll-area.css +7 -0
  88. package/dist/tokens/components/segmented.css +0 -2
  89. package/dist/tokens/components/shell.css +2 -0
  90. package/dist/tokens/components/table.css +3 -0
  91. package/dist/tokens/foundation.css +7 -0
  92. package/docs/COMPONENTS.md +14 -14
  93. package/docs/CONSUMER-RULES.md +24 -1
  94. package/docs/CUSTOMER-THEMING.md +7 -1
  95. package/docs/FRAME-COVERAGE-REPORT.md +5 -11
  96. package/docs/TOKENS.md +65 -0
  97. package/docs/data-display/badge.tsx +37 -0
  98. package/docs/data-display/data-table/index.tsx +45 -1
  99. package/docs/data-display/popover.tsx +43 -5
  100. package/docs/data-display/progress.tsx +28 -0
  101. package/docs/data-display/scroll-area.tsx +51 -45
  102. package/docs/data-entry/calendar.tsx +2 -2
  103. package/docs/data-entry/date-picker.tsx +144 -5
  104. package/docs/data-entry/form/examples/invoice-form.tsx +2 -2
  105. package/docs/data-entry/form-field/examples/a11y-contract.tsx +3 -5
  106. package/docs/data-entry/form.tsx +4 -4
  107. package/docs/data-entry/input-otp.tsx +5 -2
  108. package/docs/data-entry/number-input.tsx +31 -0
  109. package/docs/data-entry/segmented.tsx +143 -2
  110. package/docs/data-entry/select-matrix.tsx +1 -2
  111. package/docs/data-entry/select.tsx +108 -3
  112. package/docs/data-entry/slider.tsx +125 -47
  113. package/docs/feedback/dialog.tsx +1 -2
  114. package/docs/feedback/sheet.tsx +1 -2
  115. package/docs/layout/app-shell-states.tsx +143 -0
  116. package/docs/layout/mobile-shell.tsx +5 -0
  117. package/docs/navigation/dropdown-menu.tsx +75 -4
  118. package/docs/navigation/tabs.tsx +149 -0
  119. package/docs/roadmap/antd-parity.md +35 -34
  120. package/docs/roadmap/parity-audit-data-entry.md +196 -172
  121. package/docs/roadmap/parity-audit-layout-navigation-general.md +304 -310
  122. package/docs/roadmap/parity-backlog.md +8 -10
  123. package/docs/showcase/acme-portal.tsx +7 -0
  124. package/package.json +28 -64
  125. package/scripts/ui-audit.mjs +18 -5
  126. package/scripts/visual-audit-rules.mjs +0 -7
  127. package/scripts/visual-audit.mjs +6 -27
  128. package/dist/components/data-entry/date-range-picker.d.ts +0 -11
  129. package/dist/components/data-entry/date-range-picker.js +0 -349
  130. package/dist/components/data-entry/month-picker.d.ts +0 -10
  131. package/dist/components/data-entry/month-picker.js +0 -241
  132. package/dist/components/data-entry/month-range-picker.d.ts +0 -10
  133. package/dist/components/data-entry/month-range-picker.js +0 -301
  134. package/dist/components/navigation/context-menu.d.ts +0 -21
  135. package/dist/components/navigation/context-menu.js +0 -149
  136. package/dist/components/navigation/menubar.d.ts +0 -21
  137. package/dist/components/navigation/menubar.js +0 -136
  138. package/dist/components/navigation/navigation-menu.d.ts +0 -10
  139. package/dist/components/navigation/navigation-menu.js +0 -91
  140. package/dist/components/ui/context-menu.d.ts +0 -1
  141. package/dist/components/ui/context-menu.js +0 -2
  142. package/dist/components/ui/date-range-picker.d.ts +0 -1
  143. package/dist/components/ui/date-range-picker.js +0 -2
  144. package/dist/components/ui/menubar.d.ts +0 -1
  145. package/dist/components/ui/menubar.js +0 -2
  146. package/dist/components/ui/navigation-menu.d.ts +0 -1
  147. package/dist/components/ui/navigation-menu.js +0 -2
  148. package/docs/FRAME-A11Y-CI.md +0 -349
  149. package/docs/data-entry/date-range-picker.tsx +0 -175
  150. package/docs/data-entry/month-picker.tsx +0 -101
  151. package/docs/data-entry/month-range-picker.tsx +0 -115
  152. package/docs/navigation/context-menu.tsx +0 -128
  153. package/docs/navigation/menubar.tsx +0 -141
  154. package/docs/navigation/navigation-menu.tsx +0 -158
@@ -1,349 +0,0 @@
1
- "use client";
2
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
- import * as React from "react";
4
- import { ArrowRight, CalendarIcon, X } from "lucide-react";
5
- import { usePickerLocales, useTranslation } from "../../i18n/use-translation.js";
6
- import { toIsoDate } from "../../lib/datetime/index.js";
7
- import {
8
- formatPickerDate,
9
- parsePickerDate,
10
- pickerDateAllowed
11
- } from "../../lib/datetime/picker-format.js";
12
- import { Button } from "../general/button.js";
13
- import { Flex } from "../layout/flex.js";
14
- import {
15
- CONTROL_VARIANT_CHROME_CLASS,
16
- CONTROL_STATUS_CHROME_CLASS,
17
- controlAppearanceAttributes
18
- } from "./control-appearance.js";
19
- import { useControlledLatch } from "../../lib/hooks.js";
20
- import { pickGroupFieldA11y, useFieldIdentity } from "../../lib/field-a11y.js";
21
- import { cn } from "../../lib/utils.js";
22
- import { resolveAllowClear } from "./control-surface.js";
23
- import { Popover, PopoverAnchor, PopoverContent, PopoverTrigger } from "../data-display/popover.js";
24
- import { Calendar } from "./calendar.js";
25
- const ISO_HINT = "yyyy-mm-dd";
26
- function DateRangePicker({
27
- value: valueProp,
28
- defaultValue,
29
- onValueChange,
30
- placeholder,
31
- disabled,
32
- className,
33
- id,
34
- name,
35
- locale: localeProp,
36
- showToday,
37
- showClose,
38
- fromDate,
39
- toDate,
40
- disabledDate,
41
- cellRender,
42
- allowClear,
43
- format,
44
- parseFormat,
45
- minDate,
46
- maxDate,
47
- showWeek,
48
- presets,
49
- needConfirm = false,
50
- allowEmpty = [true, true],
51
- order = true,
52
- open: openProp,
53
- defaultOpen = false,
54
- onOpenChange,
55
- inputReadOnly,
56
- preserveInvalidOnBlur,
57
- placement = "bottom-start",
58
- renderExtraFooter,
59
- size,
60
- status,
61
- variant = "outlined",
62
- ref,
63
- ...ariaProps
64
- }) {
65
- const { t } = useTranslation();
66
- const { dayPickerLocale, locale } = usePickerLocales(localeProp);
67
- const appearance = controlAppearanceAttributes({ status, variant });
68
- const [internalOpen, setInternalOpen] = React.useState(defaultOpen);
69
- const open = !disabled && (openProp ?? internalOpen);
70
- const [pending, setPending] = React.useState();
71
- const [hasPending, setHasPending] = React.useState(false);
72
- const setOpen = (next) => {
73
- if (disabled && next) return;
74
- if (openProp === void 0) setInternalOpen(next);
75
- onOpenChange?.(next);
76
- if (!next) {
77
- setHasPending(false);
78
- setPending(void 0);
79
- }
80
- };
81
- const minimum = minDate ?? fromDate;
82
- const maximum = maxDate ?? toDate;
83
- const allowed = (date) => pickerDateAllowed(date, minimum, maximum, disabledDate);
84
- const display = (date) => formatPickerDate(date, format, locale);
85
- const parse = (text) => parsePickerDate(text, format, parseFormat);
86
- const groupA11y = pickGroupFieldA11y(ariaProps);
87
- const autoId = React.useId();
88
- const groupId = id ?? autoId;
89
- const fromId = `${groupId}-from`;
90
- const toId = `${groupId}-to`;
91
- const identity = useFieldIdentity({ id: groupId, name, "data-field": groupA11y["data-field"] });
92
- const rangeField = groupA11y["data-field"] ?? identity["data-field"];
93
- const rangeName = name ?? identity.name;
94
- const isControlled = useControlledLatch(valueProp !== void 0);
95
- const [internalValue, setInternalValue] = React.useState(defaultValue);
96
- const value = isControlled ? valueProp : internalValue;
97
- const working = hasPending ? pending : value;
98
- const [fromText, setFromText] = React.useState(() => display(value?.from));
99
- const [toText, setToText] = React.useState(() => display(value?.to));
100
- React.useEffect(() => {
101
- setFromText(formatPickerDate(value?.from, format, locale));
102
- setToText(formatPickerDate(value?.to, format, locale));
103
- }, [value?.from, value?.to, format, locale]);
104
- const resolvedPlaceholder = placeholder ?? t("dataEntry.dateRangePicker.placeholder") ?? ISO_HINT;
105
- const emit = (next) => {
106
- if (!isControlled) setInternalValue(next);
107
- onValueChange?.(next);
108
- };
109
- const clearControl = resolveAllowClear(allowClear, true, t("common.clear") ?? "Clear");
110
- const showClear = clearControl.enabled && allowEmpty.every(Boolean) && Boolean(value?.from || value?.to) && !disabled;
111
- const clear = () => {
112
- emit(void 0);
113
- setFromText("");
114
- setToText("");
115
- };
116
- const validRange = (next) => Boolean(
117
- next && (next.from ? allowed(next.from) : allowEmpty[0]) && (next.to ? allowed(next.to) : allowEmpty[1])
118
- );
119
- const choose = (range) => {
120
- let next = range;
121
- if (next?.from && !allowed(next.from) || next?.to && !allowed(next.to)) return;
122
- if (order && next?.from && next.to && next.from > next.to)
123
- next = { from: next.to, to: next.from };
124
- if (needConfirm) {
125
- setPending(next);
126
- setHasPending(true);
127
- } else emit(next);
128
- };
129
- const commitEdge = (edge, raw) => {
130
- const trimmed = raw.trim();
131
- const parsed = parse(trimmed);
132
- if (trimmed && (!parsed || !allowed(parsed))) return;
133
- if (!trimmed && !allowEmpty[edge === "from" ? 0 : 1]) return;
134
- const next = { from: working?.from, to: working?.to, [edge]: parsed };
135
- choose(next.from || next.to ? next : void 0);
136
- };
137
- const sharedKeyHandlers = {
138
- onKeyDown: (event) => {
139
- if (event.key === "ArrowDown") {
140
- event.preventDefault();
141
- setOpen(true);
142
- } else if (event.key === "Escape" && open) {
143
- setOpen(false);
144
- }
145
- }
146
- };
147
- const innerInputClass = "ui-month-picker-input";
148
- return /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: setOpen, children: [
149
- (format || needConfirm || preserveInvalidOnBlur) && rangeName ? /* @__PURE__ */ jsxs(Fragment, { children: [
150
- /* @__PURE__ */ jsx(
151
- "input",
152
- {
153
- type: "hidden",
154
- disabled,
155
- name: `${rangeName}_from`,
156
- value: toIsoDate(value?.from)
157
- }
158
- ),
159
- /* @__PURE__ */ jsx(
160
- "input",
161
- {
162
- type: "hidden",
163
- disabled,
164
- name: `${rangeName}_to`,
165
- value: toIsoDate(value?.to)
166
- }
167
- )
168
- ] }) : null,
169
- /* @__PURE__ */ jsx(PopoverAnchor, { asChild: true, children: /* @__PURE__ */ jsxs(
170
- "div",
171
- {
172
- role: "group",
173
- id: groupId,
174
- ...groupA11y,
175
- "data-field": rangeField,
176
- "data-size": size,
177
- ...appearance,
178
- "aria-disabled": disabled ? true : void 0,
179
- "data-state": open ? "open" : "closed",
180
- className: cn(
181
- // One input-styled shell for the whole range — the shared composite-field box, so
182
- // this and the month range picker cannot drift into two slightly different fields.
183
- "ui-control ui-control-composite-field",
184
- "aria-invalid:border-destructive",
185
- CONTROL_VARIANT_CHROME_CLASS[variant],
186
- CONTROL_STATUS_CHROME_CLASS,
187
- className
188
- ),
189
- onClick: () => {
190
- if (!disabled) setOpen(true);
191
- },
192
- children: [
193
- /* @__PURE__ */ jsx(
194
- "input",
195
- {
196
- id: fromId,
197
- "data-field": rangeField ? `${rangeField}_from` : void 0,
198
- name: !format && !needConfirm && !preserveInvalidOnBlur && rangeName ? `${rangeName}_from` : void 0,
199
- ref,
200
- readOnly: inputReadOnly,
201
- value: fromText,
202
- disabled,
203
- placeholder: resolvedPlaceholder,
204
- inputMode: "numeric",
205
- autoComplete: "off",
206
- "aria-label": t("dataEntry.dateRangePicker.from") ?? "From",
207
- className: innerInputClass,
208
- ...sharedKeyHandlers,
209
- onChange: (event) => {
210
- setFromText(event.target.value);
211
- commitEdge("from", event.target.value);
212
- },
213
- onBlur: (event) => {
214
- const parsed = parse(event.target.value);
215
- const accepted = parsed && allowed(parsed) ? parsed : void 0;
216
- if (!preserveInvalidOnBlur)
217
- setFromText(accepted ? display(accepted) : display(value?.from));
218
- }
219
- }
220
- ),
221
- /* @__PURE__ */ jsx(ArrowRight, { className: "ui-month-picker-separator-icon", "aria-hidden": "true" }),
222
- /* @__PURE__ */ jsx(
223
- "input",
224
- {
225
- id: toId,
226
- "data-field": rangeField ? `${rangeField}_to` : void 0,
227
- name: !format && !needConfirm && !preserveInvalidOnBlur && rangeName ? `${rangeName}_to` : void 0,
228
- readOnly: inputReadOnly,
229
- value: toText,
230
- disabled,
231
- placeholder: resolvedPlaceholder,
232
- inputMode: "numeric",
233
- autoComplete: "off",
234
- "aria-label": t("dataEntry.dateRangePicker.to") ?? "To",
235
- className: innerInputClass,
236
- ...sharedKeyHandlers,
237
- onChange: (event) => {
238
- setToText(event.target.value);
239
- commitEdge("to", event.target.value);
240
- },
241
- onBlur: (event) => {
242
- const parsed = parse(event.target.value);
243
- const accepted = parsed && allowed(parsed) ? parsed : void 0;
244
- if (!preserveInvalidOnBlur)
245
- setToText(accepted ? display(accepted) : display(value?.to));
246
- }
247
- }
248
- ),
249
- showClear ? /* @__PURE__ */ jsx(
250
- "button",
251
- {
252
- type: "button",
253
- tabIndex: -1,
254
- "aria-label": clearControl.label,
255
- className: "text-muted-foreground hover:text-foreground shrink-0",
256
- onClick: (event) => {
257
- event.stopPropagation();
258
- event.currentTarget.closest("div")?.querySelector("input:not([type=hidden])")?.focus();
259
- clear();
260
- },
261
- children: clearControl.clearIcon ?? /* @__PURE__ */ jsx(X, { className: "ui-month-picker-icon", "aria-hidden": "true" })
262
- }
263
- ) : /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
264
- "button",
265
- {
266
- type: "button",
267
- disabled,
268
- tabIndex: -1,
269
- "aria-label": t("dataEntry.dateRangePicker.openCalendar") ?? "Open calendar",
270
- className: "text-muted-foreground hover:text-foreground shrink-0",
271
- children: /* @__PURE__ */ jsx(CalendarIcon, { className: "ui-month-picker-icon", "aria-hidden": "true" })
272
- }
273
- ) }),
274
- /* @__PURE__ */ jsxs(
275
- PopoverContent,
276
- {
277
- className: "ui-control-panel-flush",
278
- side: placement.startsWith("top") ? "top" : "bottom",
279
- align: placement.endsWith("end") ? "end" : "start",
280
- onClick: (event) => event.stopPropagation(),
281
- onOpenAutoFocus: (event) => event.preventDefault(),
282
- children: [
283
- presets?.length ? /* @__PURE__ */ jsx(Flex, { wrap: true, gap: "xs", pad: "sm", children: presets.map((preset, index) => /* @__PURE__ */ jsx(
284
- Button,
285
- {
286
- type: "button",
287
- variant: "ghost",
288
- size: "sm",
289
- onClick: () => {
290
- const range = typeof preset.value === "function" ? preset.value() : preset.value;
291
- if (validRange(range)) choose(range);
292
- },
293
- children: preset.label
294
- },
295
- index
296
- )) }) : null,
297
- /* @__PURE__ */ jsx(
298
- Calendar,
299
- {
300
- mode: "range",
301
- selected: working,
302
- defaultMonth: working?.from,
303
- showWeekNumber: showWeek,
304
- numberOfMonths: 2,
305
- onSelect: (range) => {
306
- choose(range);
307
- setFromText(display(range?.from));
308
- setToText(display(range?.to));
309
- },
310
- locale: dayPickerLocale,
311
- disabled: [
312
- ...minimum ? [{ before: minimum }] : [],
313
- ...maximum ? [{ after: maximum }] : [],
314
- ...disabledDate ? [disabledDate] : []
315
- ],
316
- cellRender,
317
- startMonth: minimum,
318
- endMonth: maximum,
319
- showToday,
320
- showClose,
321
- onClose: () => setOpen(false)
322
- }
323
- ),
324
- needConfirm ? /* @__PURE__ */ jsx(Flex, { pad: "sm", justify: "end", children: /* @__PURE__ */ jsx(
325
- Button,
326
- {
327
- type: "button",
328
- disabled: !validRange(working),
329
- onClick: () => {
330
- if (validRange(working)) {
331
- emit(working);
332
- setOpen(false);
333
- }
334
- },
335
- children: t("dataEntry.timePicker.confirm")
336
- }
337
- ) }) : null,
338
- renderExtraFooter?.()
339
- ]
340
- }
341
- )
342
- ]
343
- }
344
- ) })
345
- ] });
346
- }
347
- export {
348
- DateRangePicker
349
- };
@@ -1,10 +0,0 @@
1
- import * as React from "react";
2
- import type { MonthPickerProp } from "../../props/components/data-entry.prop.js";
3
- export type { MonthPickerProp, MonthPickerProp as MonthPickerProps, } from "../../props/components/data-entry.prop.js";
4
- /**
5
- * MonthPicker — year/month (`yyyy/MM`) input with a 12-cell month grid
6
- * popover: a year header with chevrons over a 3×4 grid of the twelve months.
7
- * The field stays a real, typeable input (form-submittable via `name` as
8
- * `yyyy-MM`); the grid is the visual affordance.
9
- */
10
- export declare function MonthPicker({ value: valueProp, defaultValue, onValueChange, placeholder, disabled, className, id, name, fromYear, toYear, allowClear, open: openProp, defaultOpen, onOpenChange, status, variant, size, inputReadOnly, preserveInvalidOnBlur, placement, renderExtraFooter, ref, ...ariaProps }: MonthPickerProp): React.JSX.Element;
@@ -1,241 +0,0 @@
1
- "use client";
2
- import { jsx, jsxs } from "react/jsx-runtime";
3
- import * as React from "react";
4
- import { CalendarIcon, ChevronLeft, ChevronRight, X } from "lucide-react";
5
- import { usePickerLocales, useTranslation } from "../../i18n/use-translation.js";
6
- import { useControlledLatch } from "../../lib/hooks.js";
7
- import { pickFieldA11y, useFieldIdentity } from "../../lib/field-a11y.js";
8
- import { cn } from "../../lib/utils.js";
9
- import { resolveAllowClear, resolveAriaInvalid } from "./control-surface.js";
10
- import { CONTROL_STATUS_CHROME_CLASS, CONTROL_VARIANT_CHROME_CLASS } from "./control-appearance.js";
11
- import { Button } from "../general/button.js";
12
- import { Popover, PopoverAnchor, PopoverContent, PopoverTrigger } from "../data-display/popover.js";
13
- const YM_HINT = "yyyy/mm";
14
- const toYmText = (d) => d ? `${d.getFullYear()}/${String(d.getMonth() + 1).padStart(2, "0")}` : "";
15
- const parseYm = (raw) => {
16
- const m = /^(\d{4})[/-](\d{1,2})$/.exec(raw.trim());
17
- if (!m) return void 0;
18
- const month = Number(m[2]);
19
- if (month < 1 || month > 12) return void 0;
20
- return new Date(Number(m[1]), month - 1, 1);
21
- };
22
- function MonthPicker({
23
- value: valueProp,
24
- defaultValue,
25
- onValueChange,
26
- placeholder,
27
- disabled,
28
- className,
29
- id,
30
- name,
31
- fromYear,
32
- toYear,
33
- allowClear,
34
- open: openProp,
35
- defaultOpen = false,
36
- onOpenChange,
37
- status,
38
- variant,
39
- size,
40
- inputReadOnly,
41
- preserveInvalidOnBlur,
42
- placement = "bottom-start",
43
- renderExtraFooter,
44
- ref,
45
- ...ariaProps
46
- }) {
47
- const { t } = useTranslation();
48
- const { locale } = usePickerLocales();
49
- const [internalOpen, setInternalOpen] = React.useState(defaultOpen);
50
- const isOpenControlled = openProp !== void 0;
51
- const open = isOpenControlled ? openProp : internalOpen;
52
- const setOpen = React.useCallback(
53
- (next) => {
54
- if (next && inputReadOnly) return;
55
- if (!isOpenControlled) setInternalOpen(next);
56
- onOpenChange?.(next);
57
- },
58
- [isOpenControlled, onOpenChange, inputReadOnly]
59
- );
60
- const autoId = React.useId();
61
- const inputId = id ?? autoId;
62
- const dialogId = `${inputId}-dialog`;
63
- const fieldA11y = pickFieldA11y(ariaProps);
64
- const identity = useFieldIdentity({ id, name, "data-field": fieldA11y["data-field"] });
65
- const isControlled = useControlledLatch(valueProp !== void 0);
66
- const [internalValue, setInternalValue] = React.useState(defaultValue);
67
- const value = isControlled ? valueProp : internalValue;
68
- const [text, setText] = React.useState(() => toYmText(value));
69
- const [viewYear, setViewYear] = React.useState(() => (value ?? /* @__PURE__ */ new Date()).getFullYear());
70
- React.useEffect(() => {
71
- setText(toYmText(value));
72
- if (value) setViewYear(value.getFullYear());
73
- }, [value]);
74
- const monthLabels = React.useMemo(() => {
75
- const fmt = new Intl.DateTimeFormat(locale, { month: "short" });
76
- return Array.from({ length: 12 }, (_, i) => fmt.format(new Date(2026, i, 1)));
77
- }, [locale]);
78
- const emit = (next) => {
79
- if (!isControlled) setInternalValue(next);
80
- onValueChange?.(next);
81
- };
82
- const clearControl = resolveAllowClear(allowClear, true, t("common.clear") ?? "Clear");
83
- const showClear = clearControl.enabled && Boolean(value) && !disabled && !inputReadOnly;
84
- const prevDisabled = fromYear !== void 0 && viewYear <= fromYear;
85
- const nextDisabled = toYear !== void 0 && viewYear >= toYear;
86
- return /* @__PURE__ */ jsx(Popover, { open, onOpenChange: setOpen, children: /* @__PURE__ */ jsx(PopoverAnchor, { asChild: true, children: /* @__PURE__ */ jsxs(
87
- "div",
88
- {
89
- "data-disabled": disabled ? "" : void 0,
90
- "data-state": open ? "open" : "closed",
91
- "data-size": size,
92
- "data-status": status,
93
- "data-variant": variant,
94
- className: cn(
95
- "ui-control ui-control-composite-field",
96
- "aria-invalid:border-destructive",
97
- CONTROL_VARIANT_CHROME_CLASS[variant ?? "outlined"],
98
- CONTROL_STATUS_CHROME_CLASS,
99
- className
100
- ),
101
- onClick: () => {
102
- if (!disabled) setOpen(true);
103
- },
104
- children: [
105
- /* @__PURE__ */ jsx(
106
- "input",
107
- {
108
- ref,
109
- id: inputId,
110
- name: name ?? identity.name,
111
- "data-field": fieldA11y["data-field"] ?? identity["data-field"],
112
- value: text,
113
- disabled,
114
- readOnly: inputReadOnly,
115
- placeholder: placeholder ?? t("dataEntry.monthPicker.placeholder") ?? YM_HINT,
116
- inputMode: "numeric",
117
- autoComplete: "off",
118
- role: "combobox",
119
- "aria-expanded": open,
120
- "aria-haspopup": "dialog",
121
- "aria-controls": open ? dialogId : void 0,
122
- "aria-readonly": inputReadOnly || void 0,
123
- ...fieldA11y,
124
- "aria-invalid": resolveAriaInvalid(fieldA11y["aria-invalid"], status),
125
- className: "ui-month-picker-input",
126
- onKeyDown: (event) => {
127
- if (event.key === "ArrowDown") {
128
- event.preventDefault();
129
- setOpen(true);
130
- } else if (event.key === "Escape" && open) {
131
- setOpen(false);
132
- }
133
- },
134
- onChange: (event) => {
135
- setText(event.target.value);
136
- const parsed = parseYm(event.target.value);
137
- if (parsed) emit(parsed);
138
- else if (event.target.value.trim() === "") emit(void 0);
139
- },
140
- onBlur: () => {
141
- if (!preserveInvalidOnBlur) setText(toYmText(value));
142
- }
143
- }
144
- ),
145
- showClear ? /* @__PURE__ */ jsx(
146
- "button",
147
- {
148
- type: "button",
149
- tabIndex: -1,
150
- "aria-label": clearControl.label,
151
- className: "text-muted-foreground hover:text-foreground shrink-0",
152
- onClick: (event) => {
153
- event.stopPropagation();
154
- event.currentTarget.closest("div")?.querySelector("input:not([type=hidden])")?.focus();
155
- emit(void 0);
156
- setText("");
157
- },
158
- children: clearControl.clearIcon ?? /* @__PURE__ */ jsx(X, { className: "ui-month-picker-icon", "aria-hidden": "true" })
159
- }
160
- ) : /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
161
- "button",
162
- {
163
- type: "button",
164
- disabled,
165
- tabIndex: -1,
166
- "aria-label": t("dataEntry.monthPicker.openGrid") ?? "Open month grid",
167
- className: "text-muted-foreground hover:text-foreground shrink-0",
168
- children: /* @__PURE__ */ jsx(CalendarIcon, { className: "ui-month-picker-icon", "aria-hidden": "true" })
169
- }
170
- ) }),
171
- /* @__PURE__ */ jsxs(
172
- PopoverContent,
173
- {
174
- id: dialogId,
175
- role: "dialog",
176
- "aria-label": t("dataEntry.monthPicker.openGrid") ?? "Month grid",
177
- className: "ui-month-picker-panel",
178
- side: placement.startsWith("top") ? "top" : "bottom",
179
- align: placement.endsWith("end") ? "end" : "start",
180
- onOpenAutoFocus: (event) => event.preventDefault(),
181
- onClick: (event) => event.stopPropagation(),
182
- children: [
183
- /* @__PURE__ */ jsxs("div", { className: "ui-month-picker-nav", children: [
184
- /* @__PURE__ */ jsx(
185
- Button,
186
- {
187
- type: "button",
188
- variant: "outline",
189
- size: "icon-sm",
190
- disabled: prevDisabled,
191
- "aria-label": t("dataEntry.monthPicker.previousYear") ?? "Previous year",
192
- className: "ui-month-picker-nav-button",
193
- onClick: () => setViewYear((y) => y - 1),
194
- children: /* @__PURE__ */ jsx(ChevronLeft, { className: "ui-month-picker-icon", "aria-hidden": "true" })
195
- }
196
- ),
197
- /* @__PURE__ */ jsx("span", { className: "ui-month-picker-nav-label", "aria-live": "polite", children: viewYear }),
198
- /* @__PURE__ */ jsx(
199
- Button,
200
- {
201
- type: "button",
202
- variant: "outline",
203
- size: "icon-sm",
204
- disabled: nextDisabled,
205
- "aria-label": t("dataEntry.monthPicker.nextYear") ?? "Next year",
206
- className: "ui-month-picker-nav-button",
207
- onClick: () => setViewYear((y) => y + 1),
208
- children: /* @__PURE__ */ jsx(ChevronRight, { className: "ui-month-picker-icon", "aria-hidden": "true" })
209
- }
210
- )
211
- ] }),
212
- /* @__PURE__ */ jsx("div", { role: "grid", "aria-label": String(viewYear), className: "ui-month-picker-grid", children: monthLabels.map((label, i) => {
213
- const selected = value?.getFullYear() === viewYear && value?.getMonth() === i;
214
- return /* @__PURE__ */ jsx(
215
- Button,
216
- {
217
- type: "button",
218
- variant: selected ? "default" : "ghost",
219
- size: "sm",
220
- "aria-pressed": selected,
221
- className: "ui-month-picker-cell",
222
- onClick: () => {
223
- emit(new Date(viewYear, i, 1));
224
- setOpen(false);
225
- },
226
- children: label
227
- },
228
- label
229
- );
230
- }) }),
231
- renderExtraFooter?.()
232
- ]
233
- }
234
- )
235
- ]
236
- }
237
- ) }) });
238
- }
239
- export {
240
- MonthPicker
241
- };
@@ -1,10 +0,0 @@
1
- import * as React from "react";
2
- import type { MonthRangePickerProp } from "../../props/components/data-entry.prop.js";
3
- export type { MonthRangePickerProp, MonthRangePickerProp as MonthRangePickerProps, } from "../../props/components/data-entry.prop.js";
4
- /**
5
- * MonthRangePicker — year/month (`yyyy/MM`) RANGE rendered as ONE input-styled control (the
6
- * RangePicker convention): `[ from → to ✕ 📅 ]`. The two inner fields stay real, typeable
7
- * `yyyy/MM` inputs (form-submittable via `${name}_from` / `${name}_to`); the Ant-style month grid
8
- * is the visual affordance.
9
- */
10
- export declare function MonthRangePicker({ value: valueProp, defaultValue, onValueChange, placeholder, disabled, className, id, name, fromYear, toYear, allowClear, open: openProp, defaultOpen, onOpenChange, status, variant, size, inputReadOnly, preserveInvalidOnBlur, placement, renderExtraFooter, ref, ...ariaProps }: MonthRangePickerProp): React.JSX.Element;