@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
@@ -1,3 +1,4 @@
1
+ import { useTranslation } from "@godxjp/ui/i18n";
1
2
  import {
2
3
  Card,
3
4
  CardContent,
@@ -5,6 +6,7 @@ import {
5
6
  CardHeader,
6
7
  CardTitle,
7
8
  Timeline,
9
+ RangeTimeline,
8
10
  type TimelineItem,
9
11
  } from "@godxjp/ui/data-display";
10
12
  import { Flex, PageContainer } from "@godxjp/ui/layout";
@@ -52,6 +54,11 @@ const approvalItems: TimelineItem[] = [
52
54
  ];
53
55
 
54
56
  export default function Demo() {
57
+ const { t, locale } = useTranslation();
58
+ const dayLabel = (day: number) =>
59
+ new Intl.DateTimeFormat(locale, { month: "short", day: "numeric", timeZone: "UTC" }).format(
60
+ Date.UTC(2026, 8, day),
61
+ );
55
62
  return (
56
63
  <PageContainer title="Timeline" subtitle="現在のステップを強調する縦型のイベント一覧">
57
64
  <Flex direction="col" gap="lg">
@@ -106,6 +113,40 @@ export default function Demo() {
106
113
  <Timeline variant="status" items={approvalItems} />
107
114
  </CardContent>
108
115
  </Card>
116
+ <Card>
117
+ <CardHeader>
118
+ <CardTitle level={2}>RangeTimeline</CardTitle>
119
+ </CardHeader>
120
+ <CardContent>
121
+ <RangeTimeline
122
+ label={t("rangeTimeline.schedule")}
123
+ bands={[
124
+ {
125
+ label: new Intl.DateTimeFormat(locale, {
126
+ year: "numeric",
127
+ month: "long",
128
+ timeZone: "UTC",
129
+ }).format(Date.UTC(2026, 8, 1)),
130
+ units: 7,
131
+ },
132
+ ]}
133
+ columns={Array.from({ length: 7 }, (_, index) => ({
134
+ label: dayLabel(index + 1),
135
+ units: 1,
136
+ }))}
137
+ rows={[
138
+ {
139
+ id: "example",
140
+ label: "期日確認",
141
+ start: 1,
142
+ end: 4,
143
+ startLabel: dayLabel(2),
144
+ endLabel: dayLabel(5),
145
+ },
146
+ ]}
147
+ />
148
+ </CardContent>
149
+ </Card>
109
150
  </Flex>
110
151
  </PageContainer>
111
152
  );
@@ -186,6 +186,91 @@ export default function Demo() {
186
186
  </FormField>
187
187
  </CardContent>
188
188
  </Card>
189
+
190
+ <Card>
191
+ <CardHeader>
192
+ <CardTitle level={2}>表示形式と元号</CardTitle>
193
+ </CardHeader>
194
+ <CardContent>
195
+ <Flex direction="col" gap="md">
196
+ <FormField id="formatted-date" label="契約日">
197
+ <DatePicker
198
+ id="formatted-date"
199
+ name="contract"
200
+ defaultValue={new Date(2026, 8, 9)}
201
+ format="yyyy年MM月dd日"
202
+ />
203
+ </FormField>
204
+ <FormField id="era-date" label="和暦">
205
+ <DatePicker
206
+ id="era-date"
207
+ name="era"
208
+ defaultValue={new Date(2026, 8, 9)}
209
+ format={{
210
+ calendar: "japanese",
211
+ era: "long",
212
+ year: "numeric",
213
+ month: "long",
214
+ day: "numeric",
215
+ }}
216
+ inputReadOnly
217
+ />
218
+ </FormField>
219
+ </Flex>
220
+ </CardContent>
221
+ </Card>
222
+ <Card>
223
+ <CardHeader>
224
+ <CardTitle level={2}>日時の予約</CardTitle>
225
+ </CardHeader>
226
+ <CardContent>
227
+ <FormField id="appointment-date" label="予約日時">
228
+ <DatePicker
229
+ id="appointment-date"
230
+ name="appointment"
231
+ defaultValue={new Date(2026, 8, 9, 17, 30)}
232
+ showTime
233
+ needConfirm
234
+ presets={[{ label: "翌営業日", value: () => new Date(2026, 8, 10, 9, 0) }]}
235
+ />
236
+ </FormField>
237
+ </CardContent>
238
+ </Card>
239
+ <Card>
240
+ <CardHeader>
241
+ <CardTitle level={2}>集計単位</CardTitle>
242
+ </CardHeader>
243
+ <CardContent>
244
+ <Flex direction="col" gap="md">
245
+ {(["week", "month", "quarter", "year"] as const).map((picker) => (
246
+ <FormField key={picker} id={`period-${picker}`} label={picker}>
247
+ <DatePicker
248
+ id={`period-${picker}`}
249
+ picker={picker}
250
+ defaultValue={new Date(2026, 8, 9)}
251
+ showWeek
252
+ />
253
+ </FormField>
254
+ ))}
255
+ </Flex>
256
+ </CardContent>
257
+ </Card>
258
+ <Card>
259
+ <CardHeader>
260
+ <CardTitle level={2}>複数日を確定</CardTitle>
261
+ </CardHeader>
262
+ <CardContent>
263
+ <FormField id="multiple-dates" label="出勤日">
264
+ <DatePicker
265
+ id="multiple-dates"
266
+ name="attendance"
267
+ multiple
268
+ needConfirm
269
+ defaultValue={[new Date(2026, 8, 9), new Date(2026, 8, 11)]}
270
+ />
271
+ </FormField>
272
+ </CardContent>
273
+ </Card>
189
274
  </Flex>
190
275
  </PageContainer>
191
276
  );
@@ -143,6 +143,32 @@ export default function Demo() {
143
143
  </FormField>
144
144
  </CardContent>
145
145
  </Card>
146
+
147
+ <Card>
148
+ <CardHeader>
149
+ <CardTitle level={2}>期間プリセットと確定</CardTitle>
150
+ </CardHeader>
151
+ <CardContent>
152
+ <FormField id="preset-range" label="集計期間">
153
+ <DateRangePicker
154
+ id="preset-range"
155
+ name="report"
156
+ format="yyyy/MM/dd"
157
+ needConfirm
158
+ allowEmpty={[false, true]}
159
+ showWeek
160
+ minDate={new Date(2026, 0, 1)}
161
+ maxDate={new Date(2026, 11, 31)}
162
+ presets={[
163
+ {
164
+ label: "9月",
165
+ value: () => ({ from: new Date(2026, 8, 1), to: new Date(2026, 8, 30) }),
166
+ },
167
+ ]}
168
+ />
169
+ </FormField>
170
+ </CardContent>
171
+ </Card>
146
172
  </Flex>
147
173
  </PageContainer>
148
174
  );
@@ -0,0 +1,199 @@
1
+ import { useState } from "react";
2
+ import { z } from "zod";
3
+ import { Alert, AlertDescription } from "@godxjp/ui/feedback";
4
+ import { Input, Select } from "@godxjp/ui/data-entry";
5
+ import { Button } from "@godxjp/ui/general";
6
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@godxjp/ui/data-display";
7
+ import { Flex, PageContainer } from "@godxjp/ui/layout";
8
+ import {
9
+ FormFieldArray,
10
+ FormFieldControl,
11
+ FormRoot,
12
+ useFormDisabled,
13
+ useFormWatch,
14
+ useZodForm,
15
+ } from "@godxjp/ui/form";
16
+
17
+ const schema = z.object({
18
+ plan: z.enum(["standard", "enterprise"]),
19
+ contacts: z
20
+ .array(
21
+ z.object({
22
+ name: z.string().min(1, "担当者名を入力してください"),
23
+ email: z.string().email("有効なメールアドレスを入力してください"),
24
+ }),
25
+ )
26
+ .min(1, "担当者を 1 名以上登録してください"),
27
+ });
28
+
29
+ type Values = z.infer<typeof schema>;
30
+
31
+ const emptyContact = { name: "", email: "" };
32
+
33
+ /** Counter driven by useFormWatch — re-renders on `contacts` only, not on every keystroke elsewhere. */
34
+ function ContactCount() {
35
+ const contacts = useFormWatch<Values>("contacts");
36
+ return (
37
+ <CardDescription>
38
+ {`登録済み ${Array.isArray(contacts) ? contacts.length : 0} 名 · 行の追加・削除・並べ替えは送信値の順序にそのまま反映されます。`}
39
+ </CardDescription>
40
+ );
41
+ }
42
+
43
+ /** Action buttons are not fields, so they read the form-level disabled state themselves. */
44
+ function Actions({ onReset }: { onReset: () => void }) {
45
+ const disabled = useFormDisabled();
46
+ return (
47
+ <Flex gap="sm" wrap>
48
+ <Button type="submit" disabled={disabled}>
49
+ 保存
50
+ </Button>
51
+ <Button type="reset" variant="outline" disabled={disabled} onClick={onReset}>
52
+ リセット
53
+ </Button>
54
+ </Flex>
55
+ );
56
+ }
57
+
58
+ export default function Demo() {
59
+ const [status, setStatus] = useState<"idle" | "saved" | "failed">("idle");
60
+ const [locked, setLocked] = useState(false);
61
+ const form = useZodForm(schema, {
62
+ defaultValues: {
63
+ plan: "standard",
64
+ contacts: [{ name: "田中 花子", email: "tanaka@example.jp" }],
65
+ },
66
+ });
67
+
68
+ return (
69
+ <PageContainer
70
+ title="FormFieldArray"
71
+ subtitle="動的な繰り返しフィールド · watch / disabled / reset / 送信失敗"
72
+ >
73
+ <Card>
74
+ <CardHeader>
75
+ <CardTitle level={2}>請求先の担当者</CardTitle>
76
+ </CardHeader>
77
+ <CardContent>
78
+ <FormRoot
79
+ form={form}
80
+ layout="horizontal"
81
+ disabled={locked}
82
+ onSubmit={() => setStatus("saved")}
83
+ onSubmitFailed={() => setStatus("failed")}
84
+ onReset={() => setStatus("idle")}
85
+ >
86
+ <ContactCount />
87
+ {status === "failed" && (
88
+ <Alert tone="destructive">
89
+ <AlertDescription>
90
+ 入力内容を確認してください。最初のエラー項目へ移動しました。
91
+ </AlertDescription>
92
+ </Alert>
93
+ )}
94
+ {status === "saved" && (
95
+ <Alert tone="success">
96
+ <AlertDescription>保存しました。</AlertDescription>
97
+ </Alert>
98
+ )}
99
+
100
+ <FormFieldControl<Values> name="plan" label="プラン">
101
+ {(field) => (
102
+ <Select
103
+ {...field}
104
+ value={typeof field.value === "string" ? field.value : "standard"}
105
+ onValueChange={field.onValueChange}
106
+ options={[
107
+ { value: "standard", label: "スタンダード" },
108
+ { value: "enterprise", label: "エンタープライズ" },
109
+ ]}
110
+ />
111
+ )}
112
+ </FormFieldControl>
113
+
114
+ <FormFieldArray<Values, "contacts"> name="contacts">
115
+ {({ fields, append, remove, move, error, disabled }) => (
116
+ <>
117
+ {error && (
118
+ <Alert tone="destructive">
119
+ <AlertDescription>{error}</AlertDescription>
120
+ </Alert>
121
+ )}
122
+ {fields.map((row, position) => (
123
+ <Card key={row.key} variant="outline">
124
+ <CardContent>
125
+ <Flex direction="col" gap="md">
126
+ <FormFieldControl<Values>
127
+ name={`${row.name}.name`}
128
+ label={`担当者 ${position + 1} · 氏名`}
129
+ required
130
+ >
131
+ {(field) => (
132
+ <Input
133
+ {...field}
134
+ value={typeof field.value === "string" ? field.value : ""}
135
+ />
136
+ )}
137
+ </FormFieldControl>
138
+ <FormFieldControl<Values>
139
+ name={`${row.name}.email`}
140
+ label={`担当者 ${position + 1} · メール`}
141
+ required
142
+ >
143
+ {(field) => (
144
+ <Input
145
+ {...field}
146
+ type="email"
147
+ value={typeof field.value === "string" ? field.value : ""}
148
+ />
149
+ )}
150
+ </FormFieldControl>
151
+ <Flex gap="sm" wrap>
152
+ <Button
153
+ type="button"
154
+ variant="outline"
155
+ size="sm"
156
+ disabled={disabled || position === 0}
157
+ onClick={() => move(row.index, row.index - 1)}
158
+ >
159
+ {`担当者 ${position + 1} を上へ`}
160
+ </Button>
161
+ <Button
162
+ type="button"
163
+ variant="destructive"
164
+ size="sm"
165
+ disabled={disabled || fields.length === 1}
166
+ onClick={() => remove(row.index)}
167
+ >
168
+ {`担当者 ${position + 1} を削除`}
169
+ </Button>
170
+ </Flex>
171
+ </Flex>
172
+ </CardContent>
173
+ </Card>
174
+ ))}
175
+ <Button
176
+ type="button"
177
+ variant="outline"
178
+ disabled={disabled}
179
+ onClick={() => append(emptyContact)}
180
+ >
181
+ 担当者を追加
182
+ </Button>
183
+ </>
184
+ )}
185
+ </FormFieldArray>
186
+
187
+ <Actions onReset={() => setStatus("idle")} />
188
+ </FormRoot>
189
+ </CardContent>
190
+ </Card>
191
+
192
+ <Flex gap="sm" wrap>
193
+ <Button type="button" variant="ghost" onClick={() => setLocked((value) => !value)}>
194
+ {locked ? "フォームのロックを解除" : "フォーム全体をロック (disabled)"}
195
+ </Button>
196
+ </Flex>
197
+ </PageContainer>
198
+ );
199
+ }
@@ -36,9 +36,10 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@godx
36
36
  import { Button, Heading, Text } from "@godxjp/ui/general";
37
37
  import { Flex, PageContainer } from "@godxjp/ui/layout";
38
38
 
39
- /** No-op uploader: resolves immediately with a fake mediaId for preview purposes. */
39
+ /** Keep the upload lifecycle visible in the form example. */
40
40
  async function noopUpload(_file: File, _item: UploadFileItem) {
41
- return { mediaId: "preview-media-id" };
41
+ await new Promise((resolve) => setTimeout(resolve, 800));
42
+ return { mediaId: crypto.randomUUID() };
42
43
  }
43
44
 
44
45
  /** Expense category tree for the Cascader field. */
@@ -107,6 +108,8 @@ export default function Demo() {
107
108
  const [issueDate, setIssueDate] = useState<Date | undefined>(new Date(2026, 0, 15));
108
109
  const [period, setPeriod] = useState<DateRange | undefined>();
109
110
  const [closeTime, setCloseTime] = useState("17:30");
111
+ const [startTime, setStartTime] = useState("09:00");
112
+ const [endTime, setEndTime] = useState("");
110
113
  const [score, setScore] = useState(4);
111
114
  const [tags, setTags] = useState<string[]>(["優先"]);
112
115
  const [otp, setOtp] = useState("");
@@ -119,6 +122,39 @@ export default function Demo() {
119
122
  subtitle="全フィールド型 × 全レイアウト × 全状態 · 実画面のフォーム例 (real primitives only)"
120
123
  >
121
124
  <Flex direction="col" gap="lg">
125
+ <Card>
126
+ <CardHeader>
127
+ <CardTitle level={2}>Validation feedback</CardTitle>
128
+ <CardDescription>確認中・成功・注意・エラーを区別します。</CardDescription>
129
+ </CardHeader>
130
+ <CardContent>
131
+ <Form requiredMark="optional">
132
+ <FormField label="確認済み" hasFeedback validateStatus="success">
133
+ <Input defaultValue="verified@example.jp" />
134
+ </FormField>
135
+ <FormField label="確認中" hasFeedback validateStatus="validating">
136
+ <Input defaultValue="checking@example.jp" />
137
+ </FormField>
138
+ <FormField
139
+ label="注意"
140
+ hasFeedback
141
+ validateStatus="warning"
142
+ helper="内容を確認してください。"
143
+ >
144
+ <Input defaultValue="review@example.jp" />
145
+ </FormField>
146
+ <FormField
147
+ label="エラー"
148
+ required
149
+ hasFeedback
150
+ error="メールアドレスを入力してください。"
151
+ >
152
+ <Input />
153
+ </FormField>
154
+ </Form>
155
+ </CardContent>
156
+ </Card>
157
+
122
158
  {/* ════════════════════ 1. EVERY FIELD TYPE ════════════════════ */}
123
159
  <Heading level={2}>1. 全フィールド型 · すべての入力コントロール</Heading>
124
160
  <Text tone="muted" size="sm">
@@ -414,12 +450,46 @@ export default function Demo() {
414
450
  minuteStep={15}
415
451
  />
416
452
  </FormField>
453
+ {/* 開始/終了 の対 — 終了は開始より前を選べない。`disabledTime` が列と
454
+ 入力欄の両方を止めるので、キーボードから規則をすり抜けられない。 */}
455
+ <FormField id="f-start" label="開始時刻" required>
456
+ <TimePicker
457
+ id="f-start"
458
+ name="start_time"
459
+ value={startTime}
460
+ onValueChange={setStartTime}
461
+ minuteStep={15}
462
+ />
463
+ </FormField>
464
+ <FormField id="f-end" label="終了時刻" required>
465
+ <TimePicker
466
+ id="f-end"
467
+ name="end_time"
468
+ value={endTime}
469
+ onValueChange={setEndTime}
470
+ minuteStep={15}
471
+ disabledTime={() => {
472
+ const [startHour, startMinute] = startTime.split(":").map(Number);
473
+ return {
474
+ disabledHours: () => Array.from({ length: startHour }, (_, hour) => hour),
475
+ disabledMinutes: (hour) =>
476
+ hour === startHour
477
+ ? Array.from({ length: 60 }, (_, minute) => minute).filter(
478
+ (minute) => minute <= startMinute,
479
+ )
480
+ : [],
481
+ };
482
+ }}
483
+ />
484
+ </FormField>
417
485
  <FormField id="f-period" label="会計期間" colSpan={2}>
418
486
  <DateRangePicker
419
487
  id="f-period"
420
488
  name="period"
421
489
  value={period}
422
490
  onValueChange={setPeriod}
491
+ // 土日は選べない。fromDate/toDate では表せない規則。
492
+ disabledDate={(date) => date.getDay() === 0 || date.getDay() === 6}
423
493
  />
424
494
  </FormField>
425
495
  </Form>
@@ -647,9 +717,9 @@ export default function Demo() {
647
717
  <CardDescription>
648
718
  Form に errors(Inertia の form.errors)を渡すと、name
649
719
  を持つフィールドは自分のメッセージをバッグから自動解決してキーを消費(claim)する。
650
- FormErrors は残り
720
+ FormErrors は残り ·
651
721
  隠し・派生フィールド(action_mode、page…)に付いた、どのフィールドにも表示先が無いエラー
652
- だけを role=alert のバナーとして表示する。
722
+ · だけを role=alert のバナーとして表示する。
653
723
  </CardDescription>
654
724
  </CardHeader>
655
725
  <CardContent>
@@ -225,6 +225,30 @@ export default function Demo() {
225
225
  </FormField>
226
226
  </CardContent>
227
227
  </Card>
228
+ <Card>
229
+ <CardHeader>
230
+ <CardTitle level={2}>マスク・入力の正規化</CardTitle>
231
+ <CardDescription>
232
+ 入力と貼り付けを大文字に変換。マスクは表示だけに適用されます。
233
+ </CardDescription>
234
+ </CardHeader>
235
+ <CardContent>
236
+ <FormField id="masked-code" label="確認コード">
237
+ <InputOTP
238
+ maxLength={4}
239
+ defaultValue=""
240
+ mask
241
+ formatter={(value) => value.toUpperCase()}
242
+ >
243
+ <InputOTPGroup>
244
+ {[0, 1, 2, 3].map((index) => (
245
+ <InputOTPSlot key={index} index={index} />
246
+ ))}
247
+ </InputOTPGroup>
248
+ </InputOTP>
249
+ </FormField>
250
+ </CardContent>
251
+ </Card>
228
252
  </Flex>
229
253
  </PageContainer>
230
254
  );
@@ -107,7 +107,52 @@ export default function Demo() {
107
107
  </Flex>
108
108
  </CardContent>
109
109
  </Card>
110
- <Input id="code" size="sm" placeholder="Compact tier (sm)" aria-label="Code" />
110
+ <Card>
111
+ <CardHeader>
112
+ <CardTitle level={2}>Ant Design axes · status, variant, size</CardTitle>
113
+ <CardDescription>
114
+ status is the validation state a form paints consistently (error also reports
115
+ aria-invalid; warning does not, because a warning is not a validity failure). variant
116
+ is how much chrome the field draws. size is the shared control-height ladder.
117
+ </CardDescription>
118
+ </CardHeader>
119
+ <CardContent>
120
+ <Flex direction="col" gap="md">
121
+ <Input aria-label="エラー状態" status="error" defaultValue="不正な値" />
122
+ <Input aria-label="警告状態" status="warning" defaultValue="確認してください" />
123
+ <Input aria-label="枠線あり" variant="outlined" defaultValue="outlined" />
124
+ <Input aria-label="塗りつぶし" variant="filled" defaultValue="filled" />
125
+ <Input aria-label="枠線なし" variant="borderless" defaultValue="borderless" />
126
+ <Input aria-label="小さいサイズ" size="sm" placeholder="Compact tier (sm)" />
127
+ <Input aria-label="標準サイズ" size="md" placeholder="Default tier (md)" />
128
+ <Input aria-label="大きいサイズ" size="lg" placeholder="Comfortable tier (lg)" />
129
+ </Flex>
130
+ </CardContent>
131
+ </Card>
132
+
133
+ <Card>
134
+ <CardHeader>
135
+ <CardTitle level={2}>Affixes · prefix / suffix inside, addons outside</CardTitle>
136
+ <CardDescription>
137
+ A prefix or suffix sits inside the field&apos;s own box, in its padding. An addon is a
138
+ separate surface welded to the outside of the border: a protocol, a currency, a unit.
139
+ The counter reports an overrun and never edits the value.
140
+ </CardDescription>
141
+ </CardHeader>
142
+ <CardContent>
143
+ <Flex direction="col" gap="md">
144
+ <Input aria-label="金額" prefix="¥" suffix="円" defaultValue="12000" />
145
+ <Input
146
+ aria-label="サイト"
147
+ addonBefore="https://"
148
+ addonAfter=".co.jp"
149
+ defaultValue="example"
150
+ />
151
+ <Input aria-label="件名" count={{ max: 20 }} defaultValue="東京都の請求書" />
152
+ <Input aria-label="超過した件名" count={{ max: 5 }} defaultValue="長すぎる件名です" />
153
+ </Flex>
154
+ </CardContent>
155
+ </Card>
111
156
  </Flex>
112
157
  </PageContainer>
113
158
  );
@@ -51,7 +51,7 @@ export default function Demo() {
51
51
  <CardTitle level={2}>検索フィルター (未選択)</CardTitle>
52
52
  <CardDescription>
53
53
  初期値なし。確定済み範囲の上でもう一度選ぶと新しい範囲が開始される (reset-on-complete
54
- 開始月が固まって選び直せない、が起きない)。
54
+ · 開始月が固まって選び直せない、が起きない)。
55
55
  </CardDescription>
56
56
  </CardHeader>
57
57
  <CardContent>
@@ -365,6 +365,33 @@ export default function Demo() {
365
365
  </Flex>
366
366
  </CardContent>
367
367
  </Card>
368
+ <Card>
369
+ <CardHeader>
370
+ <CardTitle level={2}>複数選択・上限・検索</CardTitle>
371
+ <CardDescription>
372
+ 最大 3 件を選択。選択行の再クリック・クリア・キーボード操作に対応。
373
+ </CardDescription>
374
+ </CardHeader>
375
+ <CardContent>
376
+ <FormField id="reviewers" label="レビュアー">
377
+ <Select
378
+ mode="multiple"
379
+ name="reviewers[]"
380
+ defaultValue={["tanaka"]}
381
+ showSearch
382
+ maxCount={3}
383
+ maxTagCount={2}
384
+ allowClear
385
+ options={[
386
+ { value: "tanaka", label: "田中" },
387
+ { value: "sato", label: "佐藤" },
388
+ { value: "suzuki", label: "鈴木" },
389
+ { value: "ito", label: "伊藤" },
390
+ ]}
391
+ />
392
+ </FormField>
393
+ </CardContent>
394
+ </Card>
368
395
  </Flex>
369
396
  </PageContainer>
370
397
  );