@godxjp/ui 13.7.0 → 13.9.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 (122) hide show
  1. package/dist/app/index.js +4 -4
  2. package/dist/{checkbox-ChJ2PyIz.d.ts → checkbox-bBfLARyF.d.ts} +1 -1
  3. package/dist/{chunk-CSTUENE6.js → chunk-2JOJYHG2.js} +4 -4
  4. package/dist/{chunk-TNFAUPEV.js → chunk-3UTHNXKB.js} +4 -4
  5. package/dist/{chunk-IRPZJKLU.js → chunk-4FGE5XVC.js} +10 -0
  6. package/dist/{chunk-C7W5BO7R.js → chunk-5732TWQF.js} +22 -1
  7. package/dist/{chunk-IMERLXSC.js → chunk-5LTW2LWF.js} +6 -1
  8. package/dist/{chunk-SXECWZZV.js → chunk-6SZKFJYY.js} +8 -8
  9. package/dist/{chunk-QMGNMDQG.js → chunk-76YSDJDP.js} +5 -5
  10. package/dist/chunk-7PBKPDBJ.js +881 -0
  11. package/dist/{chunk-QTEPKNQE.js → chunk-B6S6LTWT.js} +1 -1
  12. package/dist/{chunk-GOJQHYED.js → chunk-DBVZ73ML.js} +9 -5
  13. package/dist/{chunk-I472SSN2.js → chunk-E3Z3XCQR.js} +4 -3
  14. package/dist/{chunk-ZYJRZLMZ.js → chunk-HIEGUYPP.js} +1 -1
  15. package/dist/{chunk-W3AS2GEC.js → chunk-HNL7CJFO.js} +5 -5
  16. package/dist/{chunk-76S3TQFH.js → chunk-IAJOP2Z3.js} +31 -13
  17. package/dist/{chunk-RBXVCWXL.js → chunk-IESBK6XZ.js} +2 -2
  18. package/dist/{chunk-LFO5BJ4H.js → chunk-ION2KFXK.js} +2 -2
  19. package/dist/{chunk-QPRBPE4S.js → chunk-IRBI2EZ2.js} +4 -4
  20. package/dist/{chunk-3KPEZ5CF.js → chunk-J2DEWIYY.js} +1 -1
  21. package/dist/{chunk-6AIYMYSG.js → chunk-K24AQV4R.js} +1 -1
  22. package/dist/{chunk-B23A5CPA.js → chunk-KZ7ZDNUN.js} +3 -3
  23. package/dist/{chunk-6EKAEN6T.js → chunk-MSRJC3RT.js} +3 -3
  24. package/dist/{chunk-O3F3KF2B.js → chunk-N2WBUCVY.js} +4 -4
  25. package/dist/{chunk-MUUO7SPN.js → chunk-NU2SOVTU.js} +9 -3
  26. package/dist/{chunk-B7R7DHZF.js → chunk-NZBAFFWP.js} +1 -1
  27. package/dist/{chunk-ZOMIELKI.js → chunk-Q3KUZG4P.js} +1 -1
  28. package/dist/{chunk-LTKHPOE3.js → chunk-QROC2RMH.js} +2 -2
  29. package/dist/{chunk-P5KPCT6R.js → chunk-QVBOICFU.js} +2 -0
  30. package/dist/{chunk-3JQDPNTA.js → chunk-R2TXLS7S.js} +1 -1
  31. package/dist/{chunk-JXGDDD46.js → chunk-TRBD4RXJ.js} +4 -4
  32. package/dist/{chunk-KW54IV4X.js → chunk-UT6DAJUL.js} +3 -3
  33. package/dist/{chunk-2FJ5TMQU.js → chunk-X6AJ5HEI.js} +1 -1
  34. package/dist/{chunk-5L77S5CL.js → chunk-XBFUROAY.js} +1 -1
  35. package/dist/chunk-YXVJFYQE.js +191 -0
  36. package/dist/components/admin/index.d.ts +4 -4
  37. package/dist/components/admin/index.js +30 -30
  38. package/dist/components/data-display/badge.js +3 -3
  39. package/dist/components/data-display/card.d.ts +6 -4
  40. package/dist/components/data-display/card.js +1 -1
  41. package/dist/components/data-display/carousel.js +3 -3
  42. package/dist/components/data-display/index.js +11 -11
  43. package/dist/components/data-display/scroll-area.js +1 -1
  44. package/dist/components/data-entry/calendar.d.ts +2 -2
  45. package/dist/components/data-entry/calendar.js +4 -4
  46. package/dist/components/data-entry/cascader.d.ts +2 -2
  47. package/dist/components/data-entry/cascader.js +5 -5
  48. package/dist/components/data-entry/checkbox.d.ts +3 -3
  49. package/dist/components/data-entry/color-picker.d.ts +2 -2
  50. package/dist/components/data-entry/color-picker.js +4 -4
  51. package/dist/components/data-entry/date-picker.d.ts +2 -2
  52. package/dist/components/data-entry/date-picker.js +6 -6
  53. package/dist/components/data-entry/date-range-picker.d.ts +7 -5
  54. package/dist/components/data-entry/date-range-picker.js +5 -6
  55. package/dist/components/data-entry/index.d.ts +25 -7
  56. package/dist/components/data-entry/index.js +25 -25
  57. package/dist/components/data-entry/input.js +3 -3
  58. package/dist/components/data-entry/radio.d.ts +2 -2
  59. package/dist/components/data-entry/select.d.ts +2 -2
  60. package/dist/components/data-entry/select.js +5 -5
  61. package/dist/components/data-entry/slider.d.ts +2 -2
  62. package/dist/components/data-entry/switch.d.ts +2 -2
  63. package/dist/components/data-entry/textarea.js +3 -3
  64. package/dist/components/data-entry/time-input.js +2 -2
  65. package/dist/components/data-entry/time-picker.d.ts +2 -2
  66. package/dist/components/data-entry/time-picker.js +5 -5
  67. package/dist/components/data-entry/transfer.d.ts +3 -3
  68. package/dist/components/data-entry/transfer.js +7 -7
  69. package/dist/components/data-entry/tree-select.d.ts +2 -2
  70. package/dist/components/data-entry/tree-select.js +6 -6
  71. package/dist/components/data-entry/upload.d.ts +3 -3
  72. package/dist/components/data-entry/upload.js +7 -7
  73. package/dist/components/data-grid/index.js +9 -9
  74. package/dist/components/feedback/alert.js +5 -5
  75. package/dist/components/feedback/dialog.js +5 -5
  76. package/dist/components/feedback/index.js +10 -10
  77. package/dist/components/feedback/sheet.d.ts +1 -1
  78. package/dist/components/feedback/sheet.js +1 -1
  79. package/dist/components/general/button.js +3 -3
  80. package/dist/components/general/index.js +3 -3
  81. package/dist/components/layout/index.d.ts +4 -4
  82. package/dist/components/layout/index.js +5 -5
  83. package/dist/components/navigation/index.js +9 -9
  84. package/dist/components/navigation/pagination.js +6 -6
  85. package/dist/components/navigation/steps.js +3 -3
  86. package/dist/components/query/index.js +5 -5
  87. package/dist/components/ui/index.d.ts +3 -3
  88. package/dist/components/ui/index.js +32 -32
  89. package/dist/{data-entry.prop-CyiZuEI8.d.ts → data-entry.prop-BSUaseKh.d.ts} +41 -3
  90. package/dist/{flex-X2EFU1d5.d.ts → flex-1Cy46L0M.d.ts} +1 -1
  91. package/dist/form/index.js +1 -1
  92. package/dist/i18n/index.d.ts +6 -0
  93. package/dist/i18n/index.js +2 -2
  94. package/dist/index.d.ts +4 -4
  95. package/dist/index.js +43 -43
  96. package/dist/{layout.prop-D2be7gOo.d.ts → layout.prop-fCz-Vlie.d.ts} +2 -0
  97. package/dist/lib/datetime/index.js +1 -1
  98. package/dist/props/components/index.d.ts +2 -2
  99. package/dist/props/index.d.ts +2 -2
  100. package/dist/props/index.js +1 -1
  101. package/dist/props/registry.d.ts +10 -0
  102. package/dist/props/registry.js +1 -1
  103. package/dist/{search-input--ui38eQV.d.ts → search-input-D4v1JGOA.d.ts} +1 -1
  104. package/dist/styles/alert-layout.css +2 -2
  105. package/dist/styles/card-layout.css +10 -10
  106. package/dist/styles/control.css +8 -8
  107. package/dist/styles/data-display-layout.css +7 -7
  108. package/dist/styles/data-entry-layout.css +2 -2
  109. package/dist/styles/dialog-layout.css +1 -1
  110. package/dist/styles/form-layout.css +5 -5
  111. package/dist/styles/index.css +1 -1
  112. package/dist/styles/layout.css +10 -10
  113. package/dist/styles/navigation-layout.css +5 -5
  114. package/dist/styles/shell-layout.css +10 -10
  115. package/dist/styles/table-layout.css +6 -6
  116. package/dist/tokens/base.css +1 -0
  117. package/dist/tokens/components/card.css +4 -0
  118. package/dist/tokens/components/form.css +11 -0
  119. package/dist/tokens/semantic/layout.css +11 -0
  120. package/package.json +5 -3
  121. package/dist/chunk-3ZNSR2NL.js +0 -467
  122. package/dist/chunk-G7A6SUIN.js +0 -183
