@godxjp/ui 13.7.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 (101) hide show
  1. package/dist/app/index.js +4 -4
  2. package/dist/{checkbox-ChJ2PyIz.d.ts → checkbox-BpnDa6_M.d.ts} +1 -1
  3. package/dist/{chunk-KW54IV4X.js → chunk-2MRAHKOD.js} +3 -3
  4. package/dist/{chunk-QMGNMDQG.js → chunk-3VFNWVR7.js} +5 -5
  5. package/dist/{chunk-IRPZJKLU.js → chunk-4FGE5XVC.js} +10 -0
  6. package/dist/{chunk-SXECWZZV.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-CSTUENE6.js → chunk-65RWSIZF.js} +4 -4
  10. package/dist/{chunk-JXGDDD46.js → chunk-A3J2G2ZA.js} +2 -2
  11. package/dist/{chunk-QTEPKNQE.js → chunk-B6S6LTWT.js} +1 -1
  12. package/dist/{chunk-GOJQHYED.js → chunk-BJTNKNMY.js} +9 -5
  13. package/dist/{chunk-6EKAEN6T.js → chunk-D4JX6O2W.js} +3 -3
  14. package/dist/{chunk-W3AS2GEC.js → chunk-FLY23H4G.js} +2 -2
  15. package/dist/{chunk-ZYJRZLMZ.js → chunk-HIEGUYPP.js} +1 -1
  16. package/dist/{chunk-TNFAUPEV.js → chunk-HRW7JDUJ.js} +4 -4
  17. package/dist/{chunk-O3F3KF2B.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-B23A5CPA.js → chunk-L6NMIUKC.js} +2 -2
  22. package/dist/{chunk-RBXVCWXL.js → chunk-LVLPNKJP.js} +2 -2
  23. package/dist/{chunk-MUUO7SPN.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-QPRBPE4S.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-5L77S5CL.js → chunk-XBFUROAY.js} +1 -1
  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 +8 -8
  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 +23 -23
  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 +8 -8
  72. package/dist/components/general/button.js +3 -3
  73. package/dist/components/general/index.js +3 -3
  74. package/dist/components/layout/index.js +4 -4
  75. package/dist/components/navigation/index.js +9 -9
  76. package/dist/components/navigation/pagination.js +6 -6
  77. package/dist/components/navigation/steps.js +3 -3
  78. package/dist/components/query/index.js +5 -5
  79. package/dist/components/ui/index.d.ts +2 -2
  80. package/dist/components/ui/index.js +25 -25
  81. package/dist/{data-entry.prop-CyiZuEI8.d.ts → data-entry.prop-C9QWj5Kl.d.ts} +40 -2
  82. package/dist/form/index.js +1 -1
  83. package/dist/i18n/index.d.ts +6 -0
  84. package/dist/i18n/index.js +2 -2
  85. package/dist/index.d.ts +2 -2
  86. package/dist/index.js +36 -36
  87. package/dist/lib/datetime/index.js +1 -1
  88. package/dist/props/components/index.d.ts +1 -1
  89. package/dist/props/index.d.ts +1 -1
  90. package/dist/props/index.js +2 -2
  91. package/dist/props/registry.d.ts +10 -0
  92. package/dist/props/registry.js +1 -1
  93. package/dist/{search-input--ui38eQV.d.ts → search-input-d1g8eW9z.d.ts} +1 -1
  94. package/dist/styles/form-layout.css +5 -5
  95. package/dist/styles/layout.css +2 -2
  96. package/dist/tokens/base.css +1 -0
  97. package/dist/tokens/components/form.css +11 -0
  98. package/dist/tokens/semantic/layout.css +11 -0
  99. package/package.json +2 -1
  100. package/dist/chunk-3ZNSR2NL.js +0 -467
  101. package/dist/chunk-G7A6SUIN.js +0 -183
