@godxjp/ui 20.0.0 → 20.1.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 (250) hide show
  1. package/README.md +8 -2
  2. package/dist/app/theme-axes.d.ts +2 -0
  3. package/dist/app/theme-axes.js +45 -0
  4. package/dist/components/charts/chart-cartesian.d.ts +2 -1
  5. package/dist/components/charts/chart-cartesian.js +52 -3
  6. package/dist/components/charts/chart-category-axis.d.ts +55 -0
  7. package/dist/components/charts/chart-category-axis.js +93 -0
  8. package/dist/components/charts/chart-frame.d.ts +10 -1
  9. package/dist/components/charts/chart-frame.js +19 -3
  10. package/dist/components/charts/compact-bar-trend.d.ts +1 -1
  11. package/dist/components/charts/compact-bar-trend.js +2 -0
  12. package/dist/components/charts/pie-chart.d.ts +1 -1
  13. package/dist/components/charts/pie-chart.js +12 -1
  14. package/dist/components/charts/recharts-peer.d.ts +49 -0
  15. package/dist/components/charts/recharts-peer.js +45 -0
  16. package/dist/components/data-display/card.d.ts +12 -1
  17. package/dist/components/data-display/card.js +22 -4
  18. package/dist/components/data-display/code-block.js +6 -2
  19. package/dist/components/data-display/data-table.d.ts +39 -4
  20. package/dist/components/data-display/data-table.js +802 -270
  21. package/dist/components/data-display/descriptions.d.ts +27 -6
  22. package/dist/components/data-display/descriptions.js +58 -17
  23. package/dist/components/data-display/index.d.ts +2 -0
  24. package/dist/components/data-display/index.js +2 -0
  25. package/dist/components/data-display/list-row.d.ts +10 -1
  26. package/dist/components/data-display/list-row.js +7 -1
  27. package/dist/components/data-display/popover.d.ts +2 -2
  28. package/dist/components/data-display/popover.js +61 -11
  29. package/dist/components/data-display/range-timeline.d.ts +38 -0
  30. package/dist/components/data-display/range-timeline.js +161 -0
  31. package/dist/components/data-display/scroll-area.js +13 -2
  32. package/dist/components/data-display/service-launcher-card.js +10 -10
  33. package/dist/components/data-display/table.d.ts +11 -3
  34. package/dist/components/data-display/table.js +48 -18
  35. package/dist/components/data-display/tree-list.js +4 -9
  36. package/dist/components/data-entry/calendar.d.ts +2 -2
  37. package/dist/components/data-entry/calendar.js +12 -1
  38. package/dist/components/data-entry/cascader.d.ts +1 -1
  39. package/dist/components/data-entry/cascader.js +188 -24
  40. package/dist/components/data-entry/color-picker.d.ts +1 -1
  41. package/dist/components/data-entry/color-picker.js +17 -5
  42. package/dist/components/data-entry/control-appearance.d.ts +64 -0
  43. package/dist/components/data-entry/control-appearance.js +39 -0
  44. package/dist/components/data-entry/control-surface.d.ts +61 -0
  45. package/dist/components/data-entry/control-surface.js +39 -0
  46. package/dist/components/data-entry/date-picker.d.ts +1 -1
  47. package/dist/components/data-entry/date-picker.js +344 -113
  48. package/dist/components/data-entry/date-range-picker.d.ts +1 -1
  49. package/dist/components/data-entry/date-range-picker.js +278 -140
  50. package/dist/components/data-entry/form-field.d.ts +1 -1
  51. package/dist/components/data-entry/form-field.js +39 -4
  52. package/dist/components/data-entry/form.d.ts +4 -0
  53. package/dist/components/data-entry/form.js +4 -2
  54. package/dist/components/data-entry/index.d.ts +7 -3
  55. package/dist/components/data-entry/index.js +10 -1
  56. package/dist/components/data-entry/input-otp.d.ts +1 -1
  57. package/dist/components/data-entry/input.d.ts +12 -34
  58. package/dist/components/data-entry/input.js +92 -24
  59. package/dist/components/data-entry/month-picker.d.ts +1 -1
  60. package/dist/components/data-entry/month-picker.js +47 -10
  61. package/dist/components/data-entry/month-range-picker.d.ts +1 -1
  62. package/dist/components/data-entry/month-range-picker.js +51 -11
  63. package/dist/components/data-entry/number-input.d.ts +7 -0
  64. package/dist/components/data-entry/number-input.js +147 -98
  65. package/dist/components/data-entry/password-input.d.ts +1 -1
  66. package/dist/components/data-entry/radio.d.ts +1 -1
  67. package/dist/components/data-entry/radio.js +61 -13
  68. package/dist/components/data-entry/search-input.d.ts +1 -1
  69. package/dist/components/data-entry/search-input.js +16 -2
  70. package/dist/components/data-entry/search-select.d.ts +2 -2
  71. package/dist/components/data-entry/search-select.js +209 -78
  72. package/dist/components/data-entry/select.d.ts +22 -4
  73. package/dist/components/data-entry/select.js +215 -163
  74. package/dist/components/data-entry/slider.d.ts +9 -1
  75. package/dist/components/data-entry/slider.js +87 -9
  76. package/dist/components/data-entry/switch.d.ts +3 -0
  77. package/dist/components/data-entry/switch.js +29 -3
  78. package/dist/components/data-entry/textarea.d.ts +14 -56
  79. package/dist/components/data-entry/textarea.js +80 -33
  80. package/dist/components/data-entry/time-picker.d.ts +2 -2
  81. package/dist/components/data-entry/time-picker.js +333 -109
  82. package/dist/components/data-entry/time-range-picker.d.ts +5 -0
  83. package/dist/components/data-entry/time-range-picker.js +89 -0
  84. package/dist/components/data-entry/transfer.d.ts +1 -1
  85. package/dist/components/data-entry/transfer.js +86 -28
  86. package/dist/components/data-entry/tree-select.d.ts +1 -1
  87. package/dist/components/data-entry/tree-select.js +201 -113
  88. package/dist/components/data-entry/tree-utils.js +7 -14
  89. package/dist/components/data-entry/upload-files.d.ts +2 -0
  90. package/dist/components/data-entry/upload-files.js +31 -0
  91. package/dist/components/data-entry/upload-request.d.ts +4 -0
  92. package/dist/components/data-entry/upload-request.js +53 -0
  93. package/dist/components/data-entry/upload-types.d.ts +28 -0
  94. package/dist/components/data-entry/upload-types.js +2 -0
  95. package/dist/components/data-entry/upload.d.ts +2 -2
  96. package/dist/components/data-entry/upload.js +475 -115
  97. package/dist/components/feedback/dialog.js +16 -19
  98. package/dist/components/general/button.d.ts +1 -1
  99. package/dist/components/general/button.js +5 -1
  100. package/dist/components/general/index.d.ts +1 -0
  101. package/dist/components/general/index.js +2 -0
  102. package/dist/components/general/typography.d.ts +3 -0
  103. package/dist/components/general/typography.js +15 -1
  104. package/dist/components/general/visually-hidden.d.ts +3 -0
  105. package/dist/components/general/visually-hidden.js +9 -0
  106. package/dist/components/layout/app-launcher.d.ts +34 -0
  107. package/dist/components/layout/app-launcher.js +228 -0
  108. package/dist/components/layout/app-shell.d.ts +2 -0
  109. package/dist/components/layout/app-shell.js +20 -7
  110. package/dist/components/layout/breadcrumb.d.ts +14 -2
  111. package/dist/components/layout/breadcrumb.js +46 -4
  112. package/dist/components/layout/flex.d.ts +1 -1
  113. package/dist/components/layout/flex.js +23 -2
  114. package/dist/components/layout/index.d.ts +3 -0
  115. package/dist/components/layout/index.js +5 -1
  116. package/dist/components/layout/org-switcher.js +20 -1
  117. package/dist/components/layout/page-container.d.ts +1 -1
  118. package/dist/components/layout/page-container.js +5 -3
  119. package/dist/components/layout/responsive-grid.d.ts +16 -2
  120. package/dist/components/layout/responsive-grid.js +29 -2
  121. package/dist/components/layout/topbar-item.d.ts +3 -0
  122. package/dist/components/layout/topbar-item.js +18 -3
  123. package/dist/components/layout/topbar.d.ts +1 -1
  124. package/dist/components/layout/topbar.js +27 -6
  125. package/dist/components/navigation/dropdown-menu.d.ts +30 -2
  126. package/dist/components/navigation/dropdown-menu.js +68 -11
  127. package/dist/components/navigation/pagination.d.ts +2 -2
  128. package/dist/components/navigation/pagination.js +155 -83
  129. package/dist/components/navigation/steps.d.ts +2 -2
  130. package/dist/components/navigation/steps.js +29 -4
  131. package/dist/components/navigation/tabs.d.ts +5 -33
  132. package/dist/components/navigation/tabs.js +232 -64
  133. package/dist/components/ui/input-otp.d.ts +26 -28
  134. package/dist/components/ui/input-otp.js +50 -7
  135. package/dist/components/ui/password-input.d.ts +36 -2
  136. package/dist/components/ui/password-input.js +27 -7
  137. package/dist/components/ui/rating.d.ts +25 -0
  138. package/dist/components/ui/rating.js +67 -27
  139. package/dist/components/ui/segmented.d.ts +9 -0
  140. package/dist/components/ui/segmented.js +17 -2
  141. package/dist/components/ui/separator.d.ts +4 -0
  142. package/dist/components/ui/separator.js +13 -0
  143. package/dist/components/ui/tag-input.d.ts +45 -0
  144. package/dist/components/ui/tag-input.js +109 -27
  145. package/dist/form/form-context.d.ts +29 -0
  146. package/dist/form/form-context.js +44 -4
  147. package/dist/form/form-field-array.d.ts +22 -0
  148. package/dist/form/form-field-array.js +50 -0
  149. package/dist/form/form-field-control.d.ts +2 -1
  150. package/dist/form/form-field-control.js +116 -40
  151. package/dist/form/form-root.d.ts +2 -1
  152. package/dist/form/form-root.js +142 -23
  153. package/dist/form/index.d.ts +3 -1
  154. package/dist/form/index.js +12 -1
  155. package/dist/i18n/messages/en.json +56 -5
  156. package/dist/i18n/messages/ja.json +56 -5
  157. package/dist/i18n/messages/vi.json +56 -5
  158. package/dist/inertia/index.d.ts +20 -0
  159. package/dist/inertia/index.js +60 -1
  160. package/dist/lib/control-styles.d.ts +25 -3
  161. package/dist/lib/control-styles.js +9 -3
  162. package/dist/lib/datetime/picker-format.d.ts +8 -0
  163. package/dist/lib/datetime/picker-format.js +54 -0
  164. package/dist/props/components/charts.prop.d.ts +21 -0
  165. package/dist/props/components/data-display.prop.d.ts +55 -4
  166. package/dist/props/components/data-entry.prop.d.ts +687 -43
  167. package/dist/props/components/form.prop.d.ts +142 -4
  168. package/dist/props/components/general.prop.d.ts +17 -1
  169. package/dist/props/components/index.d.ts +2 -2
  170. package/dist/props/components/layout.prop.d.ts +163 -6
  171. package/dist/props/components/navigation.prop.d.ts +113 -3
  172. package/dist/props/registry.d.ts +376 -10
  173. package/dist/props/registry.js +493 -9
  174. package/dist/props/vocabulary/data.prop.d.ts +179 -1
  175. package/dist/props/vocabulary/index.d.ts +4 -4
  176. package/dist/props/vocabulary/interaction.prop.d.ts +51 -2
  177. package/dist/props/vocabulary/navigation.prop.d.ts +40 -0
  178. package/dist/props/vocabulary/shared.prop.d.ts +33 -0
  179. package/dist/styles/badge-layout.css +2 -1
  180. package/dist/styles/card-layout.css +26 -4
  181. package/dist/styles/chart-layout.css +15 -0
  182. package/dist/styles/control.css +515 -14
  183. package/dist/styles/data-display-layout.css +241 -6
  184. package/dist/styles/data-entry-layout.css +13 -0
  185. package/dist/styles/layout.css +221 -4
  186. package/dist/styles/navigation-layout.css +227 -0
  187. package/dist/styles/shell-layout.css +229 -5
  188. package/dist/styles/table-layout.css +95 -6
  189. package/dist/styles/text-layout.css +5 -0
  190. package/dist/tokens/base.css +1 -0
  191. package/dist/tokens/components/badge.css +1 -0
  192. package/dist/tokens/components/chart.css +6 -0
  193. package/dist/tokens/components/control.css +79 -2
  194. package/dist/tokens/components/data-display.css +9 -0
  195. package/dist/tokens/components/descriptions.css +11 -0
  196. package/dist/tokens/components/flex.css +8 -0
  197. package/dist/tokens/components/navigation.css +55 -0
  198. package/dist/tokens/components/shell.css +50 -2
  199. package/dist/tokens/components/table.css +14 -0
  200. package/dist/tokens/semantic/layout.css +9 -0
  201. package/docs/CONSUMER-RULES.md +13 -0
  202. package/docs/DESIGN-AUTHORITY.md +43 -6
  203. package/docs/DEVELOPMENT.md +4 -3
  204. package/docs/FORMS.md +151 -6
  205. package/docs/FRAME-COVERAGE-REPORT.md +29 -16
  206. package/docs/README.md +1 -1
  207. package/docs/STANDARDS-vocabulary-tokens.md +1 -1
  208. package/docs/TESTING.md +15 -6
  209. package/docs/charts/cjk-category-axis.tsx +81 -0
  210. package/docs/data-display/card/index.tsx +22 -0
  211. package/docs/data-display/data-table/examples/antd-parity.tsx +257 -0
  212. package/docs/data-display/data-table/index.tsx +23 -0
  213. package/docs/data-display/descriptions.tsx +41 -0
  214. package/docs/data-display/scroll-area.tsx +31 -25
  215. package/docs/data-display/table.tsx +104 -45
  216. package/docs/data-display/timeline.tsx +41 -0
  217. package/docs/data-entry/date-picker.tsx +85 -0
  218. package/docs/data-entry/date-range-picker.tsx +26 -0
  219. package/docs/data-entry/form-dynamic-fields.tsx +199 -0
  220. package/docs/data-entry/form.tsx +74 -4
  221. package/docs/data-entry/input-otp.tsx +24 -0
  222. package/docs/data-entry/input.tsx +46 -1
  223. package/docs/data-entry/month-range-picker.tsx +1 -1
  224. package/docs/data-entry/select.tsx +27 -0
  225. package/docs/data-entry/switch.tsx +41 -0
  226. package/docs/data-entry/textarea.tsx +38 -0
  227. package/docs/data-entry/time-picker.tsx +85 -1
  228. package/docs/data-entry/time-range-picker.tsx +55 -0
  229. package/docs/data-entry/transfer.tsx +17 -0
  230. package/docs/data-entry/upload.tsx +56 -12
  231. package/docs/feedback/tooltip.tsx +1 -1
  232. package/docs/general/activity.tsx +2 -2
  233. package/docs/general/button/index.tsx +14 -1
  234. package/docs/general/typography.tsx +14 -1
  235. package/docs/layout/app-launcher.tsx +151 -0
  236. package/docs/layout/app-shell.tsx +11 -0
  237. package/docs/layout/flex.tsx +50 -0
  238. package/docs/layout/responsive-grid.tsx +21 -1
  239. package/docs/layout/topbar.tsx +5 -9
  240. package/docs/navigation/app-setting-picker.tsx +11 -0
  241. package/docs/navigation/breadcrumb.tsx +48 -0
  242. package/docs/navigation/dropdown-menu.tsx +21 -0
  243. package/docs/navigation/pagination.tsx +52 -0
  244. package/docs/navigation/steps.tsx +37 -0
  245. package/docs/navigation/tabs.tsx +97 -1
  246. package/docs/query/button-refetch.tsx +1 -0
  247. package/package.json +19 -2
  248. package/scripts/_agent-setup.mjs +19 -2
  249. package/scripts/init-guinea-pig.mjs +26 -4
  250. package/scripts/ui-audit.mjs +243 -30