@@ -28,7 +28,7 @@
28
28
  min-width: 0;
29
29
  flex-direction: column;
30
30
  overflow: hidden;
31
- border-right: 1px solid hsl(var(--border));
31
+ border-inline-end: 1px solid hsl(var(--border));
32
32
  background: hsl(var(--card));
33
33
  color: hsl(var(--foreground));
34
34
  }
@@ -137,7 +137,7 @@
137
137
  background: transparent;
138
138
  color: inherit;
139
139
  font-family: inherit;
140
- text-align: left;
140
+ text-align: start;
141
141
  }
142
142
 
143
143
  .sb-product {
@@ -241,7 +241,7 @@
241
241
  cursor: pointer;
242
242
  font-family: inherit;
243
243
  font-size: var(--font-size-sm);
244
- text-align: left;
244
+ text-align: start;
245
245
  }
246
246
 
247
247
  .sb-nav-item:hover,
@@ -297,7 +297,7 @@
297
297
 
298
298
  /* Collapsible group trigger chevron (rotates when open). */
299
299
  .sb-nav-group-trigger .sb-chevron {
300
- margin-left: auto;
300
+ margin-inline-start: auto;
301
301
  flex: 0 0 auto;
302
302
  opacity: 0.6;
303
303
  transition: transform 150ms ease;
@@ -314,8 +314,8 @@
314
314
  flex-direction: column;
315
315
  gap: 2px;
316
316
  margin: 2px 0 var(--space-1) calc(var(--space-2) + 0.6875rem);
317
- padding-left: var(--space-3);
318
- border-left: 1px solid hsl(var(--border));
317
+ padding-inline-start: var(--space-3);
318
+ border-inline-start: 1px solid hsl(var(--border));
319
319
  }