@@ -57,7 +57,7 @@
57
57
  ) {
58
58
  grid-template-columns: var(--form-label-width, max-content) minmax(0, 1fr);
59
59
  align-items: start;
60
- column-gap: var(--space-4);
60
+ column-gap: var(--form-label-gap);
61
61
  row-gap: var(--space-stack-xs);
62
62
  }
63
63
  .ui-form-field[data-collapse-below="false"]:is([data-layout="horizontal"], [data-layout="inline"])
@@ -80,7 +80,7 @@
80
80
  ) {
81
81
  grid-template-columns: var(--form-label-width, max-content) minmax(0, 1fr);
82
82
  align-items: start;
83
- column-gap: var(--space-4);
83
+ column-gap: var(--form-label-gap);
84
84
  row-gap: var(--space-stack-xs);
85
85
  }
86
86
  .ui-form-field[data-collapse-below="sm"]:is([data-layout="horizontal"], [data-layout="inline"])
@@ -104,7 +104,7 @@
104
104
  ) {
105
105
  grid-template-columns: var(--form-label-width, max-content) minmax(0, 1fr);
106
106
  align-items: start;
107
- column-gap: var(--space-4);
107
+ column-gap: var(--form-label-gap);
108
108
  row-gap: var(--space-stack-xs);
109
109
  }
110
110
  .ui-form-field[data-collapse-below="md"]:is([data-layout="horizontal"], [data-layout="inline"])
@@ -128,7 +128,7 @@
128
128
  ) {
129
129
  grid-template-columns: var(--form-label-width, max-content) minmax(0, 1fr);
130
130
  align-items: start;
131
- column-gap: var(--space-4);
131
+ column-gap: var(--form-label-gap);
132
132
  row-gap: var(--space-stack-xs);
133
133
  }
134
134
  .ui-form-field[data-collapse-below="lg"]:is([data-layout="horizontal"], [data-layout="inline"])
@@ -152,7 +152,7 @@
152
152
  ) {
153
153
  grid-template-columns: var(--form-label-width, max-content) minmax(0, 1fr);
154
154
  align-items: start;
155
- column-gap: var(--space-4);
155
+ column-gap: var(--form-label-gap);
156
156
  row-gap: var(--space-stack-xs);
157
157
  }
158
158
  .ui-form-field[data-collapse-below="xl"]:is([data-layout="horizontal"], [data-layout="inline"])
@@ -312,8 +312,8 @@
312
312
  display: flex;
313
313
  flex-direction: column;
314
314
  gap: var(--space-stack-sm);
315
- padding-bottom: var(--space-stack-md);
316
- border-bottom: 1px solid hsl(var(--border));
315
+ padding-bottom: var(--page-header-pad-bottom);
316
+ border-bottom: var(--page-header-divider);
317
317
  }
318
318
 