@@ -9,7 +9,12 @@ const Rating = React.forwardRef(
9
9
  value,
10
10
  defaultValue = 0,
11
11
  onValueChange,
12
- max = 5,
12
+ max,
13
+ count,
14
+ allowHalf = false,
15
+ allowClear = false,
16
+ character,
17
+ tooltips,
13
18
  readOnly,
14
19
  disabled,
15
20
  name,
@@ -17,68 +22,103 @@ const Rating = React.forwardRef(
17
22
  ...rest
18
23
  }, ref) => {
19
24
  const { t } = useTranslation();
25
+ const symbols = count ?? max ?? 5;
20
26
  const [internal, setInternal] = React.useState(defaultValue);
21
27
  const current = value ?? internal;
22
28
  const [hover, setHover] = React.useState(null);
23
29
  const display = hover ?? current;
24
30
  const interactive = !readOnly && !disabled;
31
+ const stepSize = allowHalf ? 0.5 : 1;
25
32
  const select = (next) => {
26
33
  if (!interactive) return;
27
- if (value === void 0) setInternal(next);
28
- onValueChange?.(next);
34
+ const resolved = allowClear && next === current ? 0 : next;
35
+ if (value === void 0) setInternal(resolved);
36
+ onValueChange?.(resolved);
29
37
  };
30
- const focusableStar = current > 0 ? current : 1;
38
+ const focusableStar = current > 0 ? Math.ceil(current) : 1;
31
39
  const onKeyDown = (e, star) => {
32
40
  if (!interactive) return;
33
41
  let next;
34
42
  switch (e.key) {
35
43
  case "ArrowRight":
36
44
  case "ArrowUp":
37
- next = Math.min(max, star + 1);
45
+ next = Math.min(symbols, star + stepSize);
38
46
  break;
39
47
  case "ArrowLeft":
40
48
  case "ArrowDown":
41
- next = Math.max(1, star - 1);
49
+ next = Math.max(stepSize, star - stepSize);
42
50
  break;
43
51
  case "Home":
44
- next = 1;
52
+ next = stepSize;
45
53
  break;
46
54
  case "End":
47
- next = max;
55
+ next = symbols;
48
56
  break;
49
57
  default:
50
58
  return;
51
59
  }
52
60
  e.preventDefault();
53
- select(next);
61
+ if (value === void 0) setInternal(next);
62
+ onValueChange?.(next);
63
+ };
64
+ const stepLabel = (star) => {
65
+ const tooltip = tooltips?.[star - 1];
66
+ const base = t("ui.rating.starLabel", { star, max: symbols });
67
+ return tooltip ? `${base}, ${tooltip}` : base;
54
68
  };
69
+ const glyph = (star) => typeof character === "function" ? character(star) : character ?? /* @__PURE__ */ jsx(Star, { "aria-hidden": "true" });
55
70
  return /* @__PURE__ */ jsxs(
56
71
  "div",
57
72
  {
58
73
  ref,
59
74
  role: "radiogroup",
60
75
  "data-slot": "rating",
76
+ "data-allow-half": allowHalf ? "true" : void 0,
61
77
  className: cn("ui-rating", className),
62
78
  "aria-label": rest["aria-label"] ?? t("ui.rating.ariaLabel"),
63
79
  children: [
64
- Array.from({ length: max }, (_, i) => i + 1).map((star) => /* @__PURE__ */ jsx(
65
- "button",
66
- {
67
- type: "button",
68
- role: "radio",
69
- "aria-checked": current === star,
70
- "aria-label": t("ui.rating.starLabel", { star, max }),
71
- disabled: disabled || readOnly,
72
- tabIndex: interactive && star === focusableStar ? 0 : -1,
73
- className: cn("ui-rating-star", star <= display && "ui-rating-star-filled"),
74
- onMouseEnter: () => interactive && setHover(star),
75
- onMouseLeave: () => setHover(null),
76
- onClick: () => select(star),
77
- onKeyDown: (e) => onKeyDown(e, star),
78
- children: /* @__PURE__ */ jsx(Star, { "aria-hidden": "true" })
79
- },
80
- star
81
- )),
80
+ Array.from({ length: symbols }, (_, i) => i + 1).map((star) => {
81
+ const symbol = /* @__PURE__ */ jsx(
82
+ "button",
83
+ {
84
+ type: "button",
85
+ role: "radio",
86
+ "aria-checked": current === star,
87
+ "aria-label": stepLabel(star),
88
+ disabled: disabled || readOnly,
89
+ tabIndex: interactive && star === focusableStar ? 0 : -1,
90
+ className: cn(
91
+ "ui-rating-star",
92
+ star <= display && "ui-rating-star-filled",
93
+ allowHalf && star - 0.5 === display && "ui-rating-star-half-filled"
94
+ ),
95
+ onMouseEnter: () => interactive && setHover(star),
96
+ onMouseLeave: () => setHover(null),
97
+ onClick: () => select(star),
98
+ onKeyDown: (e) => onKeyDown(e, star),
99
+ children: glyph(star)
100
+ },
101
+ star
102
+ );
103
+ if (!allowHalf) return symbol;
104
+ return /* @__PURE__ */ jsxs("span", { "data-slot": "rating-symbol", className: "ui-rating-symbol", children: [
105
+ /* @__PURE__ */ jsx(
106
+ "button",
107
+ {
108
+ type: "button",
109
+ tabIndex: -1,
110
+ "aria-hidden": "true",
111
+ disabled: disabled || readOnly,
112
+ "data-slot": "rating-half",
113
+ className: "ui-rating-star-half",
114
+ onMouseEnter: () => interactive && setHover(star - 0.5),
115
+ onMouseLeave: () => setHover(null),
116
+ onClick: () => select(star - 0.5)
117
+ }
118
+ ),
119
+ symbol
120
+ ] }, star);
121
+ }),
82
122
  name ? /* @__PURE__ */ jsx("input", { type: "hidden", name, value: current }) : null
83
123
  ]
84
124
  }
@@ -18,6 +18,15 @@ export type SegmentedProp = {
18
18
  /** Uncontrolled initial selection. */
19
19
  defaultValue?: string;
20
20
  onValueChange?: (value: string) => void;
21
+ /**
22
+ * antd `block` — stretch the bar to its container and share the width EQUALLY between the
23
+ * choices, so the selected pill does not resize as the label changes length.
24
+ */
25
+ block?: boolean;
26
+ /** antd `vertical` — stack the choices in a column. Arrow keys follow the axis. */
27
+ vertical?: boolean;
28
+ /** Control height tier: `md` (default), `sm` or `lg` — the same tiers as every other control. */
29
+ size?: "sm" | "md" | "lg";
21
30
  /** Disable the whole group. */
22
31
  disabled?: boolean;
23
32
  /** Form field name — submits the selected value with the form. */
@@ -3,15 +3,30 @@ import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import * as RadioGroupPrimitive from "@radix-ui/react-radio-group";
5
5
  import { cn } from "../../lib/utils.js";
6
- const Segmented = React.forwardRef(function Segmented2({ options, value, defaultValue, onValueChange, disabled, name, id, className, ...props }, ref) {
6
+ const Segmented = React.forwardRef(function Segmented2({
7
+ options,
8
+ value,
9
+ defaultValue,
10
+ onValueChange,
11
+ disabled,
12
+ block = false,
13
+ vertical = false,
14
+ size,
15
+ name,
16
+ id,
17
+ className,
18
+ ...props
19
+ }, ref) {
7
20
  return /* @__PURE__ */ jsx(
8
21
  RadioGroupPrimitive.Root,
9
22
  {
10
23
  ref,
11
24
  id,
12
25
  "data-slot": "segmented",
26
+ "data-block": block ? "true" : void 0,
27
+ "data-size": size,
13
28
  className: cn("ui-segmented", className),
14
- orientation: "horizontal",
29
+ orientation: vertical ? "vertical" : "horizontal",
15
30
  value,
16
31
  defaultValue,
17
32
  onValueChange,
@@ -12,6 +12,10 @@ export type { SeparatorProp, SeparatorProp as SeparatorProps, } from "../../prop
12
12
  * the ref, and we decide what element carries them.
13
13
  */
14
14
  export declare const Separator: React.ForwardRefExoticComponent<Omit<React.HTMLAttributes<HTMLDivElement>, "children"> & {
15
+ labelSize?: "2xs" | "xs" | "sm" | "md";
16
+ space?: import("../../props/index.js").GapProp;
17
+ hideBelow?: import("../../props/index.js").BreakpointProp;
18
+ hideFrom?: import("../../props/index.js").BreakpointProp;
15
19
  orientation?: import("../../props/index.js").OrientationProp;
16
20
  label?: string;
17
21
  labelAlign?: import("../../props/index.js").TextAlignProp;
@@ -1,10 +1,16 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
2
  import * as React from "react";
3
3
  import { Separator as AriaSeparator } from "react-aria-components";
4
+ import { padStepToken } from "../../lib/variants.js";
4
5
  import { cn } from "../../lib/utils.js";
5
6
  const Separator = React.forwardRef(
6
7
  ({
7
8
  className,
9
+ labelSize,
10
+ space,
11
+ hideBelow,
12
+ hideFrom,
13
+ style,
8
14
  orientation = "horizontal",
9
15
  decorative,
10
16
  label,
@@ -28,6 +34,13 @@ const Separator = React.forwardRef(
28
34
  ref,
29
35
  elementType: "div",
30
36
  "data-slot": "separator",
37
+ "data-label-size": labelSize,
38
+ "data-hide-below": hideBelow,
39
+ "data-hide-from": hideFrom,
40
+ style: {
41
+ ...style,
42
+ ...space === void 0 ? void 0 : { marginBlock: padStepToken(space) }
43
+ },
31
44
  "data-orientation": orientation,
32
45
  "data-tone": tone,
33
46
  "data-labelled": labelled ? "" : void 0,
@@ -1,13 +1,58 @@
1
1
  import * as React from "react";
2
+ import type { AllowClearProp, ControlStatusProp, ControlVariantProp, MaxTagCountProp, MaxTagPlaceholderProp, SizeProp } from "../../props/vocabulary/index.js";
3
+ /**
4
+ * TagInput is this library's answer to antd's `Select mode="tags"` — a free-text token field — so
5
+ * it takes the same token-level props antd puts on that mode.
6
+ */
2
7
  export type TagInputProps = {
3
8
  value?: string[];
4
9
  defaultValue?: string[];
5
10
  onValueChange?: (tags: string[]) => void;
6
11
  placeholder?: string;
7
12
  disabled?: boolean;
13
+ /**
14
+ * Read-only: the tags stay visible, selectable and submitted, but nothing can be added or
15
+ * removed — the draft field is read-only, the chip removers and the clear ✕ are withdrawn, and
16
+ * the container reports `aria-readonly`. Mirrors Select's readOnly contract (still focusable and
17
+ * still in the tab order, unlike `disabled`).
18
+ */
19
+ readOnly?: boolean;
8
20
  name?: string;
9
21
  className?: string;
10
22
  id?: string;
11
23
  "aria-label"?: string;
24
+ /** Control height tier (antd `size`) — the shared `--control-height` ladder. */
25
+ size?: SizeProp;
26
+ /** Validation status (antd `status`). `error` also sets `aria-invalid`; `warning` recolours only. */
27
+ status?: ControlStatusProp;
28
+ /** Control surface (antd `variant`). Default `outlined`. */
29
+ variant?: ControlVariantProp;
30
+ /** Hard ceiling on how many tags may be held (antd `maxCount`). Further input is refused. */
31
+ maxCount?: number;
32
+ /**
33
+ * antd `allowClear` — a single ✕ that drops EVERY tag at once. Off by default (antd's own
34
+ * default for a tags field). The object form replaces the icon and/or the accessible label.
35
+ */
36
+ allowClear?: AllowClearProp;
37
+ /** Fired after every tag is dropped through the ✕ (antd `onClear`). */
38
+ onClear?: () => void;
39
+ /** How many chips render before the rest collapse into the overflow node (antd `maxTagCount`). */
40
+ maxTagCount?: MaxTagCountProp;
41
+ /** The node standing in for what `maxTagCount` hid (antd `maxTagPlaceholder`). */
42
+ maxTagPlaceholder?: MaxTagPlaceholderProp;
43
+ /** Render one chip yourself (antd `tagRender`) — receives the value and an `onClose` remover. */
44
+ tagRender?: (props: {
45
+ value: string;
46
+ label: React.ReactNode;
47
+ onClose: () => void;
48
+ index: number;
49
+ disabled: boolean;
50
+ }) => React.ReactNode;
51
+ /**
52
+ * Characters that commit the draft into a tag (antd `tokenSeparators`). Default `[","]`; Enter
53
+ * always commits and is not a separator. Pasting a run containing a separator splits it into
54
+ * several tags, which is antd's behaviour and the reason the prop exists.
55
+ */
56
+ tokenSeparators?: string[];
12
57
  };
13
58
  export declare const TagInput: React.ForwardRefExoticComponent<TagInputProps & React.RefAttributes<HTMLInputElement>>;
@@ -1,9 +1,17 @@
1
1
  "use client";
2
- import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { X } from "lucide-react";
5
5
  import { useTranslation } from "../../i18n/use-translation.js";
6
6
  import { cn } from "../../lib/utils.js";
7
+ import {
8
+ applyMaxTagCount,
9
+ controlSurfaceAttrs,
10
+ resolveAllowClear
11
+ } from "../data-entry/control-surface.js";
12
+ function escapeForCharClass(character) {
13
+ return character.replace(/[\\\]^-]/g, "\\$&");
14
+ }
7
15
  const TagInput = React.forwardRef(
8
16
  ({
9
17
  value,
@@ -11,10 +19,21 @@ const TagInput = React.forwardRef(
11
19
  onValueChange,
12
20
  placeholder,
13
21
  disabled,
22
+ readOnly,
14
23
  name,
15
24
  className,
16
25
  id,
17
- "aria-label": ariaLabel
26
+ "aria-label": ariaLabel,
27
+ size,
28
+ status,
29
+ variant,
30
+ maxCount,
31
+ allowClear,
32
+ onClear,
33
+ maxTagCount,
34
+ maxTagPlaceholder,
35
+ tagRender,
36
+ tokenSeparators = [","]
18
37
  }, ref) => {
19
38
  const { t } = useTranslation();
20
39
  const [internal, setInternal] = React.useState(defaultValue);
@@ -27,11 +46,25 @@ const TagInput = React.forwardRef(
27
46
  const add = (raw) => {
28
47
  const tag = raw.trim();
29
48
  if (!tag || tags.includes(tag)) return;
49
+ if (maxCount !== void 0 && tags.length >= maxCount) return;
30
50
  commit([...tags, tag]);
31
51
  };
52
+ const addAll = (raw) => {
53
+ let next = [...tags];
54
+ const pieces = tokenSeparators.length ? raw.split(new RegExp(`[${tokenSeparators.map(escapeForCharClass).join("")}]`)) : [raw];
55
+ for (const piece of pieces) {
56
+ const tag = piece.trim();
57
+ if (!tag || next.includes(tag)) continue;
58
+ if (maxCount !== void 0 && next.length >= maxCount) break;
59
+ next = [...next, tag];
60
+ }
61
+ if (next.length !== tags.length) commit(next);
62
+ };
32
63
  const removeAt = (i) => commit(tags.filter((_, idx) => idx !== i));
33
64
  const onKeyDown = (e) => {
34
- if (e.key === "Enter" || e.key === ",") {
65
+ if (readOnly) return;
66
+ if (e.nativeEvent.isComposing) return;
67
+ if (e.key === "Enter" || tokenSeparators.includes(e.key)) {
35
68
  e.preventDefault();
36
69
  add(draft);
37
70
  setDraft("");
@@ -39,35 +72,63 @@ const TagInput = React.forwardRef(
39
72
  removeAt(tags.length - 1);
40
73
  }
41
74
  };
75
+ const clearControl = resolveAllowClear(allowClear, false, t("common.clear"));
76
+ const showClear = clearControl.enabled && tags.length > 0 && !disabled && !readOnly;
77
+ const clearAll = () => {
78
+ commit([]);
79
+ onClear?.();
80
+ };
81
+ const chips = tags.map((tag, index) => ({ value: tag, label: tag, index }));
82
+ const {
83
+ visible: visibleChips,
84
+ omitted: omittedChips,
85
+ overflow
86
+ } = applyMaxTagCount(chips, maxTagCount, maxTagPlaceholder);
42
87
  return /* @__PURE__ */ jsxs(
43
88
  "div",
44
89
  {
45
90
  "data-slot": "tag-input",
46
91
  "aria-disabled": disabled || void 0,
47
- className: cn("ui-tag-input", disabled && "ui-tag-input-disabled", className),
92
+ "aria-readonly": readOnly || void 0,
93
+ ...controlSurfaceAttrs({ variant, status, size }),
94
+ "aria-invalid": status === "error" ? true : void 0,
95
+ className: cn(
96
+ "ui-tag-input ui-control-surface",
97
+ disabled && "ui-tag-input-disabled",
98
+ className
99
+ ),
48
100
  children: [
49
- 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(
50
- "li",
51
- {
52
- role: "listitem",
53
- className: "ui-tag-input-chip",
54
- "data-slot": "tag-input-chip",
55
- children: [
56
- tag,
57
- !disabled ? /* @__PURE__ */ jsx(
58
- "button",
59
- {
60
- type: "button",
61
- className: "ui-tag-input-remove",
62
- "aria-label": t("ui.tagInput.removeTag", { tag }),
63
- onClick: () => removeAt(i),
64
- children: /* @__PURE__ */ jsx(X, { "aria-hidden": "true" })
65
- }
66
- ) : null
67
- ]
68
- },
69
- tag
70
- )) }) : null,
101
+ tags.length > 0 ? /* @__PURE__ */ jsxs("ul", { role: "list", className: "ui-tag-input-list", "data-slot": "tag-input-list", children: [
102
+ visibleChips.map((chip) => /* @__PURE__ */ jsx(
103
+ "li",
104
+ {
105
+ role: "listitem",
106
+ className: "ui-tag-input-chip",
107
+ "data-slot": "tag-input-chip",
108
+ children: tagRender ? tagRender({
109
+ value: chip.value,
110
+ label: chip.label,
111
+ index: chip.index,
112
+ disabled: Boolean(disabled) || Boolean(readOnly),
113
+ onClose: () => removeAt(chip.index)
114
+ }) : /* @__PURE__ */ jsxs(Fragment, { children: [
115
+ chip.value,
116
+ !disabled && !readOnly ? /* @__PURE__ */ jsx(
117
+ "button",
118
+ {
119
+ type: "button",
120
+ className: "ui-tag-input-remove",
121
+ "aria-label": t("ui.tagInput.removeTag", { tag: chip.value }),
122
+ onClick: () => removeAt(chip.index),
123
+ children: /* @__PURE__ */ jsx(X, { "aria-hidden": "true" })
124
+ }
125
+ ) : null
126
+ ] })
127
+ },
128
+ chip.value
129
+ )),
130
+ omittedChips.length > 0 ? /* @__PURE__ */ jsx("li", { role: "listitem", className: "ui-tag-input-chip", "data-slot": "tag-input-overflow", children: overflow ?? t("dataEntry.selection.overflow", { count: omittedChips.length }) }) : null
131
+ ] }) : null,
71
132
  /* @__PURE__ */ jsx(
72
133
  "input",
73
134
  {
@@ -76,12 +137,21 @@ const TagInput = React.forwardRef(
76
137
  type: "text",
77
138
  className: "ui-tag-input-field",
78
139
  value: draft,
79
- placeholder: tags.length === 0 ? placeholder : void 0,
140
+ placeholder: tags.length === 0 && !readOnly ? placeholder : void 0,
80
141
  disabled,
142
+ readOnly,
81
143
  "aria-label": ariaLabel ?? t("ui.tagInput.inputLabel"),
82
144
  onChange: (e) => setDraft(e.target.value),
83
145
  onKeyDown,
146
+ onPaste: (e) => {
147
+ const text = e.clipboardData.getData("text");
148
+ if (!tokenSeparators.some((separator) => text.includes(separator))) return;
149
+ e.preventDefault();
150
+ addAll(text);
151
+ setDraft("");
152
+ },
84
153
  onBlur: () => {
154
+ if (readOnly) return;
85
155
  if (draft.trim()) {
86
156
  add(draft);
87
157
  setDraft("");
@@ -89,6 +159,18 @@ const TagInput = React.forwardRef(
89
159
  }
90
160
  }
91
161
  ),
162
+ showClear ? /* @__PURE__ */ jsx(
163
+ "button",
164
+ {
165
+ type: "button",
166
+ tabIndex: -1,
167
+ "aria-label": clearControl.label,
168
+ "data-slot": "tag-input-clear",
169
+ className: "ui-control-inline-affix-action ui-tag-input-clear",
170
+ onClick: clearAll,
171
+ children: clearControl.clearIcon ?? /* @__PURE__ */ jsx(X, { className: "ui-control-inline-affix-icon", "aria-hidden": "true" })
172
+ }
173
+ ) : null,
92
174
  /* @__PURE__ */ jsx("span", { "aria-live": "polite", className: "sr-only", "data-slot": "tag-input-status", children: t("ui.tagInput.tagCount", { count: tags.length }) }),
93
175
  name ? /* @__PURE__ */ jsx("input", { type: "hidden", name, value: tags.join(",") }) : null
94
176
  ]
@@ -1,3 +1,4 @@
1
+ import { type Control, type FieldPath, type FieldValues, type UseFormReturn } from "react-hook-form";
1
2
  import type { FormStateAdapter } from "../props/components/form.prop.js";
2
3
  /**
3
4
  * Context carrying a framework-agnostic {@link FormStateAdapter}. `FormRoot` provides it on the
@@ -7,11 +8,39 @@ import type { FormStateAdapter } from "../props/components/form.prop.js";
7
8
  export declare const FormAdapterContext: import("react").Context<FormStateAdapter | null>;
8
9
  /** Read the active form-state adapter, or `null` when `FormRoot` runs the react-hook-form path. */
9
10
  export declare function useFormAdapter(): FormStateAdapter | null;
11
+ /** Form-wide options shared by BOTH paths — currently the antd-style form-level `disabled`. */
12
+ export interface FormOptionsContextValue {
13
+ disabled: boolean;
14
+ submitting?: boolean;
15
+ }
16
+ /** Defaults to enabled so a `FormField`/`FormFieldControl` outside a `FormRoot` keeps working. */
17
+ export declare const FormOptionsContext: import("react").Context<FormOptionsContextValue>;
18
+ /**
19
+ * Whether the surrounding `FormRoot` disables its fields (`FormRoot disabled`). Read it for the
20
+ * action buttons, which are NOT fields and so are never disabled automatically.
21
+ */
22
+ export declare function useFormDisabled(): boolean;
10
23
  /**
11
24
  * Whether the surrounding `FormRoot` is currently submitting — works for BOTH paths: the adapter's
12
25
  * `isSubmitting` (e.g. Inertia's `processing`) or react-hook-form's `formState.isSubmitting`.
13
26
  */
14
27
  export declare function useFormSubmitting(): boolean;
28
+ /**
29
+ * The react-hook-form instance of the surrounding `FormRoot` (antd `Form.useFormInstance`) — for
30
+ * `setValue`/`trigger`/`getValues` deep inside a form without prop-drilling `form`. Throws on the
31
+ * adapter path, which has no RHF instance; use {@link useFormAdapter} there.
32
+ */
33
+ export declare function useFormInstance<TFieldValues extends FieldValues = FieldValues>(): UseFormReturn<TFieldValues>;
34
+ export declare function getFallbackFormControl(): Control<FieldValues>;
35
+ /**
36
+ * Subscribe to a field's value (antd `Form.useWatch`) — the "show this section only when `plan`
37
+ * is `enterprise`" case, WITHOUT re-rendering the whole form. Omit `name` for every value.
38
+ * Works on both paths: react-hook-form's `useWatch`, or the adapter's `getValue`/`getValues`.
39
+ *
40
+ * Returns `unknown` — the same contract as `FormFieldControl`'s render-prop `value`. Narrow at the
41
+ * call site (`String(v ?? "")`, a Zod parse, a type guard).
42
+ */
43
+ export declare function useFormWatch<TFieldValues extends FieldValues = FieldValues>(name?: FieldPath<TFieldValues>): unknown;
15
44
  /**
16
45
  * Normalise a control's change payload to a bare value. The `FormFieldControl` render-prop spreads
17
46
  * `onChange` onto a control that may call it with a DOM `ChangeEvent` (native `<Input>`) OR a raw
@@ -1,15 +1,50 @@
1
1
  "use client";
2
2
  import { createContext, useContext } from "react";
3
- import { useFormContext } from "react-hook-form";
3
+ import {
4
+ createFormControl,
5
+ useFormContext,
6
+ useWatch
7
+ } from "react-hook-form";
4
8
  const FormAdapterContext = createContext(null);
5
9
  function useFormAdapter() {
6
10
  return useContext(FormAdapterContext);
7
11
  }
12
+ const FormOptionsContext = createContext({ disabled: false });
13
+ function useFormDisabled() {
14
+ return useContext(FormOptionsContext).disabled;
15
+ }
8
16
  function useFormSubmitting() {
17
+ const { submitting } = useContext(FormOptionsContext);
18
+ const adapter = useContext(FormAdapterContext);
19
+ const rhf = useFormContext();
20
+ if (adapter) return Boolean(submitting || adapter.isSubmitting);
21
+ return Boolean(submitting || rhf?.formState?.isSubmitting);
22
+ }
23
+ function useFormInstance() {
24
+ const rhf = useFormContext();
25
+ if (!rhf) {
26
+ throw new Error(
27
+ "useFormInstance must be rendered inside <FormRoot form={\u2026}> (react-hook-form path). On the adapter path use useFormAdapter()."
28
+ );
29
+ }
30
+ return rhf;
31
+ }
32
+ let fallbackControl;
33
+ function getFallbackFormControl() {
34
+ fallbackControl ??= createFormControl().control;
35
+ return fallbackControl;
36
+ }
37
+ function useFormWatch(name) {
9
38
  const adapter = useContext(FormAdapterContext);
10
39
  const rhf = useFormContext();
11
- if (adapter) return adapter.isSubmitting;
12
- return rhf?.formState?.isSubmitting ?? false;
40
+ const control = rhf?.control ?? getFallbackFormControl();
41
+ const watched = useWatch({
42
+ control,
43
+ name,
44
+ disabled: !rhf
45
+ });
46
+ if (adapter) return name === void 0 ? adapter.getValues?.() : adapter.getValue(name);
47
+ return watched;
13
48
  }
14
49
  function extractControlValue(arg) {
15
50
  if (arg && typeof arg === "object" && "target" in arg) {
@@ -23,7 +58,12 @@ function extractControlValue(arg) {
23
58
  }
24
59
  export {
25
60
  FormAdapterContext,
61
+ FormOptionsContext,
26
62
  extractControlValue,
63
+ getFallbackFormControl,
27
64
  useFormAdapter,
28
- useFormSubmitting
65
+ useFormDisabled,
66
+ useFormInstance,
67
+ useFormSubmitting,
68
+ useFormWatch
29
69
  };
@@ -0,0 +1,22 @@
1
+ import * as React from "react";
2
+ import { type FieldArrayPath, type FieldValues } from "react-hook-form";
3
+ import type { FormFieldArrayProp } from "../props/components/form.prop.js";
4
+ export type { FormFieldArrayProp } from "../props/components/form.prop.js";
5
+ /**
6
+ * FormFieldArray — repeating fields (antd `Form.List`) on react-hook-form's `useFieldArray`, so
7
+ * rows keep their identity across insert/remove/move instead of being re-keyed by index.
8
+ *
9
+ * Each row hands back its dotted path prefix; build the child field name from it, which is what
10
+ * makes nested validation messages land on the right row:
11
+ *
12
+ * ```tsx
13
+ * <FormFieldArray<Values, "contacts"> name="contacts">
14
+ * {({ fields, append, remove }) => (…
15
+ * <FormFieldControl name={`${row.name}.email`} label={t("contact.email")}>…
16
+ * )}
17
+ * </FormFieldArray>
18
+ * ```
19
+ *
20
+ * react-hook-form path only — a server-driven `adapter` owns its own collection state.
21
+ */
22
+ export declare function FormFieldArray<TFieldValues extends FieldValues, TName extends FieldArrayPath<TFieldValues> = FieldArrayPath<TFieldValues>>({ name, children }: FormFieldArrayProp<TFieldValues, TName>): React.JSX.Element;