@godxjp/ui 13.6.0 → 13.8.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 (106) hide show
  1. package/dist/app/index.js +4 -4
  2. package/dist/{checkbox-DAIwpMHO.d.ts → checkbox-BpnDa6_M.d.ts} +1 -1
  3. package/dist/{chunk-2JW2QLUU.js → chunk-2MRAHKOD.js} +3 -3
  4. package/dist/{chunk-6MZMMMHV.js → chunk-3VFNWVR7.js} +5 -5
  5. package/dist/{chunk-IRPZJKLU.js → chunk-4FGE5XVC.js} +10 -0
  6. package/dist/{chunk-J254ZX3A.js → chunk-4SSJ54R2.js} +4 -4
  7. package/dist/{chunk-C7W5BO7R.js → chunk-5732TWQF.js} +22 -1
  8. package/dist/chunk-57VDEN64.js +881 -0
  9. package/dist/{chunk-UKTVFPRV.js → chunk-65RWSIZF.js} +4 -4
  10. package/dist/{chunk-5XFFEW2P.js → chunk-A3J2G2ZA.js} +2 -2
  11. package/dist/{chunk-QTEPKNQE.js → chunk-B6S6LTWT.js} +1 -1
  12. package/dist/{chunk-PKSHDFKP.js → chunk-BJTNKNMY.js} +36 -10
  13. package/dist/{chunk-W2RYT6NT.js → chunk-D4JX6O2W.js} +3 -3
  14. package/dist/{chunk-KNI7QI4J.js → chunk-FLY23H4G.js} +2 -2
  15. package/dist/{chunk-ZYJRZLMZ.js → chunk-HIEGUYPP.js} +1 -1
  16. package/dist/{chunk-UVGI2RPC.js → chunk-HRW7JDUJ.js} +4 -4
  17. package/dist/{chunk-UDMFKYZG.js → chunk-I5D4QYZI.js} +3 -3
  18. package/dist/{chunk-76S3TQFH.js → chunk-IAJOP2Z3.js} +31 -13
  19. package/dist/{chunk-LFO5BJ4H.js → chunk-ION2KFXK.js} +2 -2
  20. package/dist/{chunk-6AIYMYSG.js → chunk-K24AQV4R.js} +1 -1
  21. package/dist/{chunk-6VUW6EKH.js → chunk-L6NMIUKC.js} +2 -2
  22. package/dist/{chunk-RBXVCWXL.js → chunk-LVLPNKJP.js} +2 -2
  23. package/dist/{chunk-HS73G2C3.js → chunk-NU2SOVTU.js} +9 -3
  24. package/dist/{chunk-B7R7DHZF.js → chunk-NZBAFFWP.js} +1 -1
  25. package/dist/{chunk-LTKHPOE3.js → chunk-PL4NEIVO.js} +1 -1
  26. package/dist/{chunk-ZOMIELKI.js → chunk-Q3KUZG4P.js} +1 -1
  27. package/dist/{chunk-DVV6UYVS.js → chunk-QE4IYHJU.js} +3 -3
  28. package/dist/{chunk-3JQDPNTA.js → chunk-R2TXLS7S.js} +1 -1
  29. package/dist/{chunk-2FJ5TMQU.js → chunk-X6AJ5HEI.js} +1 -1
  30. package/dist/{chunk-XZH3URGD.js → chunk-XBFUROAY.js} +6 -3
  31. package/dist/{chunk-I472SSN2.js → chunk-YLRCDJMR.js} +1 -1
  32. package/dist/chunk-YXVJFYQE.js +191 -0
  33. package/dist/components/admin/index.d.ts +2 -2
  34. package/dist/components/admin/index.js +23 -23
  35. package/dist/components/data-display/badge.js +3 -3
  36. package/dist/components/data-display/card.d.ts +2 -2
  37. package/dist/components/data-display/carousel.js +3 -3
  38. package/dist/components/data-display/index.js +7 -7
  39. package/dist/components/data-entry/calendar.d.ts +1 -1
  40. package/dist/components/data-entry/calendar.js +4 -4
  41. package/dist/components/data-entry/cascader.d.ts +1 -1
  42. package/dist/components/data-entry/cascader.js +4 -4
  43. package/dist/components/data-entry/checkbox.d.ts +2 -2
  44. package/dist/components/data-entry/color-picker.d.ts +1 -1
  45. package/dist/components/data-entry/color-picker.js +4 -4
  46. package/dist/components/data-entry/date-picker.d.ts +1 -1
  47. package/dist/components/data-entry/date-picker.js +6 -6
  48. package/dist/components/data-entry/date-range-picker.d.ts +6 -4
  49. package/dist/components/data-entry/date-range-picker.js +5 -6
  50. package/dist/components/data-entry/index.d.ts +24 -6
  51. package/dist/components/data-entry/index.js +21 -21
  52. package/dist/components/data-entry/input.js +3 -3
  53. package/dist/components/data-entry/radio.d.ts +1 -1
  54. package/dist/components/data-entry/select.d.ts +1 -1
  55. package/dist/components/data-entry/select.js +5 -5
  56. package/dist/components/data-entry/slider.d.ts +1 -1
  57. package/dist/components/data-entry/switch.d.ts +1 -1
  58. package/dist/components/data-entry/textarea.js +3 -3
  59. package/dist/components/data-entry/time-input.js +2 -2
  60. package/dist/components/data-entry/time-picker.d.ts +1 -1
  61. package/dist/components/data-entry/time-picker.js +5 -5
  62. package/dist/components/data-entry/transfer.d.ts +2 -2
  63. package/dist/components/data-entry/transfer.js +6 -6
  64. package/dist/components/data-entry/tree-select.d.ts +1 -1
  65. package/dist/components/data-entry/tree-select.js +4 -4
  66. package/dist/components/data-entry/upload.d.ts +2 -2
  67. package/dist/components/data-entry/upload.js +7 -7
  68. package/dist/components/data-grid/index.js +6 -6
  69. package/dist/components/feedback/alert.js +5 -5
  70. package/dist/components/feedback/dialog.js +5 -5
  71. package/dist/components/feedback/index.js +7 -7
  72. package/dist/components/general/button.d.ts +3 -1
  73. package/dist/components/general/button.js +3 -3
  74. package/dist/components/general/index.d.ts +1 -1
  75. package/dist/components/general/index.js +3 -3
  76. package/dist/components/layout/index.js +3 -3
  77. package/dist/components/navigation/index.js +9 -9
  78. package/dist/components/navigation/pagination.js +6 -6
  79. package/dist/components/navigation/steps.js +3 -3
  80. package/dist/components/query/index.d.ts +2 -2
  81. package/dist/components/query/index.js +5 -5
  82. package/dist/components/ui/index.d.ts +3 -3
  83. package/dist/components/ui/index.js +22 -22
  84. package/dist/{data-entry.prop-vn5VGnwY.d.ts → data-entry.prop-C9QWj5Kl.d.ts} +61 -2
  85. package/dist/form/index.js +1 -1
  86. package/dist/{general.prop-CzcLDzMw.d.ts → general.prop-CzlQin6v.d.ts} +11 -1
  87. package/dist/i18n/index.d.ts +6 -0
  88. package/dist/i18n/index.js +2 -2
  89. package/dist/index.d.ts +2 -2
  90. package/dist/index.js +31 -31
  91. package/dist/lib/datetime/index.js +1 -1
  92. package/dist/props/components/index.d.ts +3 -3
  93. package/dist/props/index.d.ts +3 -3
  94. package/dist/props/index.js +1 -1
  95. package/dist/props/registry.d.ts +10 -0
  96. package/dist/props/registry.js +1 -1
  97. package/dist/{query.prop-DAILZ0x5.d.ts → query.prop-CT2gFtr4.d.ts} +1 -1
  98. package/dist/{search-input-Oxwpv_s9.d.ts → search-input-d1g8eW9z.d.ts} +1 -1
  99. package/dist/styles/form-layout.css +5 -5
  100. package/dist/styles/layout.css +2 -2
  101. package/dist/tokens/base.css +1 -0
  102. package/dist/tokens/components/form.css +11 -0
  103. package/dist/tokens/semantic/layout.css +11 -0
  104. package/package.json +4 -2
  105. package/dist/chunk-GFDKJY63.js +0 -467
  106. package/dist/chunk-OIAT773P.js +0 -183