319
319
  .ui-page-header-row {
@@ -6,6 +6,7 @@
6
6
  @import "./foundation.css";
7
7
  @import "./semantic/layout.css";
8
8
  @import "./components/control.css";
9
+ @import "./components/form.css";
9
10
  @import "./components/card.css";
10
11
  @import "./components/table.css";
11
12
  @import "./components/feedback.css";
@@ -0,0 +1,11 @@
1
+ /* Form layout tokens: horizontal-layout label column geometry. */
2
+
3
+ :root {
4
+ /* Width of the label column in horizontal/inline layout. A service theme sets
5
+ * this once (e.g. 110px) to align every form to its design grid; the Form/
6
+ * FormField `labelWidth` prop overrides per form/field. */
7
+ --form-label-width: max-content;
8
+
9
+ /* Column gap between the label and its control in horizontal/inline layout. */
10
+ --form-label-gap: var(--space-4); /* 16px */
11
+ }
@@ -22,6 +22,17 @@
22
22
  --space-page-active-y: var(--space-page-y);
23
23
  --space-section-active: var(--space-section);
24
24
 
25
+ /* Full-bleed rule under the PageContainer header. OFF by default — the title/body
26
+ * gap already separates them; a divider is a service-theme opt-in:
27
+ * `--page-header-divider: 1px solid hsl(var(--border));` */
28
+ --page-header-divider: none;
29
+
30
+ /* Bottom inset of the page header, sized so title→body distance (this pad + the
31
+ * --space-section-active container gap) EQUALS the page's top padding — the title
32
+ * band reads vertically balanced (24 above / 24 below by default), instead of the
33
+ * old fixed 16px pad that left 24 above vs 32 below. */
34
+ --page-header-pad-bottom: calc(var(--space-page-active-y) - var(--space-section-active));
35
+
25
36
  /* Section "chrome" padding — the SHARED inset for header / body / footer of every overlay-ish
26
37
  * surface (Dialog · Sheet · Drawer · Card). One global knob: a service sets these once instead of
27
38
  * per-component. Vertical is tightened (16 vs the old 24) and density-aware (see density.css);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@godxjp/ui",
3
- "version": "13.7.0",
3
+ "version": "13.8.0",
4
4
  "type": "module",
5
5
  "packageManager": "pnpm@10.29.1",
6
6
  "sideEffects": false,
@@ -281,6 +281,7 @@
281
281
  "docs:sync-primitives": "node preview/scripts/sync-primitive-docs.mjs && prettier --write \"docs/primitives/**/*.tsx\"",
282
282
  "audit": "node scripts/ui-audit.mjs",
283
283
  "build": "tsup && node scripts/copy-styles.mjs",
284
+ "dev": "node scripts/dev.mjs",
284
285
  "prepublishOnly": "pnpm run build"
285
286
  },