320
320
 
321
321
  .sb-nav-item--sub {
@@ -383,7 +383,7 @@
383
383
  color: inherit;
384
384
  cursor: pointer;
385
385
  font-family: inherit;
386
- text-align: left;
386
+ text-align: start;
387
387
  }
388
388
 
389
389
  .sb-user:hover,
@@ -416,7 +416,7 @@
416
416
 
417
417
  .sb-user-live {
418
418
  position: absolute;
419
- right: -1px;
419
+ inset-inline-end: -1px;
420
420
  bottom: -1px;
421
421
  width: 0.5625rem;
422
422
  height: 0.5625rem;
@@ -572,7 +572,7 @@
572
572
  .kbd {
573
573
  display: inline-flex;
574
574
  align-items: center;
575
- margin-left: auto;
575
+ margin-inline-start: auto;
576
576
  padding: 1px 5px;
577
577
  border: 1px solid hsl(var(--border));
578
578
  border-bottom-width: 2px;
@@ -617,7 +617,7 @@
617
617
  .tb-bell-dot {
618
618
  position: absolute;
619
619
  top: 0.35rem;
620
- right: 0.35rem;
620
+ inset-inline-end: 0.35rem;
621
621
  width: 0.375rem;
622
622
  height: 0.375rem;
623
623
  border-radius: var(--radius-pill);
@@ -6,7 +6,7 @@
6
6
  [data-slot="table-head"] {
7
7
  height: var(--table-row-height);
8
8
  padding: var(--table-cell-padding-y) var(--table-cell-space-x);
9
- text-align: left;
9
+ text-align: start;
10
10
  vertical-align: middle;
11
11
  background: hsl(var(--secondary));
12
12
  font-size: var(--font-size-xs);
@@ -16,7 +16,7 @@
16
16
  }
17
17
 
18
18
  [data-slot="table-head"]:has([role="checkbox"]) {
19
- padding-right: 0;
19
+ padding-inline-end: 0;
20
20
  }
21
21
 
22
22
  /* A column with an empty header (icon / action column) shouldn't paint the
@@ -37,7 +37,7 @@
37
37
  }
38
38
 
39
39
  [data-slot="table-cell"]:has([role="checkbox"]) {
40
- padding-right: 0;
40
+ padding-inline-end: 0;
41
41
  }
42
42
 
43
43
  .ui-data-table-toolbar {
@@ -125,7 +125,7 @@
125
125
  content: "";
126
126
  position: absolute;
127
127
  top: 0;
128
- right: var(--space-page-active-x);
128
+ inset-inline-end: var(--space-page-active-x);
129
129
  bottom: 0;
130
130
  z-index: 20;
131
131
  width: 1.75rem;
@@ -139,7 +139,7 @@
139
139
  }
140
140
 
141
141
  [data-slot="card-content"][data-flush] .ui-data-table-scroll::after {
142
- right: 0;
142
+ inset-inline-end: 0;
143
143
  }
144
144
 
145
145
  [data-slot="card-content"][data-flush] .ui-data-table-surface {
@@ -154,7 +154,7 @@
154
154
  }
155
155
 
156
156
  .ui-data-table-scroll::after {
157
- right: 0;
157
+ inset-inline-end: 0;
158
158
  }
159
159
  }
160
160
  }
@@ -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";
@@ -15,6 +15,10 @@
15
15
  --card-border: var(--border);
16
16
  --card-header-background: var(--muted);
17
17
  --card-header-background-alpha: 0.55;
18
+ /* Banded-header divider — tokenised (rule #44) so a service theme can make it
19
+ * dashed / heavier / none without forking CSS. Pair with
20
+ * --card-header-background-alpha: 0 for a quiet borderless-band header. */
21
+ --card-header-border-bottom: 1px solid hsl(var(--card-border));
18
22
  --card-radius: var(--radius);
19
23
  --stat-card-label-font-size: var(--font-size-xs);
20
24
  --stat-card-label-font-weight: var(--font-weight-medium);
@@ -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.9.0",
4
4
  "type": "module",
5
5
  "packageManager": "pnpm@10.29.1",
6
6
  "sideEffects": false,
@@ -266,14 +266,15 @@
266
266
  "test:watch": "vitest",
267
267
  "test:coverage": "vitest run --coverage",
268
268
  "check:example-imports": "node scripts/check-example-imports.mjs",
269
- "verify": "pnpm typecheck && pnpm lint && pnpm format && pnpm build && pnpm preview:build && pnpm check:example-imports && pnpm check:core-isolation && pnpm check:prop-vocabulary && pnpm check:token-tiers && pnpm check:control-sizing && pnpm check:mcp-sync && pnpm check:mcp-orphans && pnpm test",
270
- "verify:release": "pnpm typecheck && pnpm lint && pnpm build && pnpm preview:build && pnpm check:example-imports && pnpm check:core-isolation && pnpm check:prop-vocabulary && pnpm check:token-tiers && pnpm check:control-sizing && pnpm check:mcp-sync && pnpm check:mcp-orphans && pnpm check:mcp-prop-sync && pnpm test",
269
+ "verify": "pnpm typecheck && pnpm lint && pnpm format && pnpm build && pnpm preview:build && pnpm check:example-imports && pnpm check:core-isolation && pnpm check:prop-vocabulary && pnpm check:token-tiers && pnpm check:control-sizing && pnpm check:rtl && pnpm check:mcp-sync && pnpm check:mcp-orphans && pnpm test",
270
+ "verify:release": "pnpm typecheck && pnpm lint && pnpm build && pnpm preview:build && pnpm check:example-imports && pnpm check:core-isolation && pnpm check:prop-vocabulary && pnpm check:token-tiers && pnpm check:control-sizing && pnpm check:rtl && pnpm check:mcp-sync && pnpm check:mcp-orphans && pnpm check:mcp-prop-sync && pnpm test",
271
271
  "check:mcp-sync": "node scripts/check-mcp-sync.mjs",
272
272
  "check:mcp-orphans": "node scripts/check-mcp-orphans.mjs",
273
273
  "check:mcp-prop-sync": "node scripts/check-mcp-prop-sync.mjs",
274
274
  "check:prop-vocabulary": "node scripts/check-prop-vocabulary.mjs",
275
275
  "check:token-tiers": "node scripts/check-token-tiers.mjs",
276
276
  "check:control-sizing": "node scripts/check-control-sizing.mjs",
277
+ "check:rtl": "node scripts/check-rtl.mjs",
277
278
  "check:core-isolation": "node scripts/check-core-isolation.mjs",
278
279
  "release": "node scripts/release.mjs",
279
280
  "preview": "node preview/scripts/kill-port.mjs && vite --config preview/vite.config.ts --port 6008 --strictPort",
@@ -281,6 +282,7 @@
281
282
  "docs:sync-primitives": "node preview/scripts/sync-primitive-docs.mjs && prettier --write \"docs/primitives/**/*.tsx\"",
282
283
  "audit": "node scripts/ui-audit.mjs",
283
284
  "build": "tsup && node scripts/copy-styles.mjs",
285
+ "dev": "node scripts/dev.mjs",
284
286
  "prepublishOnly": "pnpm run build"
285
287
  },
286
288
  "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 };