@godxjp/ui 21.0.0 → 23.0.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 (154) hide show
  1. package/README.md +4 -4
  2. package/dist/app/app-provider.js +2 -1
  3. package/dist/components/data-display/badge.d.ts +28 -1
  4. package/dist/components/data-display/badge.js +2 -0
  5. package/dist/components/data-display/data-table.d.ts +18 -2
  6. package/dist/components/data-display/data-table.js +4 -0
  7. package/dist/components/data-display/index.d.ts +1 -1
  8. package/dist/components/data-display/popover.d.ts +19 -1
  9. package/dist/components/data-display/popover.js +9 -1
  10. package/dist/components/data-display/progress.d.ts +37 -0
  11. package/dist/components/data-display/progress.js +57 -5
  12. package/dist/components/data-display/scroll-area.d.ts +24 -4
  13. package/dist/components/data-display/scroll-area.js +34 -72
  14. package/dist/components/data-entry/cascader.js +8 -6
  15. package/dist/components/data-entry/checkbox.js +2 -0
  16. package/dist/components/data-entry/choice-hit-target.d.ts +24 -0
  17. package/dist/components/data-entry/choice-hit-target.js +12 -0
  18. package/dist/components/data-entry/control-surface.d.ts +8 -0
  19. package/dist/components/data-entry/control-surface.js +10 -1
  20. package/dist/components/data-entry/date-picker.d.ts +10 -3
  21. package/dist/components/data-entry/date-picker.js +503 -238
  22. package/dist/components/data-entry/form-field.d.ts +1 -1
  23. package/dist/components/data-entry/form-field.js +7 -3
  24. package/dist/components/data-entry/form.js +10 -1
  25. package/dist/components/data-entry/index.d.ts +0 -6
  26. package/dist/components/data-entry/index.js +0 -6
  27. package/dist/components/data-entry/label.d.ts +14 -1
  28. package/dist/components/data-entry/label.js +6 -1
  29. package/dist/components/data-entry/radio.d.ts +27 -6
  30. package/dist/components/data-entry/radio.js +89 -43
  31. package/dist/components/data-entry/search-select.js +226 -89
  32. package/dist/components/data-entry/select.d.ts +76 -14
  33. package/dist/components/data-entry/select.js +497 -163
  34. package/dist/components/data-entry/slider.d.ts +3 -11
  35. package/dist/components/data-entry/slider.js +582 -93
  36. package/dist/components/data-entry/switch.d.ts +6 -3
  37. package/dist/components/data-entry/switch.js +36 -11
  38. package/dist/components/layout/mobile-shell.d.ts +1 -1
  39. package/dist/components/layout/mobile-shell.js +2 -0
  40. package/dist/components/navigation/dropdown-menu.d.ts +11 -3
  41. package/dist/components/navigation/dropdown-menu.js +190 -15
  42. package/dist/components/navigation/index.d.ts +0 -3
  43. package/dist/components/navigation/index.js +0 -78
  44. package/dist/components/navigation/steps.d.ts +1 -1
  45. package/dist/components/navigation/steps.js +3 -1
  46. package/dist/components/navigation/tabs-scroll.d.ts +27 -0
  47. package/dist/components/navigation/tabs-scroll.js +52 -1
  48. package/dist/components/navigation/tabs.d.ts +2 -2
  49. package/dist/components/navigation/tabs.js +88 -23
  50. package/dist/components/ui/index.d.ts +0 -4
  51. package/dist/components/ui/index.js +0 -4
  52. package/dist/components/ui/input-otp.d.ts +1 -1
  53. package/dist/components/ui/segmented.d.ts +15 -1
  54. package/dist/components/ui/segmented.js +15 -6
  55. package/dist/components/ui/tag-input.js +3 -5
  56. package/dist/i18n/messages/en.json +3 -2
  57. package/dist/i18n/messages/ja.json +3 -2
  58. package/dist/i18n/messages/vi.json +3 -2
  59. package/dist/lib/datetime/picker-format.d.ts +18 -0
  60. package/dist/lib/datetime/picker-format.js +35 -1
  61. package/dist/lib/field-a11y.d.ts +12 -0
  62. package/dist/lib/field-a11y.js +14 -1
  63. package/dist/lib/select-options.d.ts +21 -0
  64. package/dist/lib/select-options.js +46 -0
  65. package/dist/props/components/data-display.prop.d.ts +16 -6
  66. package/dist/props/components/data-entry.prop.d.ts +503 -129
  67. package/dist/props/components/index.d.ts +2 -2
  68. package/dist/props/components/layout.prop.d.ts +12 -1
  69. package/dist/props/components/navigation.prop.d.ts +93 -1
  70. package/dist/props/registry.d.ts +79 -16
  71. package/dist/props/registry.js +85 -15
  72. package/dist/props/vocabulary/data.prop.d.ts +12 -0
  73. package/dist/props/vocabulary/index.d.ts +2 -2
  74. package/dist/props/vocabulary/layout.prop.d.ts +11 -0
  75. package/dist/styles/badge-layout.css +4 -0
  76. package/dist/styles/card-layout.css +6 -6
  77. package/dist/styles/control.css +220 -29
  78. package/dist/styles/data-display-layout.css +94 -23
  79. package/dist/styles/focus-ring.css +6 -4
  80. package/dist/styles/fonts.css +88 -4
  81. package/dist/styles/navigation-layout.css +36 -236
  82. package/dist/styles/shell-layout.css +149 -139
  83. package/dist/styles/table-layout.css +41 -3
  84. package/dist/tokens/components/control.css +4 -0
  85. package/dist/tokens/components/data-display.css +9 -5
  86. package/dist/tokens/components/navigation.css +9 -5
  87. package/dist/tokens/components/scroll-area.css +7 -0
  88. package/dist/tokens/components/segmented.css +0 -2
  89. package/dist/tokens/components/shell.css +2 -0
  90. package/dist/tokens/components/table.css +3 -0
  91. package/dist/tokens/foundation.css +7 -0
  92. package/docs/COMPONENTS.md +14 -14
  93. package/docs/CONSUMER-RULES.md +24 -1
  94. package/docs/CUSTOMER-THEMING.md +7 -1
  95. package/docs/FRAME-COVERAGE-REPORT.md +5 -11
  96. package/docs/TOKENS.md +65 -0
  97. package/docs/data-display/badge.tsx +37 -0
  98. package/docs/data-display/data-table/index.tsx +45 -1
  99. package/docs/data-display/popover.tsx +43 -5
  100. package/docs/data-display/progress.tsx +28 -0
  101. package/docs/data-display/scroll-area.tsx +51 -45
  102. package/docs/data-entry/calendar.tsx +2 -2
  103. package/docs/data-entry/date-picker.tsx +144 -5
  104. package/docs/data-entry/form/examples/invoice-form.tsx +2 -2
  105. package/docs/data-entry/form-field/examples/a11y-contract.tsx +3 -5
  106. package/docs/data-entry/form.tsx +4 -4
  107. package/docs/data-entry/input-otp.tsx +5 -2
  108. package/docs/data-entry/number-input.tsx +31 -0
  109. package/docs/data-entry/segmented.tsx +143 -2
  110. package/docs/data-entry/select-matrix.tsx +1 -2
  111. package/docs/data-entry/select.tsx +108 -3
  112. package/docs/data-entry/slider.tsx +125 -47
  113. package/docs/feedback/dialog.tsx +1 -2
  114. package/docs/feedback/sheet.tsx +1 -2
  115. package/docs/layout/app-shell-states.tsx +143 -0
  116. package/docs/layout/mobile-shell.tsx +5 -0
  117. package/docs/navigation/dropdown-menu.tsx +75 -4
  118. package/docs/navigation/tabs.tsx +149 -0
  119. package/docs/roadmap/antd-parity.md +35 -34
  120. package/docs/roadmap/parity-audit-data-entry.md +196 -172
  121. package/docs/roadmap/parity-audit-layout-navigation-general.md +304 -310
  122. package/docs/roadmap/parity-backlog.md +8 -10
  123. package/docs/showcase/acme-portal.tsx +7 -0
  124. package/package.json +28 -64
  125. package/scripts/ui-audit.mjs +18 -5
  126. package/scripts/visual-audit-rules.mjs +0 -7
  127. package/scripts/visual-audit.mjs +6 -27
  128. package/dist/components/data-entry/date-range-picker.d.ts +0 -11
  129. package/dist/components/data-entry/date-range-picker.js +0 -349
  130. package/dist/components/data-entry/month-picker.d.ts +0 -10
  131. package/dist/components/data-entry/month-picker.js +0 -241
  132. package/dist/components/data-entry/month-range-picker.d.ts +0 -10
  133. package/dist/components/data-entry/month-range-picker.js +0 -301
  134. package/dist/components/navigation/context-menu.d.ts +0 -21
  135. package/dist/components/navigation/context-menu.js +0 -149
  136. package/dist/components/navigation/menubar.d.ts +0 -21
  137. package/dist/components/navigation/menubar.js +0 -136
  138. package/dist/components/navigation/navigation-menu.d.ts +0 -10
  139. package/dist/components/navigation/navigation-menu.js +0 -91
  140. package/dist/components/ui/context-menu.d.ts +0 -1
  141. package/dist/components/ui/context-menu.js +0 -2
  142. package/dist/components/ui/date-range-picker.d.ts +0 -1
  143. package/dist/components/ui/date-range-picker.js +0 -2
  144. package/dist/components/ui/menubar.d.ts +0 -1
  145. package/dist/components/ui/menubar.js +0 -2
  146. package/dist/components/ui/navigation-menu.d.ts +0 -1
  147. package/dist/components/ui/navigation-menu.js +0 -2
  148. package/docs/FRAME-A11Y-CI.md +0 -349
  149. package/docs/data-entry/date-range-picker.tsx +0 -175
  150. package/docs/data-entry/month-picker.tsx +0 -101
  151. package/docs/data-entry/month-range-picker.tsx +0 -115
  152. package/docs/navigation/context-menu.tsx +0 -128
  153. package/docs/navigation/menubar.tsx +0 -141
  154. package/docs/navigation/navigation-menu.tsx +0 -158