286
287
  "peerDependencies": {
@@ -1,467 +0,0 @@
1
- import { Input } from './chunk-6AIYMYSG.js';
2
- import { Button } from './chunk-5L77S5CL.js';
3
- import { useTranslation } from './chunk-QTEPKNQE.js';
4
- import { cn } from './chunk-U7N2A7A3.js';
5
- import * as React from 'react';
6
- import { ChevronUp, ChevronDown, EyeOff, Eye, Minus, Star, X } from 'lucide-react';
7
- import { jsxs, jsx } from 'react/jsx-runtime';
8
- import { OTPInput, OTPInputContext } from 'input-otp';
9
-
10
- function decimalsOf(n) {
11
- if (!Number.isFinite(n)) return 0;
12
- const s = String(n);
13
- const dot = s.indexOf(".");
14
- return dot === -1 ? 0 : s.length - dot - 1;
15
- }
16
- function clamp(n, min, max) {
17
- let out = n;
18
- if (min != null && out < min) out = min;
19
- if (max != null && out > max) out = max;
20
- return out;
21
- }
22
- function roundTo(n, precision) {
23
- if (!Number.isFinite(n)) return n;
24
- const factor = 10 ** precision;
25
- return Math.round((n + Number.EPSILON) * factor) / factor;
26
- }
27
- var NumberInput = React.forwardRef(
28
- ({
29
- value: controlledValue,
30
- defaultValue = null,
31
- onValueChange,
32
- min,
33
- max,
34
- step = 1,
35
- precision,
36
- disabled,
37
- readOnly,
38
- size = "md",
39
- placeholder,
40
- prefix,
41
- suffix,
42
- name,
43
- id,
44
- className,
45
- "aria-label": ariaLabel,
46
- "aria-labelledby": ariaLabelledby,
47
- "aria-describedby": ariaDescribedby,
48
- "data-testid": dataTestId
49
- }, ref) => {
50
- const { t, locale } = useTranslation();
51
- const isControlled = controlledValue !== void 0;
52
- const [internal, setInternal] = React.useState(defaultValue);
53
- const numericValue = isControlled ? controlledValue ?? null : internal;
54
- const effectivePrecision = precision ?? decimalsOf(step);
55
- const formatter = React.useMemo(
56
- () => new Intl.NumberFormat(locale, {
57
- minimumFractionDigits: 0,
58
- maximumFractionDigits: Math.max(effectivePrecision, 0),
59
- useGrouping: false
60
- }),
61
- [locale, effectivePrecision]
62
- );
63
- const formatAtRest = React.useCallback(
64
- (n) => n == null ? "" : formatter.format(n),
65
- [formatter]
66
- );
67
- const [draft, setDraft] = React.useState(() => formatAtRest(numericValue));
68
- const [focused, setFocused] = React.useState(false);
69
- React.useEffect(() => {
70
- if (focused) return;
71
- setDraft(formatAtRest(numericValue));
72
- }, [numericValue, focused, formatAtRest]);
73
- const commit = React.useCallback(
74
- (next) => {
75
- const normalized = next == null || Number.isNaN(next) ? null : roundTo(clamp(next, min, max), effectivePrecision);
76
- if (!isControlled) setInternal(normalized);
77
- onValueChange?.(normalized);
78
- if (!focused) setDraft(formatAtRest(normalized));
79
- return normalized;
80
- },
81
- [effectivePrecision, focused, formatAtRest, isControlled, max, min, onValueChange]
82
- );
83
- const stepBy = React.useCallback(
84
- (direction, multiplier = 1) => {
85
- const base = numericValue ?? min ?? 0;
86
- const next = base + direction * step * multiplier;
87
- const committed = commit(next);
88
- setDraft(formatAtRest(committed));
89
- },
90
- [commit, formatAtRest, min, numericValue, step]
91
- );
92
- const atMin = min != null && numericValue != null && numericValue <= min;
93
- const atMax = max != null && numericValue != null && numericValue >= max;
94
- const interactive = !disabled && !readOnly;
95
- const handleChange = (event) => {
96
- const raw = event.target.value;
97
- setDraft(raw);
98
- if (readOnly) return;
99
- const trimmed = raw.trim();
100
- if (trimmed === "" || trimmed === "-") {
101
- if (trimmed === "") commit(null);
102
- return;
103
- }
104
- const parsed = Number(trimmed);
105
- if (Number.isNaN(parsed)) return;
106
- if (!isControlled) setInternal(parsed);
107
- onValueChange?.(parsed);
108
- };
109
- const handleBlur = () => {
110
- setFocused(false);
111
- const trimmed = draft.trim();
112
- if (trimmed === "" || trimmed === "-") {
113
- commit(null);
114
- setDraft(formatAtRest(null));
115
- return;
116
- }
117
- const parsed = Number(trimmed);
118
- const committed = commit(Number.isNaN(parsed) ? null : parsed);
119
- setDraft(formatAtRest(committed));
120
- };
121
- const handleKeyDown = (event) => {
122
- if (!interactive) return;
123
- if (event.key === "ArrowUp") {
124
- event.preventDefault();
125
- stepBy(1, event.shiftKey ? 10 : 1);
126
- } else if (event.key === "ArrowDown") {
127
- event.preventDefault();
128
- stepBy(-1, event.shiftKey ? 10 : 1);
129
- } else if (event.key === "Enter") {
130
- const trimmed = draft.trim();
131
- const parsed = Number(trimmed);
132
- const committed = commit(trimmed === "" || Number.isNaN(parsed) ? null : parsed);
133
- setDraft(formatAtRest(committed));
134
- }
135
- };
136
- return /* @__PURE__ */ jsxs("div", { "data-slot": "number-input", "data-size": size, className: cn("ui-number-input", className), children: [
137
- prefix != null ? /* @__PURE__ */ jsx(
138
- "span",
139
- {
140
- "data-slot": "number-input-prefix",
141
- className: "ui-number-input-affix",
142
- "aria-hidden": "true",
143
- children: prefix
144
- }
145
- ) : null,
146
- /* @__PURE__ */ jsx(
147
- Input,
148
- {
149
- ref,
150
- id,
151
- name,
152
- type: "text",
153
- inputMode: "decimal",
154
- autoComplete: "off",
155
- role: "spinbutton",
156
- "data-slot": "number-input-field",
157
- "data-testid": dataTestId,
158
- className: "ui-number-input-field",
159
- value: draft,
160
- placeholder,
161
- disabled,
162
- readOnly,
163
- "aria-label": ariaLabel,
164
- "aria-labelledby": ariaLabelledby,
165
- "aria-describedby": ariaDescribedby,
166
- "aria-valuenow": numericValue ?? void 0,
167
- "aria-valuemin": min,
168
- "aria-valuemax": max,
169
- "aria-valuetext": numericValue != null ? formatAtRest(numericValue) : void 0,
170
- onChange: handleChange,
171
- onFocus: () => setFocused(true),
172
- onBlur: handleBlur,
173
- onKeyDown: handleKeyDown
174
- }
175
- ),
176
- suffix != null ? /* @__PURE__ */ jsx(
177
- "span",
178
- {
179
- "data-slot": "number-input-suffix",
180
- className: "ui-number-input-affix",
181
- "aria-hidden": "true",
182
- children: suffix
183
- }
184
- ) : null,
185
- /* @__PURE__ */ jsxs("span", { "data-slot": "number-input-steppers", className: "ui-number-input-steppers", children: [
186
- /* @__PURE__ */ jsx(
187
- Button,
188
- {
189
- type: "button",
190
- variant: "outline",
191
- size: "icon-xs",
192
- className: "ui-number-input-step ui-number-input-step-up",
193
- tabIndex: -1,
194
- disabled: !interactive || atMax,
195
- "aria-label": t("ui.numberInput.increment"),
196
- onClick: () => stepBy(1),
197
- children: /* @__PURE__ */ jsx(ChevronUp, { "aria-hidden": "true" })
198
- }
199
- ),
200
- /* @__PURE__ */ jsx(
201
- Button,
202
- {
203
- type: "button",
204
- variant: "outline",
205
- size: "icon-xs",
206
- className: "ui-number-input-step ui-number-input-step-down",
207
- tabIndex: -1,
208
- disabled: !interactive || atMin,
209
- "aria-label": t("ui.numberInput.decrement"),
210
- onClick: () => stepBy(-1),
211
- children: /* @__PURE__ */ jsx(ChevronDown, { "aria-hidden": "true" })
212
- }
213
- )
214
- ] })
215
- ] });
216
- }
217
- );
218
- NumberInput.displayName = "NumberInput";
219
- var PasswordInput = React.forwardRef(
220
- ({ className, ...props }, ref) => {
221
- const { t } = useTranslation();
222
- const [visible, setVisible] = React.useState(false);
223
- return /* @__PURE__ */ jsxs("div", { className: "ui-password-input", "data-slot": "password-input", children: [
224
- /* @__PURE__ */ jsx(
225
- Input,
226
- {
227
- ref,
228
- type: visible ? "text" : "password",
229
- className: cn("ui-password-input-field", className),
230
- ...props
231
- }
232
- ),
233
- /* @__PURE__ */ jsx(
234
- "button",
235
- {
236
- type: "button",
237
- className: "ui-password-input-toggle",
238
- onClick: () => setVisible((v) => !v),
239
- "aria-label": visible ? t("ui.passwordInput.hide") : t("ui.passwordInput.show"),
240
- "aria-pressed": visible,
241
- children: visible ? /* @__PURE__ */ jsx(EyeOff, { "aria-hidden": "true" }) : /* @__PURE__ */ jsx(Eye, { "aria-hidden": "true" })
242
- }
243
- )
244
- ] });
245
- }
246
- );
247
- PasswordInput.displayName = "PasswordInput";
248
- var InputOTP = React.forwardRef(({ className, containerClassName, ...props }, ref) => /* @__PURE__ */ jsx(
249
- OTPInput,
250
- {
251
- ref,
252
- "data-slot": "input-otp",
253
- containerClassName: cn("ui-otp-container", containerClassName),
254
- className: cn("ui-otp-input", className),
255
- ...props
256
- }
257
- ));
258
- InputOTP.displayName = "InputOTP";
259
- var InputOTPGroup = React.forwardRef(
260
- ({ className, ...props }, ref) => /* @__PURE__ */ jsx(
261
- "div",
262
- {
263
- ref,
264
- "data-slot": "input-otp-group",
265
- className: cn("ui-otp-group", className),
266
- ...props
267
- }
268
- )
269
- );
270
- InputOTPGroup.displayName = "InputOTPGroup";
271
- var InputOTPSlot = React.forwardRef(({ index, className, ...props }, ref) => {
272
- const context = React.useContext(OTPInputContext);
273
- const slot = context.slots[index] ?? { char: null, hasFakeCaret: false, isActive: false };
274
- return /* @__PURE__ */ jsxs(
275
- "div",
276
- {
277
- ref,
278
- "data-slot": "input-otp-slot",
279
- "data-active": slot.isActive || void 0,
280
- className: cn("ui-otp-slot", className),
281
- ...props,
282
- children: [
283
- slot.char,
284
- slot.hasFakeCaret ? /* @__PURE__ */ jsx("div", { className: "ui-otp-caret-wrapper", "aria-hidden": "true", children: /* @__PURE__ */ jsx("div", { className: "ui-otp-caret" }) }) : null
285
- ]
286
- }
287
- );
288
- });
289
- InputOTPSlot.displayName = "InputOTPSlot";
290
- 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" }) }));
291
- InputOTPSeparator.displayName = "InputOTPSeparator";
292
- var Rating = React.forwardRef(
293
- ({
294
- value,
295
- defaultValue = 0,
296
- onValueChange,
297
- max = 5,
298
- readOnly,
299
- disabled,
300
- name,
301
- className,
302
- ...rest
303
- }, ref) => {
304
- const { t } = useTranslation();
305
- const [internal, setInternal] = React.useState(defaultValue);
306
- const current = value ?? internal;
307
- const [hover, setHover] = React.useState(null);
308
- const display = hover ?? current;
309
- const interactive = !readOnly && !disabled;
310
- const select = (next) => {
311
- if (!interactive) return;
312
- if (value === void 0) setInternal(next);
313
- onValueChange?.(next);
314
- };
315
- const focusableStar = current > 0 ? current : 1;
316
- const onKeyDown = (e, star) => {
317
- if (!interactive) return;
318
- let next = null;
319
- switch (e.key) {
320
- case "ArrowRight":
321
- case "ArrowUp":
322
- next = Math.min(max, star + 1);
323
- break;
324
- case "ArrowLeft":
325
- case "ArrowDown":
326
- next = Math.max(1, star - 1);
327
- break;
328
- case "Home":
329
- next = 1;
330
- break;
331
- case "End":
332
- next = max;
333
- break;
334
- default:
335
- return;
336
- }
337
- e.preventDefault();
338
- select(next);
339
- };
340
- return /* @__PURE__ */ jsxs(
341
- "div",
342
- {
343
- ref,
344
- role: "radiogroup",
345
- "data-slot": "rating",
346
- className: cn("ui-rating", className),
347
- "aria-label": rest["aria-label"] ?? t("ui.rating.ariaLabel"),
348
- children: [
349
- Array.from({ length: max }, (_, i) => i + 1).map((star) => /* @__PURE__ */ jsx(
350
- "button",
351
- {
352
- type: "button",
353
- role: "radio",
354
- "aria-checked": current === star,
355
- "aria-label": t("ui.rating.starLabel", { star, max }),
356
- disabled: disabled || readOnly,
357
- tabIndex: interactive && star === focusableStar ? 0 : -1,
358
- className: cn("ui-rating-star", star <= display && "ui-rating-star-filled"),
359
- onMouseEnter: () => interactive && setHover(star),
360
- onMouseLeave: () => setHover(null),
361
- onClick: () => select(star),
362
- onKeyDown: (e) => onKeyDown(e, star),
363
- children: /* @__PURE__ */ jsx(Star, { "aria-hidden": "true" })
364
- },
365
- star
366
- )),
367
- name ? /* @__PURE__ */ jsx("input", { type: "hidden", name, value: current }) : null
368
- ]
369
- }
370
- );
371
- }
372
- );
373
- Rating.displayName = "Rating";
374
- var TagInput = React.forwardRef(
375
- ({
376
- value,
377
- defaultValue = [],
378
- onValueChange,
379
- placeholder,
380
- disabled,
381
- name,
382
- className,
383
- id,
384
- "aria-label": ariaLabel
385
- }, ref) => {
386
- const { t } = useTranslation();
387
- const [internal, setInternal] = React.useState(defaultValue);
388
- const tags = value ?? internal;
389
- const [draft, setDraft] = React.useState("");
390
- const commit = (next) => {
391
- if (value === void 0) setInternal(next);
392
- onValueChange?.(next);
393
- };
394
- const add = (raw) => {
395
- const tag = raw.trim();
396
- if (!tag || tags.includes(tag)) return;
397
- commit([...tags, tag]);
398
- };
399
- const removeAt = (i) => commit(tags.filter((_, idx) => idx !== i));
400
- const onKeyDown = (e) => {
401
- if (e.key === "Enter" || e.key === ",") {
402
- e.preventDefault();
403
- add(draft);
404
- setDraft("");
405
- } else if (e.key === "Backspace" && draft === "" && tags.length > 0) {
406
- removeAt(tags.length - 1);
407
- }
408
- };
409
- return /* @__PURE__ */ jsxs(
410
- "div",
411
- {
412
- "data-slot": "tag-input",
413
- className: cn("ui-tag-input", disabled && "ui-tag-input-disabled", className),
414
- children: [
415
- 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(
416
- "li",
417
- {
418
- role: "listitem",
419
- className: "ui-tag-input-chip",
420
- "data-slot": "tag-input-chip",
421
- children: [
422
- tag,
423
- !disabled ? /* @__PURE__ */ jsx(
424
- "button",
425
- {
426
- type: "button",
427
- className: "ui-tag-input-remove",
428
- "aria-label": t("ui.tagInput.removeTag", { tag }),
429
- onClick: () => removeAt(i),
430
- children: /* @__PURE__ */ jsx(X, { "aria-hidden": "true" })
431
- }
432
- ) : null
433
- ]
434
- },
435
- tag
436
- )) }) : null,
437
- /* @__PURE__ */ jsx(
438
- "input",
439
- {
440
- ref,
441
- id,
442
- type: "text",
443
- className: "ui-tag-input-field",
444
- value: draft,
445
- placeholder: tags.length === 0 ? placeholder : void 0,
446
- disabled,
447
- "aria-label": ariaLabel ?? t("ui.tagInput.inputLabel"),
448
- onChange: (e) => setDraft(e.target.value),
449
- onKeyDown,
450
- onBlur: () => {
451
- if (draft.trim()) {
452
- add(draft);
453
- setDraft("");
454
- }
455
- }
456
- }
457
- ),
458
- /* @__PURE__ */ jsx("span", { "aria-live": "polite", className: "sr-only", "data-slot": "tag-input-status", children: t("ui.tagInput.tagCount", { count: tags.length }) }),
459
- name ? /* @__PURE__ */ jsx("input", { type: "hidden", name, value: tags.join(",") }) : null
460
- ]
461
- }
462
- );
463
- }
464
- );
465
- TagInput.displayName = "TagInput";
466
-
467
- export { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot, NumberInput, PasswordInput, Rating, TagInput };