@@ -0,0 +1,881 @@
1
+ import { Input } from './chunk-K24AQV4R.js';
2
+ import { Button } from './chunk-XBFUROAY.js';
3
+ import { Popover, PopoverAnchor, PopoverTrigger, PopoverContent } from './chunk-DY5C44UP.js';
4
+ import { useTranslation, usePickerLocales } from './chunk-B6S6LTWT.js';
5
+ import { cn } from './chunk-U7N2A7A3.js';
6
+ import * as React from 'react';
7
+ import { ChevronUp, ChevronDown, EyeOff, Eye, Minus, Star, X, CalendarIcon, ChevronLeft, ChevronRight, ArrowRight } from 'lucide-react';
8
+ import { jsxs, jsx } from 'react/jsx-runtime';
9
+ import { OTPInput, OTPInputContext } from 'input-otp';
10
+
11
+ function decimalsOf(n) {
12
+ if (!Number.isFinite(n)) return 0;
13
+ const s = String(n);
14
+ const dot = s.indexOf(".");
15
+ return dot === -1 ? 0 : s.length - dot - 1;
16
+ }
17
+ function clamp(n, min, max) {
18
+ let out = n;
19
+ if (min != null && out < min) out = min;
20
+ if (max != null && out > max) out = max;
21
+ return out;
22
+ }
23
+ function roundTo(n, precision) {
24
+ if (!Number.isFinite(n)) return n;
25
+ const factor = 10 ** precision;
26
+ return Math.round((n + Number.EPSILON) * factor) / factor;
27
+ }
28
+ var NumberInput = React.forwardRef(
29
+ ({
30
+ value: controlledValue,
31
+ defaultValue = null,
32
+ onValueChange,
33
+ min,
34
+ max,
35
+ step = 1,
36
+ precision,
37
+ disabled,
38
+ readOnly,
39
+ size = "md",
40
+ placeholder,
41
+ prefix,
42
+ suffix,
43
+ name,
44
+ id,
45
+ className,
46
+ "aria-label": ariaLabel,
47
+ "aria-labelledby": ariaLabelledby,
48
+ "aria-describedby": ariaDescribedby,
49
+ "data-testid": dataTestId
50
+ }, ref) => {
51
+ const { t, locale } = useTranslation();
52
+ const isControlled = controlledValue !== void 0;
53
+ const [internal, setInternal] = React.useState(defaultValue);
54
+ const numericValue = isControlled ? controlledValue ?? null : internal;
55
+ const effectivePrecision = precision ?? decimalsOf(step);
56
+ const formatter = React.useMemo(
57
+ () => new Intl.NumberFormat(locale, {
58
+ minimumFractionDigits: 0,
59
+ maximumFractionDigits: Math.max(effectivePrecision, 0),
60
+ useGrouping: false
61
+ }),
62
+ [locale, effectivePrecision]
63
+ );
64
+ const formatAtRest = React.useCallback(
65
+ (n) => n == null ? "" : formatter.format(n),
66
+ [formatter]
67
+ );
68
+ const [draft, setDraft] = React.useState(() => formatAtRest(numericValue));
69
+ const [focused, setFocused] = React.useState(false);
70
+ React.useEffect(() => {
71
+ if (focused) return;
72
+ setDraft(formatAtRest(numericValue));
73
+ }, [numericValue, focused, formatAtRest]);
74
+ const commit = React.useCallback(
75
+ (next) => {
76
+ const normalized = next == null || Number.isNaN(next) ? null : roundTo(clamp(next, min, max), effectivePrecision);
77
+ if (!isControlled) setInternal(normalized);
78
+ onValueChange?.(normalized);
79
+ if (!focused) setDraft(formatAtRest(normalized));
80
+ return normalized;
81
+ },
82
+ [effectivePrecision, focused, formatAtRest, isControlled, max, min, onValueChange]
83
+ );
84
+ const stepBy = React.useCallback(
85
+ (direction, multiplier = 1) => {
86
+ const base = numericValue ?? min ?? 0;
87
+ const next = base + direction * step * multiplier;
88
+ const committed = commit(next);
89
+ setDraft(formatAtRest(committed));
90
+ },
91
+ [commit, formatAtRest, min, numericValue, step]
92
+ );
93
+ const atMin = min != null && numericValue != null && numericValue <= min;
94
+ const atMax = max != null && numericValue != null && numericValue >= max;
95
+ const interactive = !disabled && !readOnly;
96
+ const handleChange = (event) => {
97
+ const raw = event.target.value;
98
+ setDraft(raw);
99
+ if (readOnly) return;
100
+ const trimmed = raw.trim();
101
+ if (trimmed === "" || trimmed === "-") {
102
+ if (trimmed === "") commit(null);
103
+ return;
104
+ }
105
+ const parsed = Number(trimmed);
106
+ if (Number.isNaN(parsed)) return;
107
+ if (!isControlled) setInternal(parsed);
108
+ onValueChange?.(parsed);
109
+ };
110
+ const handleBlur = () => {
111
+ setFocused(false);
112
+ const trimmed = draft.trim();
113
+ if (trimmed === "" || trimmed === "-") {
114
+ commit(null);
115
+ setDraft(formatAtRest(null));
116
+ return;
117
+ }
118
+ const parsed = Number(trimmed);
119
+ const committed = commit(Number.isNaN(parsed) ? null : parsed);
120
+ setDraft(formatAtRest(committed));
121
+ };
122
+ const handleKeyDown = (event) => {
123
+ if (!interactive) return;
124
+ if (event.key === "ArrowUp") {
125
+ event.preventDefault();
126
+ stepBy(1, event.shiftKey ? 10 : 1);
127
+ } else if (event.key === "ArrowDown") {
128
+ event.preventDefault();
129
+ stepBy(-1, event.shiftKey ? 10 : 1);
130
+ } else if (event.key === "Enter") {
131
+ const trimmed = draft.trim();
132
+ const parsed = Number(trimmed);
133
+ const committed = commit(trimmed === "" || Number.isNaN(parsed) ? null : parsed);
134
+ setDraft(formatAtRest(committed));
135
+ }
136
+ };
137
+ return /* @__PURE__ */ jsxs("div", { "data-slot": "number-input", "data-size": size, className: cn("ui-number-input", className), children: [
138
+ prefix != null ? /* @__PURE__ */ jsx(
139
+ "span",
140
+ {
141
+ "data-slot": "number-input-prefix",
142
+ className: "ui-number-input-affix",
143
+ "aria-hidden": "true",
144
+ children: prefix
145
+ }
146
+ ) : null,
147
+ /* @__PURE__ */ jsx(
148
+ Input,
149
+ {
150
+ ref,
151
+ id,
152
+ name,
153
+ type: "text",
154
+ inputMode: "decimal",
155
+ autoComplete: "off",
156
+ role: "spinbutton",
157
+ "data-slot": "number-input-field",
158
+ "data-testid": dataTestId,
159
+ className: "ui-number-input-field",
160
+ value: draft,
161
+ placeholder,
162
+ disabled,
163
+ readOnly,
164
+ "aria-label": ariaLabel,
165
+ "aria-labelledby": ariaLabelledby,
166
+ "aria-describedby": ariaDescribedby,
167
+ "aria-valuenow": numericValue ?? void 0,
168
+ "aria-valuemin": min,
169
+ "aria-valuemax": max,
170
+ "aria-valuetext": numericValue != null ? formatAtRest(numericValue) : void 0,
171
+ onChange: handleChange,
172
+ onFocus: () => setFocused(true),
173
+ onBlur: handleBlur,
174
+ onKeyDown: handleKeyDown
175
+ }
176
+ ),
177
+ suffix != null ? /* @__PURE__ */ jsx(
178
+ "span",
179
+ {
180
+ "data-slot": "number-input-suffix",
181
+ className: "ui-number-input-affix",
182
+ "aria-hidden": "true",
183
+ children: suffix
184
+ }
185
+ ) : null,
186
+ /* @__PURE__ */ jsxs("span", { "data-slot": "number-input-steppers", className: "ui-number-input-steppers", children: [
187
+ /* @__PURE__ */ jsx(
188
+ Button,
189
+ {
190
+ type: "button",
191
+ variant: "outline",
192
+ size: "icon-xs",
193
+ className: "ui-number-input-step ui-number-input-step-up",
194
+ tabIndex: -1,
195
+ disabled: !interactive || atMax,
196
+ "aria-label": t("ui.numberInput.increment"),
197
+ onClick: () => stepBy(1),
198
+ children: /* @__PURE__ */ jsx(ChevronUp, { "aria-hidden": "true" })
199
+ }
200
+ ),
201
+ /* @__PURE__ */ jsx(
202
+ Button,
203
+ {
204
+ type: "button",
205
+ variant: "outline",
206
+ size: "icon-xs",
207
+ className: "ui-number-input-step ui-number-input-step-down",
208
+ tabIndex: -1,
209
+ disabled: !interactive || atMin,
210
+ "aria-label": t("ui.numberInput.decrement"),
211
+ onClick: () => stepBy(-1),
212
+ children: /* @__PURE__ */ jsx(ChevronDown, { "aria-hidden": "true" })
213
+ }
214
+ )
215
+ ] })
216
+ ] });
217
+ }
218
+ );
219
+ NumberInput.displayName = "NumberInput";
220
+ var YM_HINT = "yyyy/mm";
221
+ var toYmText = (d) => d ? `${d.getFullYear()}/${String(d.getMonth() + 1).padStart(2, "0")}` : "";
222
+ var parseYm = (raw) => {
223
+ const m = /^(\d{4})[/-](\d{1,2})$/.exec(raw.trim());
224
+ if (!m) return void 0;
225
+ const month = Number(m[2]);
226
+ if (month < 1 || month > 12) return void 0;
227
+ return new Date(Number(m[1]), month - 1, 1);
228
+ };
229
+ function MonthPicker({
230
+ value: valueProp,
231
+ defaultValue,
232
+ onValueChange,
233
+ placeholder,
234
+ disabled,
235
+ className,
236
+ id,
237
+ name,
238
+ fromYear,
239
+ toYear,
240
+ allowClear = true
241
+ }) {
242
+ const { t } = useTranslation();
243
+ const { locale } = usePickerLocales();
244
+ const [open, setOpen] = React.useState(false);
245
+ const autoId = React.useId();
246
+ const inputId = id ?? autoId;
247
+ const isControlled = React.useRef(valueProp !== void 0).current;
248
+ const [internalValue, setInternalValue] = React.useState(defaultValue);
249
+ const value = isControlled ? valueProp : internalValue;
250
+ const [text, setText] = React.useState(() => toYmText(value));
251
+ const [viewYear, setViewYear] = React.useState(() => (value ?? /* @__PURE__ */ new Date()).getFullYear());
252
+ React.useEffect(() => {
253
+ setText(toYmText(value));
254
+ if (value) setViewYear(value.getFullYear());
255
+ }, [value]);
256
+ const monthLabels = React.useMemo(() => {
257
+ const fmt = new Intl.DateTimeFormat(locale, { month: "short" });
258
+ return Array.from({ length: 12 }, (_, i) => fmt.format(new Date(2026, i, 1)));
259
+ }, [locale]);
260
+ const emit = (next) => {
261
+ if (!isControlled) setInternalValue(next);
262
+ onValueChange?.(next);
263
+ };
264
+ const showClear = allowClear && Boolean(value) && !disabled;
265
+ const prevDisabled = fromYear !== void 0 && viewYear <= fromYear;
266
+ const nextDisabled = toYear !== void 0 && viewYear >= toYear;
267
+ return /* @__PURE__ */ jsx(Popover, { open, onOpenChange: setOpen, children: /* @__PURE__ */ jsx(PopoverAnchor, { asChild: true, children: /* @__PURE__ */ jsxs(
268
+ "div",
269
+ {
270
+ className: cn(
271
+ "ui-control border-input bg-background flex w-full min-w-0 items-center gap-2 rounded-md border px-3 shadow-xs transition-[color,box-shadow] outline-none",
272
+ "focus-within:border-ring focus-within:ring-ring/50 focus-within:ring-[3px]",
273
+ open && "border-ring ring-ring/50 ring-[3px]",
274
+ disabled && "pointer-events-none cursor-not-allowed opacity-50",
275
+ className
276
+ ),
277
+ onClick: () => {
278
+ if (!disabled) setOpen(true);
279
+ },
280
+ children: [
281
+ /* @__PURE__ */ jsx(
282
+ "input",
283
+ {
284
+ id: inputId,
285
+ name,
286
+ value: text,
287
+ disabled,
288
+ placeholder: placeholder ?? t("dataEntry.monthPicker.placeholder") ?? YM_HINT,
289
+ inputMode: "numeric",
290
+ autoComplete: "off",
291
+ className: "placeholder:text-muted-foreground min-w-0 flex-1 bg-transparent tabular-nums outline-none disabled:cursor-not-allowed",
292
+ onKeyDown: (event) => {
293
+ if (event.key === "ArrowDown") {
294
+ event.preventDefault();
295
+ setOpen(true);
296
+ } else if (event.key === "Escape" && open) {
297
+ setOpen(false);
298
+ }
299
+ },
300
+ onChange: (event) => {
301
+ setText(event.target.value);
302
+ const parsed = parseYm(event.target.value);
303
+ if (parsed) emit(parsed);
304
+ else if (event.target.value.trim() === "") emit(void 0);
305
+ },
306
+ onBlur: () => setText(toYmText(value))
307
+ }
308
+ ),
309
+ showClear ? /* @__PURE__ */ jsx(
310
+ "button",
311
+ {
312
+ type: "button",
313
+ tabIndex: -1,
314
+ "aria-label": t("common.clear") ?? "Clear",
315
+ className: "text-muted-foreground hover:text-foreground shrink-0",
316
+ onClick: (event) => {
317
+ event.stopPropagation();
318
+ emit(void 0);
319
+ setText("");
320
+ },
321
+ children: /* @__PURE__ */ jsx(X, { className: "size-4 shrink-0", "aria-hidden": "true" })
322
+ }
323
+ ) : null,
324
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
325
+ "button",
326
+ {
327
+ type: "button",
328
+ disabled,
329
+ tabIndex: -1,
330
+ "aria-label": t("dataEntry.monthPicker.openGrid") ?? "Open month grid",
331
+ className: "text-muted-foreground hover:text-foreground shrink-0",
332
+ children: /* @__PURE__ */ jsx(CalendarIcon, { className: "size-4 shrink-0", "aria-hidden": "true" })
333
+ }
334
+ ) }),
335
+ /* @__PURE__ */ jsxs(
336
+ PopoverContent,
337
+ {
338
+ className: "w-auto p-3",
339
+ align: "start",
340
+ onOpenAutoFocus: (event) => event.preventDefault(),
341
+ onClick: (event) => event.stopPropagation(),
342
+ children: [
343
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
344
+ /* @__PURE__ */ jsx(
345
+ Button,
346
+ {
347
+ type: "button",
348
+ variant: "outline",
349
+ size: "icon-sm",
350
+ disabled: prevDisabled,
351
+ "aria-label": t("dataEntry.monthPicker.previousYear") ?? "Previous year",
352
+ className: "bg-transparent opacity-70 hover:opacity-100",
353
+ onClick: () => setViewYear((y) => y - 1),
354
+ children: /* @__PURE__ */ jsx(ChevronLeft, { className: "size-4", "aria-hidden": "true" })
355
+ }
356
+ ),
357
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", "aria-live": "polite", children: viewYear }),
358
+ /* @__PURE__ */ jsx(
359
+ Button,
360
+ {
361
+ type: "button",
362
+ variant: "outline",
363
+ size: "icon-sm",
364
+ disabled: nextDisabled,
365
+ "aria-label": t("dataEntry.monthPicker.nextYear") ?? "Next year",
366
+ className: "bg-transparent opacity-70 hover:opacity-100",
367
+ onClick: () => setViewYear((y) => y + 1),
368
+ children: /* @__PURE__ */ jsx(ChevronRight, { className: "size-4", "aria-hidden": "true" })
369
+ }
370
+ )
371
+ ] }),
372
+ /* @__PURE__ */ jsx("div", { role: "grid", "aria-label": String(viewYear), className: "mt-3 grid grid-cols-3 gap-1", children: monthLabels.map((label, i) => {
373
+ const selected = value?.getFullYear() === viewYear && value?.getMonth() === i;
374
+ return /* @__PURE__ */ jsx(
375
+ Button,
376
+ {
377
+ type: "button",
378
+ variant: selected ? "default" : "ghost",
379
+ size: "sm",
380
+ "aria-pressed": selected,
381
+ className: "px-4 font-normal",
382
+ onClick: () => {
383
+ emit(new Date(viewYear, i, 1));
384
+ setOpen(false);
385
+ },
386
+ children: label
387
+ },
388
+ label
389
+ );
390
+ }) })
391
+ ]
392
+ }
393
+ )
394
+ ]
395
+ }
396
+ ) }) });
397
+ }
398
+ var YM_HINT2 = "yyyy/mm";
399
+ var toYmText2 = (d) => d ? `${d.getFullYear()}/${String(d.getMonth() + 1).padStart(2, "0")}` : "";
400
+ var parseYm2 = (raw) => {
401
+ const m = /^(\d{4})[/-](\d{1,2})$/.exec(raw.trim());
402
+ if (!m) return void 0;
403
+ const month = Number(m[2]);
404
+ if (month < 1 || month > 12) return void 0;
405
+ return new Date(Number(m[1]), month - 1, 1);
406
+ };
407
+ var ymIndex = (d) => d.getFullYear() * 12 + d.getMonth();
408
+ function MonthRangePicker({
409
+ value: valueProp,
410
+ defaultValue,
411
+ onValueChange,
412
+ placeholder,
413
+ disabled,
414
+ className,
415
+ id,
416
+ name,
417
+ fromYear,
418
+ toYear,
419
+ allowClear = true
420
+ }) {
421
+ const { t } = useTranslation();
422
+ const { locale } = usePickerLocales();
423
+ const [open, setOpen] = React.useState(false);
424
+ const autoId = React.useId();
425
+ const fromId = id ?? autoId;
426
+ const toId = `${fromId}-to`;
427
+ const isControlled = React.useRef(valueProp !== void 0).current;
428
+ const [internalValue, setInternalValue] = React.useState(defaultValue);
429
+ const value = isControlled ? valueProp : internalValue;
430
+ const [fromText, setFromText] = React.useState(() => toYmText2(value?.from));
431
+ const [toText, setToText] = React.useState(() => toYmText2(value?.to));
432
+ const [viewYear, setViewYear] = React.useState(() => (value?.from ?? /* @__PURE__ */ new Date()).getFullYear());
433
+ React.useEffect(() => {
434
+ setFromText(toYmText2(value?.from));
435
+ setToText(toYmText2(value?.to));
436
+ if (value?.from) setViewYear(value.from.getFullYear());
437
+ }, [value?.from, value?.to]);
438
+ const monthLabels = React.useMemo(() => {
439
+ const fmt = new Intl.DateTimeFormat(locale, { month: "short" });
440
+ return Array.from({ length: 12 }, (_, i) => fmt.format(new Date(2026, i, 1)));
441
+ }, [locale]);
442
+ const emit = (next) => {
443
+ if (!isControlled) setInternalValue(next);
444
+ onValueChange?.(next);
445
+ };
446
+ const showClear = allowClear && Boolean(value?.from || value?.to) && !disabled;
447
+ const prevDisabled = fromYear !== void 0 && viewYear <= fromYear;
448
+ const nextDisabled = toYear !== void 0 && viewYear >= toYear;
449
+ const clear = () => {
450
+ emit(void 0);
451
+ setFromText("");
452
+ setToText("");
453
+ };
454
+ const commitEdge = (edge, raw) => {
455
+ const trimmed = raw.trim();
456
+ const parsed = trimmed === "" ? void 0 : parseYm2(trimmed);
457
+ if (trimmed !== "" && !parsed) return;
458
+ const next = { from: value?.from, to: value?.to, [edge]: parsed };
459
+ if (next.from && next.to && ymIndex(next.from) > ymIndex(next.to)) {
460
+ const swapped = { from: next.to, to: next.from };
461
+ emit(swapped);
462
+ return;
463
+ }
464
+ emit(next.from || next.to ? next : void 0);
465
+ };
466
+ const pickMonth = (picked) => {
467
+ const pendingFrom = value?.from && !value?.to ? value.from : void 0;
468
+ if (!pendingFrom) {
469
+ emit({ from: picked, to: void 0 });
470
+ return;
471
+ }
472
+ const [from, to] = ymIndex(picked) < ymIndex(pendingFrom) ? [picked, pendingFrom] : [pendingFrom, picked];
473
+ emit({ from, to });
474
+ setOpen(false);
475
+ };
476
+ const sharedKeyHandlers = {
477
+ onKeyDown: (event) => {
478
+ if (event.key === "ArrowDown") {
479
+ event.preventDefault();
480
+ setOpen(true);
481
+ } else if (event.key === "Escape" && open) {
482
+ setOpen(false);
483
+ }
484
+ }
485
+ };
486
+ const innerInputClass = "min-w-0 flex-1 bg-transparent tabular-nums outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed";
487
+ const resolvedPlaceholder = placeholder ?? t("dataEntry.monthPicker.placeholder") ?? YM_HINT2;
488
+ return /* @__PURE__ */ jsx(Popover, { open, onOpenChange: setOpen, children: /* @__PURE__ */ jsx(PopoverAnchor, { asChild: true, children: /* @__PURE__ */ jsxs(
489
+ "div",
490
+ {
491
+ className: cn(
492
+ "ui-control border-input bg-background flex w-full min-w-0 items-center gap-2 rounded-md border px-3 shadow-xs transition-[color,box-shadow] outline-none",
493
+ "focus-within:border-ring focus-within:ring-ring/50 focus-within:ring-[3px]",
494
+ open && "border-ring ring-ring/50 ring-[3px]",
495
+ disabled && "pointer-events-none cursor-not-allowed opacity-50",
496
+ className
497
+ ),
498
+ onClick: () => {
499
+ if (!disabled) setOpen(true);
500
+ },
501
+ children: [
502
+ /* @__PURE__ */ jsx(
503
+ "input",
504
+ {
505
+ id: fromId,
506
+ name: name ? `${name}_from` : void 0,
507
+ value: fromText,
508
+ disabled,
509
+ placeholder: resolvedPlaceholder,
510
+ inputMode: "numeric",
511
+ autoComplete: "off",
512
+ "aria-label": t("dataEntry.dateRangePicker.from") ?? "From",
513
+ className: innerInputClass,
514
+ ...sharedKeyHandlers,
515
+ onChange: (event) => {
516
+ setFromText(event.target.value);
517
+ commitEdge("from", event.target.value);
518
+ },
519
+ onBlur: () => setFromText(toYmText2(value?.from))
520
+ }
521
+ ),
522
+ /* @__PURE__ */ jsx(ArrowRight, { className: "text-muted-foreground size-3.5 shrink-0", "aria-hidden": "true" }),
523
+ /* @__PURE__ */ jsx(
524
+ "input",
525
+ {
526
+ id: toId,
527
+ name: name ? `${name}_to` : void 0,
528
+ value: toText,
529
+ disabled,
530
+ placeholder: resolvedPlaceholder,
531
+ inputMode: "numeric",
532
+ autoComplete: "off",
533
+ "aria-label": t("dataEntry.dateRangePicker.to") ?? "To",
534
+ className: innerInputClass,
535
+ ...sharedKeyHandlers,
536
+ onChange: (event) => {
537
+ setToText(event.target.value);
538
+ commitEdge("to", event.target.value);
539
+ },
540
+ onBlur: () => setToText(toYmText2(value?.to))
541
+ }
542
+ ),
543
+ showClear ? /* @__PURE__ */ jsx(
544
+ "button",
545
+ {
546
+ type: "button",
547
+ tabIndex: -1,
548
+ "aria-label": t("common.clear") ?? "Clear",
549
+ className: "text-muted-foreground hover:text-foreground shrink-0",
550
+ onClick: (event) => {
551
+ event.stopPropagation();
552
+ clear();
553
+ },
554
+ children: /* @__PURE__ */ jsx(X, { className: "size-4 shrink-0", "aria-hidden": "true" })
555
+ }
556
+ ) : null,
557
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
558
+ "button",
559
+ {
560
+ type: "button",
561
+ disabled,
562
+ tabIndex: -1,
563
+ "aria-label": t("dataEntry.monthPicker.openGrid") ?? "Open month grid",
564
+ className: "text-muted-foreground hover:text-foreground shrink-0",
565
+ children: /* @__PURE__ */ jsx(CalendarIcon, { className: "size-4 shrink-0", "aria-hidden": "true" })
566
+ }
567
+ ) }),
568
+ /* @__PURE__ */ jsxs(
569
+ PopoverContent,
570
+ {
571
+ className: "w-auto p-3",
572
+ align: "start",
573
+ onOpenAutoFocus: (event) => event.preventDefault(),
574
+ onClick: (event) => event.stopPropagation(),
575
+ children: [
576
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
577
+ /* @__PURE__ */ jsx(
578
+ Button,
579
+ {
580
+ type: "button",
581
+ variant: "outline",
582
+ size: "icon-sm",
583
+ disabled: prevDisabled,
584
+ "aria-label": t("dataEntry.monthPicker.previousYear") ?? "Previous year",
585
+ className: "bg-transparent opacity-70 hover:opacity-100",
586
+ onClick: () => setViewYear((y) => y - 1),
587
+ children: /* @__PURE__ */ jsx(ChevronLeft, { className: "size-4", "aria-hidden": "true" })
588
+ }
589
+ ),
590
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", "aria-live": "polite", children: viewYear }),
591
+ /* @__PURE__ */ jsx(
592
+ Button,
593
+ {
594
+ type: "button",
595
+ variant: "outline",
596
+ size: "icon-sm",
597
+ disabled: nextDisabled,
598
+ "aria-label": t("dataEntry.monthPicker.nextYear") ?? "Next year",
599
+ className: "bg-transparent opacity-70 hover:opacity-100",
600
+ onClick: () => setViewYear((y) => y + 1),
601
+ children: /* @__PURE__ */ jsx(ChevronRight, { className: "size-4", "aria-hidden": "true" })
602
+ }
603
+ )
604
+ ] }),
605
+ /* @__PURE__ */ jsx("div", { role: "grid", "aria-label": String(viewYear), className: "mt-3 grid grid-cols-3 gap-1", children: monthLabels.map((label, i) => {
606
+ const cell = new Date(viewYear, i, 1);
607
+ const cellIdx = ymIndex(cell);
608
+ const fromIdx = value?.from ? ymIndex(value.from) : void 0;
609
+ const toIdx = value?.to ? ymIndex(value.to) : void 0;
610
+ const isEdge = cellIdx === fromIdx || cellIdx === toIdx;
611
+ const inRange = fromIdx !== void 0 && toIdx !== void 0 && cellIdx > fromIdx && cellIdx < toIdx;
612
+ return /* @__PURE__ */ jsx(
613
+ Button,
614
+ {
615
+ type: "button",
616
+ variant: isEdge ? "default" : inRange ? "secondary" : "ghost",
617
+ size: "sm",
618
+ "aria-pressed": isEdge,
619
+ className: "px-4 font-normal",
620
+ onClick: () => pickMonth(cell),
621
+ children: label
622
+ },
623
+ label
624
+ );
625
+ }) })
626
+ ]
627
+ }
628
+ )
629
+ ]
630
+ }
631
+ ) }) });
632
+ }
633
+ var PasswordInput = React.forwardRef(
634
+ ({ className, ...props }, ref) => {
635
+ const { t } = useTranslation();
636
+ const [visible, setVisible] = React.useState(false);
637
+ return /* @__PURE__ */ jsxs("div", { className: "ui-password-input", "data-slot": "password-input", children: [
638
+ /* @__PURE__ */ jsx(
639
+ Input,
640
+ {
641
+ ref,
642
+ type: visible ? "text" : "password",
643
+ className: cn("ui-password-input-field", className),
644
+ ...props
645
+ }
646
+ ),
647
+ /* @__PURE__ */ jsx(
648
+ "button",
649
+ {
650
+ type: "button",
651
+ className: "ui-password-input-toggle",
652
+ onClick: () => setVisible((v) => !v),
653
+ "aria-label": visible ? t("ui.passwordInput.hide") : t("ui.passwordInput.show"),
654
+ "aria-pressed": visible,
655
+ children: visible ? /* @__PURE__ */ jsx(EyeOff, { "aria-hidden": "true" }) : /* @__PURE__ */ jsx(Eye, { "aria-hidden": "true" })
656
+ }
657
+ )
658
+ ] });
659
+ }
660
+ );
661
+ PasswordInput.displayName = "PasswordInput";
662
+ var InputOTP = React.forwardRef(({ className, containerClassName, ...props }, ref) => /* @__PURE__ */ jsx(
663
+ OTPInput,
664
+ {
665
+ ref,
666
+ "data-slot": "input-otp",
667
+ containerClassName: cn("ui-otp-container", containerClassName),
668
+ className: cn("ui-otp-input", className),
669
+ ...props
670
+ }
671
+ ));
672
+ InputOTP.displayName = "InputOTP";
673
+ var InputOTPGroup = React.forwardRef(
674
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsx(
675
+ "div",
676
+ {
677
+ ref,
678
+ "data-slot": "input-otp-group",
679
+ className: cn("ui-otp-group", className),
680
+ ...props
681
+ }
682
+ )
683
+ );
684
+ InputOTPGroup.displayName = "InputOTPGroup";
685
+ var InputOTPSlot = React.forwardRef(({ index, className, ...props }, ref) => {
686
+ const context = React.useContext(OTPInputContext);
687
+ const slot = context.slots[index] ?? { char: null, hasFakeCaret: false, isActive: false };
688
+ return /* @__PURE__ */ jsxs(
689
+ "div",
690
+ {
691
+ ref,
692
+ "data-slot": "input-otp-slot",
693
+ "data-active": slot.isActive || void 0,
694
+ className: cn("ui-otp-slot", className),
695
+ ...props,
696
+ children: [
697
+ slot.char,
698
+ slot.hasFakeCaret ? /* @__PURE__ */ jsx("div", { className: "ui-otp-caret-wrapper", "aria-hidden": "true", children: /* @__PURE__ */ jsx("div", { className: "ui-otp-caret" }) }) : null
699
+ ]
700
+ }
701
+ );
702
+ });
703
+ InputOTPSlot.displayName = "InputOTPSlot";
704
+ var InputOTPSeparator = React.forwardRef(({ ...props }, ref) => /* @__PURE__ */ jsx("div", { ref, "data-slot": "input-otp-separator", role: "separator", ...props, children: /* @__PURE__ */ jsx(Minus, { className: "ui-otp-separator-icon", "aria-hidden": "true" }) }));
705
+ InputOTPSeparator.displayName = "InputOTPSeparator";
706
+ var Rating = React.forwardRef(
707
+ ({
708
+ value,
709
+ defaultValue = 0,
710
+ onValueChange,
711
+ max = 5,
712
+ readOnly,
713
+ disabled,
714
+ name,
715
+ className,
716
+ ...rest
717
+ }, ref) => {
718
+ const { t } = useTranslation();
719
+ const [internal, setInternal] = React.useState(defaultValue);
720
+ const current = value ?? internal;
721
+ const [hover, setHover] = React.useState(null);
722
+ const display = hover ?? current;
723
+ const interactive = !readOnly && !disabled;
724
+ const select = (next) => {
725
+ if (!interactive) return;
726
+ if (value === void 0) setInternal(next);
727
+ onValueChange?.(next);
728
+ };
729
+ const focusableStar = current > 0 ? current : 1;
730
+ const onKeyDown = (e, star) => {
731
+ if (!interactive) return;
732
+ let next = null;
733
+ switch (e.key) {
734
+ case "ArrowRight":
735
+ case "ArrowUp":
736
+ next = Math.min(max, star + 1);
737
+ break;
738
+ case "ArrowLeft":
739
+ case "ArrowDown":
740
+ next = Math.max(1, star - 1);
741
+ break;
742
+ case "Home":
743
+ next = 1;
744
+ break;
745
+ case "End":
746
+ next = max;
747
+ break;
748
+ default:
749
+ return;
750
+ }
751
+ e.preventDefault();
752
+ select(next);
753
+ };
754
+ return /* @__PURE__ */ jsxs(
755
+ "div",
756
+ {
757
+ ref,
758
+ role: "radiogroup",
759
+ "data-slot": "rating",
760
+ className: cn("ui-rating", className),
761
+ "aria-label": rest["aria-label"] ?? t("ui.rating.ariaLabel"),
762
+ children: [
763
+ Array.from({ length: max }, (_, i) => i + 1).map((star) => /* @__PURE__ */ jsx(
764
+ "button",
765
+ {
766
+ type: "button",
767
+ role: "radio",
768
+ "aria-checked": current === star,
769
+ "aria-label": t("ui.rating.starLabel", { star, max }),
770
+ disabled: disabled || readOnly,
771
+ tabIndex: interactive && star === focusableStar ? 0 : -1,
772
+ className: cn("ui-rating-star", star <= display && "ui-rating-star-filled"),
773
+ onMouseEnter: () => interactive && setHover(star),
774
+ onMouseLeave: () => setHover(null),
775
+ onClick: () => select(star),
776
+ onKeyDown: (e) => onKeyDown(e, star),
777
+ children: /* @__PURE__ */ jsx(Star, { "aria-hidden": "true" })
778
+ },
779
+ star
780
+ )),
781
+ name ? /* @__PURE__ */ jsx("input", { type: "hidden", name, value: current }) : null
782
+ ]
783
+ }
784
+ );
785
+ }
786
+ );
787
+ Rating.displayName = "Rating";
788
+ var TagInput = React.forwardRef(
789
+ ({
790
+ value,
791
+ defaultValue = [],
792
+ onValueChange,
793
+ placeholder,
794
+ disabled,
795
+ name,
796
+ className,
797
+ id,
798
+ "aria-label": ariaLabel
799
+ }, ref) => {
800
+ const { t } = useTranslation();
801
+ const [internal, setInternal] = React.useState(defaultValue);
802
+ const tags = value ?? internal;
803
+ const [draft, setDraft] = React.useState("");
804
+ const commit = (next) => {
805
+ if (value === void 0) setInternal(next);
806
+ onValueChange?.(next);
807
+ };
808
+ const add = (raw) => {
809
+ const tag = raw.trim();
810
+ if (!tag || tags.includes(tag)) return;
811
+ commit([...tags, tag]);
812
+ };
813
+ const removeAt = (i) => commit(tags.filter((_, idx) => idx !== i));
814
+ const onKeyDown = (e) => {
815
+ if (e.key === "Enter" || e.key === ",") {
816
+ e.preventDefault();
817
+ add(draft);
818
+ setDraft("");
819
+ } else if (e.key === "Backspace" && draft === "" && tags.length > 0) {
820
+ removeAt(tags.length - 1);
821
+ }
822
+ };
823
+ return /* @__PURE__ */ jsxs(
824
+ "div",
825
+ {
826
+ "data-slot": "tag-input",
827
+ className: cn("ui-tag-input", disabled && "ui-tag-input-disabled", className),
828
+ children: [
829
+ tags.length > 0 ? /* @__PURE__ */ jsx("ul", { role: "list", className: "ui-tag-input-list", "data-slot": "tag-input-list", children: tags.map((tag, i) => /* @__PURE__ */ jsxs(
830
+ "li",
831
+ {
832
+ role: "listitem",
833
+ className: "ui-tag-input-chip",
834
+ "data-slot": "tag-input-chip",
835
+ children: [
836
+ tag,
837
+ !disabled ? /* @__PURE__ */ jsx(
838
+ "button",
839
+ {
840
+ type: "button",
841
+ className: "ui-tag-input-remove",
842
+ "aria-label": t("ui.tagInput.removeTag", { tag }),
843
+ onClick: () => removeAt(i),
844
+ children: /* @__PURE__ */ jsx(X, { "aria-hidden": "true" })
845
+ }
846
+ ) : null
847
+ ]
848
+ },
849
+ tag
850
+ )) }) : null,
851
+ /* @__PURE__ */ jsx(
852
+ "input",
853
+ {
854
+ ref,
855
+ id,
856
+ type: "text",
857
+ className: "ui-tag-input-field",
858
+ value: draft,
859
+ placeholder: tags.length === 0 ? placeholder : void 0,
860
+ disabled,
861
+ "aria-label": ariaLabel ?? t("ui.tagInput.inputLabel"),
862
+ onChange: (e) => setDraft(e.target.value),
863
+ onKeyDown,
864
+ onBlur: () => {
865
+ if (draft.trim()) {
866
+ add(draft);
867
+ setDraft("");
868
+ }
869
+ }
870
+ }
871
+ ),
872
+ /* @__PURE__ */ jsx("span", { "aria-live": "polite", className: "sr-only", "data-slot": "tag-input-status", children: t("ui.tagInput.tagCount", { count: tags.length }) }),
873
+ name ? /* @__PURE__ */ jsx("input", { type: "hidden", name, value: tags.join(",") }) : null
874
+ ]
875
+ }
876
+ );
877
+ }
878
+ );
879
+ TagInput.displayName = "TagInput";
880
+
881
+ export { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot, MonthPicker, MonthRangePicker, NumberInput, PasswordInput, Rating, TagInput };