@@ -4,11 +4,14 @@ import { ja } from "date-fns/locale";
4
4
  import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@godxjp/ui/data-display";
5
5
  import { DatePicker, FormField } from "@godxjp/ui/data-entry";
6
6
  import { Flex, PageContainer } from "@godxjp/ui/layout";
7
+ import type { DateRange } from "react-day-picker";
7
8
 
8
9
  /**
9
- * DatePicker — WAI-ARIA date combobox with a real typeable ISO-8601 input.
10
- * Always give it a `name` for form submission. Use `fromDate`/`toDate` to
11
- * constrain the selectable range. Never hand-roll a date input + calendar.
10
+ * DatePicker — ONE date control. `picker` sets the granularity (day · week · month · quarter ·
11
+ * year), `range` turns it into a two-endpoint field, `multiple` into a set. Always give it a
12
+ * `name` for form submission; it emits ISO-8601 at the picker's own precision. Use
13
+ * `minDate`/`maxDate` to constrain what is selectable. Never hand-roll a date input + calendar,
14
+ * and never compose two DatePickers side-by-side to fake a range — that is `range`.
12
15
  * Composed only from real @godxjp/ui components.
13
16
  */
14
17
  export default function Demo() {
@@ -17,6 +20,11 @@ export default function Demo() {
17
20
  const [closingDate, setClosingDate] = useState<Date | undefined>(undefined);
18
21
  const [meetingDate, setMeetingDate] = useState<Date | undefined>(new Date(2026, 5, 25));
19
22
  const [settlementDate, setSettlementDate] = useState<Date | undefined>(undefined);
23
+ const [period, setPeriod] = useState<DateRange | undefined>({
24
+ from: new Date(2026, 3, 1),
25
+ to: new Date(2027, 2, 31),
26
+ });
27
+ const [term, setTerm] = useState<DateRange | undefined>(undefined);
20
28
 
21
29
  return (
22
30
  <PageContainer
@@ -33,8 +41,7 @@ export default function Demo() {
33
41
  形式に整形され、 解釈できない入力は元の値へ復帰する。
34
42
  </CardDescription>
35
43
  </CardHeader>
36
- {/* data-axe-open: check-frame-axe opens the calendar before scanning. */}
37
- <CardContent data-axe-open>
44
+ <CardContent>
38
45
  <FormField id="issue-date" label="発行日" required>
39
46
  <DatePicker
40
47
  id="issue-date"
@@ -271,6 +278,138 @@ export default function Demo() {
271
278
  </FormField>
272
279
  </CardContent>
273
280
  </Card>
281
+ <Card>
282
+ <CardHeader>
283
+ <CardTitle level={2}>期間 (range)</CardTitle>
284
+ <CardDescription>
285
+ range を付けると値は DateRange になり、開始/終了の 2 入力を 1
286
+ つのコントロールにまとめる。 name=&#34;period&#34; は period_from / period_to として
287
+ ISO 送信される。 開始 &gt; 終了で入力しても order (既定 true) が昇順に正規化する。
288
+ </CardDescription>
289
+ </CardHeader>
290
+ <CardContent>
291
+ <FormField id="period" label="会計期間" required>
292
+ <DatePicker
293
+ range
294
+ id="period"
295
+ name="period"
296
+ value={period}
297
+ onValueChange={setPeriod}
298
+ minDate={new Date(2020, 0, 1)}
299
+ maxDate={new Date(2030, 11, 31)}
300
+ />
301
+ </FormField>
302
+ </CardContent>
303
+ </Card>
304
+ <Card>
305
+ <CardHeader>
306
+ <CardTitle level={2}>月次の期間 (range × picker)</CardTitle>
307
+ <CardDescription>
308
+ range と picker は直交する。picker=&#34;month&#34; の期間は月グリッドで選び、
309
+ term_from / term_to を ISO yyyy-MM で送信する。
310
+ </CardDescription>
311
+ </CardHeader>
312
+ <CardContent>
313
+ <FormField id="term" label="対象期間">
314
+ <DatePicker
315
+ range
316
+ picker="month"
317
+ id="term"
318
+ name="term"
319
+ value={term}
320
+ onValueChange={setTerm}
321
+ minDate={new Date(2024, 0, 1)}
322
+ maxDate={new Date(2027, 11, 31)}
323
+ />
324
+ </FormField>
325
+ </CardContent>
326
+ </Card>
327
+ <Card>
328
+ <CardHeader>
329
+ <CardTitle level={2}>期間プリセットと確定</CardTitle>
330
+ <CardDescription>
331
+ presets · needConfirm · allowEmpty は range でも同じ綴りで効く。
332
+ </CardDescription>
333
+ </CardHeader>
334
+ <CardContent>
335
+ <FormField id="preset-range" label="集計期間">
336
+ <DatePicker
337
+ range
338
+ id="preset-range"
339
+ name="report"
340
+ format="yyyy/MM/dd"
341
+ needConfirm
342
+ allowEmpty={[false, true]}
343
+ showWeek
344
+ minDate={new Date(2026, 0, 1)}
345
+ maxDate={new Date(2026, 11, 31)}
346
+ presets={[
347
+ {
348
+ label: "9月",
349
+ value: () => ({ from: new Date(2026, 8, 1), to: new Date(2026, 8, 30) }),
350
+ },
351
+ ]}
352
+ />
353
+ </FormField>
354
+ </CardContent>
355
+ </Card>
356
+ <Card>
357
+ <CardHeader>
358
+ <CardTitle level={2}>パネルの初期表示 (defaultPickerValue)</CardTitle>
359
+ <CardDescription>
360
+ 値とは独立に「パネルがどの期間を開くか」を指定する。会計年度の開始月で開く、
361
+ 編集中の行の月で開く、といった要求はこれでしか表現できない。antd と同じく
362
+ 開くたびに再適用される。
363
+ </CardDescription>
364
+ </CardHeader>
365
+ <CardContent>
366
+ <FormField id="fiscal-open" label="会計年度開始月">
367
+ <DatePicker
368
+ id="fiscal-open"
369
+ name="fiscal_open"
370
+ picker="month"
371
+ defaultPickerValue={new Date(2027, 3, 1)}
372
+ />
373
+ </FormField>
374
+ </CardContent>
375
+ </Card>
376
+ <Card>
377
+ <CardHeader>
378
+ <CardTitle level={2}>片側だけ固定した期間</CardTitle>
379
+ <CardDescription>
380
+ range のとき disabled は [from, to] のタプルを取り、片方だけロックできる。
381
+ 「開始日は契約で確定、終了日だけ交渉中」がこれで表現できる。
382
+ </CardDescription>
383
+ </CardHeader>
384
+ <CardContent>
385
+ <FormField id="contract-period" label="契約期間">
386
+ <DatePicker
387
+ range
388
+ id="contract-period"
389
+ name="contract_period"
390
+ defaultValue={{ from: new Date(2026, 3, 1), to: new Date(2027, 2, 31) }}
391
+ disabled={[true, false]}
392
+ />
393
+ </FormField>
394
+ </CardContent>
395
+ </Card>
396
+ <Card>
397
+ <CardHeader>
398
+ <CardTitle level={2}>disabled な期間</CardTitle>
399
+ <CardDescription>確定済み期間や読み取り専用フィールドに使用。</CardDescription>
400
+ </CardHeader>
401
+ <CardContent>
402
+ <FormField id="locked-period" label="確定期間">
403
+ <DatePicker
404
+ range
405
+ id="locked-period"
406
+ name="locked_period"
407
+ value={{ from: new Date(2025, 3, 1), to: new Date(2026, 2, 31) }}
408
+ disabled
409
+ />
410
+ </FormField>
411
+ </CardContent>
412
+ </Card>
274
413
  </Flex>
275
414
  </PageContainer>
276
415
  );
@@ -3,7 +3,6 @@ import { useState } from "react";
3
3
  import {
4
4
  Cascader,
5
5
  DatePicker,
6
- DateRangePicker,
7
6
  Field,
8
7
  Form,
9
8
  FormField,
@@ -294,7 +293,8 @@ export default function Demo() {
294
293
  />
295
294
  </FormField>
296
295
  <FormField id="iv-period" label="対象期間">
297
- <DateRangePicker
296
+ <DatePicker
297
+ range
298
298
  id="iv-period"
299
299
  name="period"
300
300
  value={v.period}
@@ -5,11 +5,9 @@ import {
5
5
  CheckboxGroup,
6
6
  ColorPicker,
7
7
  DatePicker,
8
- DateRangePicker,
9
8
  Form,
10
9
  FormField,
11
10
  Input,
12
- MonthPicker,
13
11
  RadioGroup,
14
12
  SearchInput,
15
13
  Select,
@@ -129,14 +127,14 @@ export default function Demo() {
129
127
  <FormField id="ac-date" label="取引日" helper="yyyy-MM-dd">
130
128
  <DatePicker id="ac-date" name="date" />
131
129
  </FormField>
132
- <FormField id="ac-month" label="対象月" helper="yyyy/MM">
133
- <MonthPicker id="ac-month" name="month" />
130
+ <FormField id="ac-month" label="対象月" helper="yyyy-MM">
131
+ <DatePicker picker="month" id="ac-month" name="month" />
134
132
  </FormField>
135
133
  <FormField id="ac-time" label="締め時刻" helper="24時間表記">
136
134
  <TimePicker id="ac-time" name="time" />
137
135
  </FormField>
138
136
  <FormField id="ac-period" label="対象期間" helper="開始日と終了日">
139
- <DateRangePicker id="ac-period" name="period" />
137
+ <DatePicker range id="ac-period" name="period" />
140
138
  </FormField>
141
139
  <FormField id="ac-color" label="タグ色" helper="16進カラー">
142
140
  <ColorPicker id="ac-color" onValueChange={() => {}} />
@@ -5,7 +5,6 @@ import {
5
5
  Checkbox,
6
6
  CheckboxGroup,
7
7
  DatePicker,
8
- DateRangePicker,
9
8
  Field,
10
9
  Form,
11
10
  FormErrors,
@@ -425,7 +424,7 @@ export default function Demo() {
425
424
 
426
425
  <Card>
427
426
  <CardHeader>
428
- <CardTitle level={2}>日時系 · DatePicker / DateRangePicker / TimePicker</CardTitle>
427
+ <CardTitle level={2}>日時系 · DatePicker / DatePicker range / TimePicker</CardTitle>
429
428
  <CardDescription>
430
429
  単一日付・期間・時刻。ISO-8601 / IANA タイムゾーンに準拠し locale
431
430
  連動でフォーマットする。
@@ -483,12 +482,13 @@ export default function Demo() {
483
482
  />
484
483
  </FormField>
485
484
  <FormField id="f-period" label="会計期間" colSpan={2}>
486
- <DateRangePicker
485
+ <DatePicker
486
+ range
487
487
  id="f-period"
488
488
  name="period"
489
489
  value={period}
490
490
  onValueChange={setPeriod}
491
- // 土日は選べない。fromDate/toDate では表せない規則。
491
+ // 土日は選べない。minDate/maxDate では表せない規則。
492
492
  disabledDate={(date) => date.getDay() === 0 || date.getDay() === 6}
493
493
  />
494
494
  </FormField>
@@ -38,8 +38,11 @@ export default function Demo() {
38
38
  <CardDescription>6スロット 1グループ · ペースト・矢印キー操作に対応。</CardDescription>
39
39
  </CardHeader>
40
40
  <CardContent>
41
- <FormField id="email-otp" label="認証コード" required>
42
- <InputOTP maxLength={6} value={emailCode} onChange={setEmailCode}>
41
+ {/* この行の id は FIELD ではなく CONTROL 側にある。FormField はその id をそのまま
42
+ 使い、ラベルのクリックもその id を見る(gh#477:以前は FormField 自身の id を
43
+ 見ていたので、両者が食い違った瞬間にラベルがどこも指さなくなった)。 */}
44
+ <FormField label="認証コード" required>
45
+ <InputOTP id="email-otp" maxLength={6} value={emailCode} onChange={setEmailCode}>
43
46
  <InputOTPGroup>
44
47
  <InputOTPSlot index={0} />
45
48
  <InputOTPSlot index={1} />
@@ -223,6 +223,37 @@ export default function Demo() {
223
223
  </CardContent>
224
224
  </Card>
225
225
 
226
+ {/* prefix / suffix — all THREE combinations, because the two-affix case is the only one
227
+ that used to be exercised and the suffix-only one is where the bug lived. */}
228
+ <Card>
229
+ <CardHeader>
230
+ <CardTitle level={2}>prefix · suffix · 単位つきの数値</CardTitle>
231
+ <CardDescription>
232
+ 通貨記号や単位はフィールドの内側に重ねる装飾。読み上げからは外れる (aria-hidden)
233
+ ので、意味は必ずラベル側に置く。
234
+ </CardDescription>
235
+ </CardHeader>
236
+ <CardContent>
237
+ <Flex direction="col" gap="md" id="number-input-affixes">
238
+ <FormField id="ni-prefix" label="金額(前置き)">
239
+ <NumberInput id="ni-prefix" prefix="¥" defaultValue={1980} aria-label="金額" />
240
+ </FormField>
241
+ <FormField id="ni-suffix" label="日数(後置き)">
242
+ <NumberInput id="ni-suffix" suffix="日" defaultValue={7} aria-label="日数" />
243
+ </FormField>
244
+ <FormField id="ni-both" label="率(前後)">
245
+ <NumberInput
246
+ id="ni-both"
247
+ prefix="+"
248
+ suffix="%"
249
+ defaultValue={12}
250
+ aria-label="率"
251
+ />
252
+ </FormField>
253
+ </Flex>
254
+ </CardContent>
255
+ </Card>
256
+
226
257
  <Card>
227
258
  <CardHeader>
228
259
  <Heading level={2}>キーボード操作</Heading>
@@ -1,8 +1,15 @@
1
1
  import { useState } from "react";
2
2
 
3
- import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@godxjp/ui/data-display";
3
+ import {
4
+ Badge,
5
+ Card,
6
+ CardContent,
7
+ CardDescription,
8
+ CardHeader,
9
+ CardTitle,
10
+ } from "@godxjp/ui/data-display";
4
11
  import { FormField, Segmented } from "@godxjp/ui/data-entry";
5
- import { Text } from "@godxjp/ui/general";
12
+ import { Text, VisuallyHidden } from "@godxjp/ui/general";
6
13
  import { Flex, PageContainer } from "@godxjp/ui/layout";
7
14
  import { CalendarDays, Columns3, LayoutList, Monitor, Moon, Sun } from "lucide-react";
8
15
 
@@ -84,6 +91,140 @@ export default function Demo() {
84
91
  </CardContent>
85
92
  </Card>
86
93
 
94
+ <Card id="four-with-counts">
95
+ <CardHeader>
96
+ <CardTitle level={2}>4 択 + 件数(スマホ幅では折り返す)</CardTitle>
97
+ <CardDescription>
98
+ 一覧の上の状態フィルター。4 つが 1 行に収まらない幅では、トラックが 2 行目に折り返し
99
+ ます — ラベルも件数も切り詰めません。`check:segmented-wrap` がこのカードを測ります。
100
+ </CardDescription>
101
+ </CardHeader>
102
+ <CardContent>
103
+ <Segmented
104
+ aria-label="状態"
105
+ defaultValue="all"
106
+ options={[
107
+ { value: "all", label: "すべて", count: 128 },
108
+ { value: "active", label: "実習中", count: 96 },
109
+ { value: "pending", label: "申請中", count: 12 },
110
+ { value: "gone", label: "失踪・帰国", count: 0 },
111
+ ].map(({ value, label, count }) => ({
112
+ value,
113
+ label: (
114
+ <>
115
+ {label}
116
+ <Badge as="span" variant="secondary">
117
+ {count}
118
+ </Badge>
119
+ </>
120
+ ),
121
+ }))}
122
+ />
123
+ </CardContent>
124
+ </Card>
125
+
126
+ <Card>
127
+ <CardHeader>
128
+ <CardTitle level={2}>size · vertical · block</CardTitle>
129
+ <CardDescription>
130
+ size は 3 段(sm / md / lg)。トラックの高さは --control-height そのもので、
131
+ MobileShell のようにその変数をスコープする領域では自動的にタッチ段(44px)になります。
132
+ vertical では 1 行ぶんが 1 コントロールの高さになります — 横 1 行のときだけトラック
133
+ 内側の余白を引くので、積んだときは引きません。block は幅いっぱいに広げます。
134
+ </CardDescription>
135
+ </CardHeader>
136
+ <CardContent>
137
+ <Flex direction="col" gap="md" align="start">
138
+ <Segmented
139
+ aria-label="サイズ sm"
140
+ size="sm"
141
+ defaultValue="a"
142
+ options={[
143
+ { value: "a", label: "小" },
144
+ { value: "b", label: "中" },
145
+ { value: "c", label: "大" },
146
+ ]}
147
+ />
148
+ <Segmented
149
+ aria-label="サイズ md"
150
+ defaultValue="b"
151
+ options={[
152
+ { value: "a", label: "小" },
153
+ { value: "b", label: "中" },
154
+ { value: "c", label: "大" },
155
+ ]}
156
+ />
157
+ <Segmented
158
+ aria-label="サイズ lg"
159
+ size="lg"
160
+ defaultValue="c"
161
+ options={[
162
+ { value: "a", label: "小" },
163
+ { value: "b", label: "中" },
164
+ { value: "c", label: "大" },
165
+ ]}
166
+ />
167
+ <Segmented
168
+ aria-label="縦積み"
169
+ vertical
170
+ block
171
+ defaultValue="ok"
172
+ options={[
173
+ { value: "ok", label: "実施" },
174
+ { value: "warn", label: "要改善" },
175
+ { value: "none", label: "未実施" },
176
+ ]}
177
+ />
178
+ </Flex>
179
+ </CardContent>
180
+ </Card>
181
+
182
+ <Card>
183
+ <CardHeader>
184
+ <CardTitle level={2}>短い記号を出して、長い名前を読ませる</CardTitle>
185
+ <CardDescription>
186
+ `label` は ReactNode なので、見える記号を aria-hidden にして VisuallyHidden に
187
+ 読み上げ名を置けます。専用の prop は要りません。○ と読み上げられても意味が伝わらない
188
+ ため、色や記号だけに意味を持たせないという WCAG 1.4.1 の要求そのものです。
189
+ </CardDescription>
190
+ </CardHeader>
191
+ <CardContent>
192
+ <Segmented
193
+ aria-label="監査結果"
194
+ defaultValue="ok"
195
+ options={[
196
+ {
197
+ value: "ok",
198
+ label: (
199
+ <>
200
+ <span aria-hidden="true">○</span>
201
+ <VisuallyHidden>実施</VisuallyHidden>
202
+ </>
203
+ ),
204
+ },
205
+ {
206
+ value: "warn",
207
+ label: (
208
+ <>
209
+ <span aria-hidden="true">△</span>
210
+ <VisuallyHidden>要改善</VisuallyHidden>
211
+ </>
212
+ ),
213
+ },
214
+ {
215
+ value: "none",
216
+ label: (
217
+ <>
218
+ <span aria-hidden="true">×</span>
219
+ <VisuallyHidden>未実施</VisuallyHidden>
220
+ </>
221
+ ),
222
+ },
223
+ ]}
224
+ />
225
+ </CardContent>
226
+ </Card>
227
+
87
228
  <Card>
88
229
  <CardHeader>
89
230
  <CardTitle level={2}>フォームの中で</CardTitle>
@@ -142,8 +142,7 @@ export default function Demo() {
142
142
  references. The internal search textbox has its own visible-purpose accessible name.
143
143
  </CardDescription>
144
144
  </CardHeader>
145
- {/* data-axe-open: check-frame-axe opens the searchable panel before scanning. */}
146
- <CardContent data-axe-open>
145
+ <CardContent>
147
146
  <FormField
148
147
  id="searchable-invalid"
149
148
  label="担当拠点"
@@ -21,7 +21,9 @@ import {
21
21
  SelectTrigger,
22
22
  SelectValue,
23
23
  } from "@godxjp/ui/data-entry";
24
+ import { Button } from "@godxjp/ui/general";
24
25
  import { Flex, PageContainer } from "@godxjp/ui/layout";
26
+ import { X } from "lucide-react";
25
27
 
26
28
  /**
27
29
  * Select — the single-select control. Prefer the data-driven `options` API
@@ -38,6 +40,12 @@ export default function Demo() {
38
40
  const [reviewerAsync, setReviewerAsync] = useState("tanaka");
39
41
  const [reviewerCustom, setReviewerCustom] = useState("tanaka");
40
42
  const [reviewerLabel, setReviewerLabel] = useState("tanaka");
43
+ const [prefixed, setPrefixed] = useState("JPY");
44
+ const [tags, setTags] = useState<string[]>(["至急", "社内便"]);
45
+ // antd `labelInValue`: the record arrives carrying its own label, before any option list loads.
46
+ const [assignedTo, setAssignedTo] = useState<
47
+ { value: string; label: React.ReactNode } | undefined
48
+ >({ value: "52", label: "東京本社" });
41
49
 
42
50
  const people = [
43
51
  { value: "tanaka", label: "田中 太郎", sublabel: "tanaka@example.com" },
@@ -82,9 +90,7 @@ export default function Demo() {
82
90
  Pass an options array; name= submits the value with the form.
83
91
  </CardDescription>
84
92
  </CardHeader>
85
- {/* data-axe-open: check-frame-axe opens this Select before scanning, so the
86
- overlay-open a11y state is measured. See docs/FRAME-A11Y-CI.md. */}
87
- <CardContent data-axe-open>
93
+ <CardContent>
88
94
  <FormField id="status" label="状態">
89
95
  <Select
90
96
  id="status"
@@ -392,6 +398,105 @@ export default function Demo() {
392
398
  </FormField>
393
399
  </CardContent>
394
400
  </Card>
401
+ <Card>
402
+ <CardHeader>
403
+ <CardTitle level={2}>
404
+ Ant Design 6 · prefix・suffixIcon・placement・listHeight
405
+ </CardTitle>
406
+ <CardDescription>
407
+ グループは options のネスト({`{ label, options }`})でも、行の group
408
+ でも同じ一覧になります。
409
+ </CardDescription>
410
+ </CardHeader>
411
+ <CardContent>
412
+ <FormField id="prefixed" label="通貨">
413
+ <Select
414
+ id="prefixed"
415
+ name="currency_prefixed"
416
+ value={prefixed}
417
+ onValueChange={setPrefixed}
418
+ prefix={<Badge tone="muted">¥</Badge>}
419
+ placement="bottomEnd"
420
+ listHeight={220}
421
+ options={[
422
+ {
423
+ label: "アジア",
424
+ options: [
425
+ { value: "JPY", label: "日本円" },
426
+ { value: "VND", label: "ベトナムドン" },
427
+ ],
428
+ },
429
+ {
430
+ label: "ヨーロッパ",
431
+ options: [{ value: "EUR", label: "ユーロ" }],
432
+ },
433
+ ]}
434
+ />
435
+ </FormField>
436
+ </CardContent>
437
+ </Card>
438
+
439
+ <Card>
440
+ <CardHeader>
441
+ <CardTitle level={2}>mode=&quot;tags&quot; · tokenSeparators・tagRender</CardTitle>
442
+ <CardDescription>
443
+ 一覧にない値も入力できます。カンマ区切りの貼り付けは一度にまとめて確定します。
444
+ </CardDescription>
445
+ </CardHeader>
446
+ <CardContent>
447
+ <FormField id="tags" label="ラベル">
448
+ <Select
449
+ id="tags"
450
+ name="labels[]"
451
+ mode="tags"
452
+ value={tags}
453
+ onValueChange={setTags}
454
+ tokenSeparators={[",", "\n"]}
455
+ maxTagTextLength={6}
456
+ options={[
457
+ { value: "至急", label: "至急" },
458
+ { value: "要確認", label: "要確認" },
459
+ ]}
460
+ tagRender={({ label, onClose }) => (
461
+ <Badge tone="info">
462
+ {label}
463
+ <Button
464
+ variant="ghost"
465
+ size="icon-xs"
466
+ aria-label={`${String(label)} を外す`}
467
+ onClick={onClose}
468
+ >
469
+ <X aria-hidden="true" />
470
+ </Button>
471
+ </Badge>
472
+ )}
473
+ />
474
+ </FormField>
475
+ </CardContent>
476
+ </Card>
477
+
478
+ <Card>
479
+ <CardHeader>
480
+ <CardTitle level={2}>labelInValue · 読み込み前でも名前が出る</CardTitle>
481
+ <CardDescription>
482
+ 値が {`{ value, label }`} を持つので、選択肢がまだ届いていなくても ID
483
+ ではなく名前を表示できます。
484
+ </CardDescription>
485
+ </CardHeader>
486
+ <CardContent>
487
+ <FormField id="assigned" label="担当拠点">
488
+ <Select
489
+ id="assigned"
490
+ name="office_id"
491
+ labelInValue
492
+ value={assignedTo}
493
+ onValueChange={setAssignedTo}
494
+ notFoundContent="拠点を読み込み中"
495
+ options={[]}
496
+ />
497
+ </FormField>
498
+ </CardContent>
499
+ </Card>
395
500
  </Flex>
396
501
  </PageContainer>
397
502
  );