@godxjp/ui 21.0.0 → 22.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 (103) hide show
  1. package/dist/app/app-provider.js +2 -1
  2. package/dist/components/data-display/badge.d.ts +28 -1
  3. package/dist/components/data-display/badge.js +2 -0
  4. package/dist/components/data-display/data-table.d.ts +18 -2
  5. package/dist/components/data-display/data-table.js +4 -0
  6. package/dist/components/data-display/index.d.ts +1 -1
  7. package/dist/components/data-display/popover.d.ts +19 -1
  8. package/dist/components/data-display/popover.js +9 -1
  9. package/dist/components/data-display/progress.d.ts +37 -0
  10. package/dist/components/data-display/progress.js +57 -5
  11. package/dist/components/data-entry/date-picker.d.ts +10 -3
  12. package/dist/components/data-entry/date-picker.js +503 -238
  13. package/dist/components/data-entry/form-field.d.ts +1 -1
  14. package/dist/components/data-entry/form-field.js +4 -1
  15. package/dist/components/data-entry/form.js +10 -1
  16. package/dist/components/data-entry/index.d.ts +0 -6
  17. package/dist/components/data-entry/index.js +0 -6
  18. package/dist/components/data-entry/label.d.ts +14 -1
  19. package/dist/components/data-entry/label.js +6 -1
  20. package/dist/components/data-entry/radio.d.ts +27 -6
  21. package/dist/components/data-entry/radio.js +88 -43
  22. package/dist/components/data-entry/search-select.js +8 -1
  23. package/dist/components/data-entry/select.js +2 -0
  24. package/dist/components/data-entry/switch.d.ts +6 -3
  25. package/dist/components/data-entry/switch.js +34 -11
  26. package/dist/components/layout/mobile-shell.d.ts +1 -1
  27. package/dist/components/layout/mobile-shell.js +2 -0
  28. package/dist/components/navigation/menubar.d.ts +10 -3
  29. package/dist/components/navigation/steps.d.ts +1 -1
  30. package/dist/components/navigation/steps.js +3 -1
  31. package/dist/components/navigation/tabs-scroll.d.ts +27 -0
  32. package/dist/components/navigation/tabs-scroll.js +52 -1
  33. package/dist/components/navigation/tabs.d.ts +2 -2
  34. package/dist/components/navigation/tabs.js +88 -23
  35. package/dist/components/ui/index.d.ts +0 -1
  36. package/dist/components/ui/index.js +0 -1
  37. package/dist/components/ui/segmented.d.ts +15 -1
  38. package/dist/components/ui/segmented.js +13 -6
  39. package/dist/i18n/messages/en.json +3 -2
  40. package/dist/i18n/messages/ja.json +3 -2
  41. package/dist/i18n/messages/vi.json +3 -2
  42. package/dist/lib/datetime/picker-format.d.ts +18 -0
  43. package/dist/lib/datetime/picker-format.js +35 -1
  44. package/dist/lib/field-a11y.d.ts +12 -0
  45. package/dist/lib/field-a11y.js +14 -1
  46. package/dist/props/components/data-display.prop.d.ts +7 -0
  47. package/dist/props/components/data-entry.prop.d.ts +180 -99
  48. package/dist/props/components/index.d.ts +1 -1
  49. package/dist/props/components/layout.prop.d.ts +12 -1
  50. package/dist/props/components/navigation.prop.d.ts +76 -1
  51. package/dist/props/registry.d.ts +16 -16
  52. package/dist/props/registry.js +16 -15
  53. package/dist/props/vocabulary/data.prop.d.ts +12 -0
  54. package/dist/props/vocabulary/index.d.ts +2 -2
  55. package/dist/props/vocabulary/layout.prop.d.ts +11 -0
  56. package/dist/styles/badge-layout.css +4 -0
  57. package/dist/styles/card-layout.css +6 -6
  58. package/dist/styles/control.css +15 -6
  59. package/dist/styles/data-display-layout.css +76 -13
  60. package/dist/styles/focus-ring.css +4 -2
  61. package/dist/styles/navigation-layout.css +25 -0
  62. package/dist/styles/shell-layout.css +5 -0
  63. package/dist/styles/table-layout.css +34 -0
  64. package/dist/tokens/components/data-display.css +9 -0
  65. package/dist/tokens/components/navigation.css +6 -0
  66. package/dist/tokens/components/segmented.css +0 -2
  67. package/dist/tokens/components/shell.css +2 -0
  68. package/dist/tokens/components/table.css +3 -0
  69. package/dist/tokens/foundation.css +7 -0
  70. package/docs/COMPONENTS.md +14 -14
  71. package/docs/CONSUMER-RULES.md +24 -1
  72. package/docs/FRAME-COVERAGE-REPORT.md +3 -6
  73. package/docs/TOKENS.md +65 -0
  74. package/docs/data-display/badge.tsx +37 -0
  75. package/docs/data-display/data-table/index.tsx +18 -0
  76. package/docs/data-display/popover.tsx +41 -1
  77. package/docs/data-display/progress.tsx +28 -0
  78. package/docs/data-entry/calendar.tsx +2 -2
  79. package/docs/data-entry/date-picker.tsx +143 -3
  80. package/docs/data-entry/form/examples/invoice-form.tsx +2 -2
  81. package/docs/data-entry/form-field/examples/a11y-contract.tsx +3 -5
  82. package/docs/data-entry/form.tsx +4 -4
  83. package/docs/data-entry/number-input.tsx +31 -0
  84. package/docs/data-entry/segmented.tsx +103 -1
  85. package/docs/layout/mobile-shell.tsx +5 -0
  86. package/docs/navigation/tabs.tsx +149 -0
  87. package/docs/roadmap/antd-parity.md +35 -34
  88. package/docs/roadmap/parity-audit-data-entry.md +178 -165
  89. package/docs/roadmap/parity-backlog.md +6 -7
  90. package/docs/showcase/acme-portal.tsx +7 -0
  91. package/package.json +11 -23
  92. package/scripts/ui-audit.mjs +17 -4
  93. package/dist/components/data-entry/date-range-picker.d.ts +0 -11
  94. package/dist/components/data-entry/date-range-picker.js +0 -349
  95. package/dist/components/data-entry/month-picker.d.ts +0 -10
  96. package/dist/components/data-entry/month-picker.js +0 -241
  97. package/dist/components/data-entry/month-range-picker.d.ts +0 -10
  98. package/dist/components/data-entry/month-range-picker.js +0 -301
  99. package/dist/components/ui/date-range-picker.d.ts +0 -1
  100. package/dist/components/ui/date-range-picker.js +0 -2
  101. package/docs/data-entry/date-range-picker.tsx +0 -175
  102. package/docs/data-entry/month-picker.tsx +0 -101
  103. package/docs/data-entry/month-range-picker.tsx +0 -115
package/docs/TOKENS.md CHANGED
@@ -44,6 +44,71 @@ src/styles/
44
44
 
45
45
  Default brand tokens use the GodX Agent Portal palette: navy primary, 朱 orange focus/accent, warm neutral surfaces. App or customer identity colors belong in the consuming app theme, not in package tokens.
46
46
 
47
+ #### The three tone tiers — FILL, TEXT, MARK
48
+
49
+ A status tone can be painted three ways, and each way is judged against a different thing. Reading
50
+ the wrong tier is this palette's most expensive recurring bug, because nothing about it looks wrong
51
+ in the source.
52
+
53
+ | Tier | Tokens | What it paints | Contrast bar |
54
+ | -------- | ------------------------------------------- | ------------------------------------------------------------------ | --------------------------------------------------------- |
55
+ | **FILL** | `--success`, `--warning`, `--info`, `--destructive` | A solid chip, band or bar with a label ON it | AA **4.5:1** against its own `*-foreground` label |
56
+ | **TEXT** | `--text-success`, `--text-warning`, `--text-info`, `--text-error` | Small coloured type — a StatCard delta, an outline badge label | AA **4.5:1** against the surface BEHIND it |
57
+ | **MARK** | `--mark-success`, `--mark-warning`, `--mark-info`, `--mark-destructive`, `--mark-primary`, `--mark-attention` | A thin shape carrying meaning with nothing written on it — a `Card accent` rail, a `DataTable rowTone` rail | SC 1.4.11 **3:1** against the surface it sits on |
58
+
59
+ The MARK tier exists because both rails were reading FILL, and two of them were effectively
60
+ invisible. Measured in Chromium on the light card: `Card accent="warning"` drew its 6px rail at
61
+ **1.74:1**, `accent="success"` at **2.18:1**. `--warning` is a bright 山吹 tuned to carry dark text
62
+ on top of it; on a near-white card it is barely a colour at all.
63
+
64
+ `--mark-*` resolve THROUGH the TEXT tier rather than restating its numbers, so a theme retunes one
65
+ place and both follow — and since 3:1 is looser than 4.5:1, anything legible as text is legible as
66
+ a mark by construction. `--mark-attention` is the exception: there is no `--text-attention`, and
67
+ the fill already clears the floor in both themes (3.32 / 6.75).
68
+
69
+ Two guards, because one was not enough:
70
+ `src/tokens/__tests__/tone-mark-contrast.test.ts` recomputes every tone × ground × theme off the
71
+ committed tokens and reads the alias out of the CSS (so repointing a mark back at FILL fails the
72
+ ratio, not just a name check); and `check:contrast` grew a **rail pass** — its graphic pass only
73
+ ever measured elements ≤24px on both axes, and a rail is 6px by the full height of a card, so no
74
+ gate had ever looked at one.
75
+
76
+ **Progress and Legend joined the tier, and the wa-iro question is settled.** `.ui-legend-swatch`,
77
+ `.ui-progress-bar` and `.ui-progress-segment` all read FILL as standalone graphics and all failed
78
+ the same floor. The floor applies: nothing is written on a progress fill, so *where the colour
79
+ stops* is the entire datum, and a slice of a partition carries its share of the whole with no words
80
+ on it — that is precisely a "graphical object required to understand the content". The hue loses.
81
+ The two moved together because a swatch is a SAMPLE of the bar beside it; a key that is not the
82
+ colour it is a key to is not a key.
83
+
84
+ | surface | tone | ground | FILL (before) | MARK (after) |
85
+ | --- | --- | --- | --- | --- |
86
+ | `.ui-legend-swatch` | warning | light card | **1.74** | **5.90** |
87
+ | `.ui-legend-swatch` | success | light card | **2.18** | **6.84** |
88
+ | `.ui-legend-swatch` | destructive | dark card | **2.95** | **5.52** |
89
+ | `.ui-progress-segment` | warning | light track | **1.60** | **5.41** |
90
+ | `.ui-progress-segment` | success | light track | **2.00** | **6.28** |
91
+ | `.ui-progress-segment` | destructive | dark track | **2.42** | **4.52** |
92
+
93
+ Worst case anywhere on the two routes after the move: **4.52:1**. `.ui-progress-bar` (meter and
94
+ over-capacity) reads the same tokens as the slice, so a `tone="warning"` meter and a `warning`
95
+ slice on one screen stay the same colour.
96
+
97
+ **A theme that repoints `--secondary` owes `--progress-track-background`.** The track defaults to
98
+ `hsl(var(--secondary))`, which is a pale neutral in the stock palette. `docs/showcase/acme-portal`
99
+ repurposes `--secondary` as a navy *button* colour, so its bars were drawn on a near-black track
100
+ and the mark fills measured 2.50 (success) / 2.90 (warning) on it. Naming the track explicitly is
101
+ the fix — 6.49 / 5.59 after — not dragging the tier back.
102
+
103
+ Three guards now, and each sees something the others cannot: the ratio tests in
104
+ `tone-mark-contrast.test.ts` (rails against card/background, progress marks against the track);
105
+ the *same file's* CSS-alias assertions, which fail if a rule is repointed at the fill tier and
106
+ which also pin the swatch and the slice to the **same** token per tone; and `check:contrast`'s
107
+ **thin fill** pass — added because adding `/isolate/data-display-progress` to that gate's route
108
+ list on its own changed nothing at all. The graphic pass wants ≤24px on both axes and the rail
109
+ pass wants a one-sided border; a progress fill is 8px (meter) or 22px (slice) tall by whatever
110
+ width its share makes it, so the sweep printed "AA clean" over a bar sitting at 1.60:1.
111
+
47
112
  #### `--border` vs `--input` — decorative chrome vs control boundary (gh#315)
48
113
 
49
114
  These two look like synonyms and are not. Keep them apart:
@@ -262,6 +262,43 @@ export default function Demo() {
262
262
  </CardContent>
263
263
  </Card>
264
264
 
265
+ <Card>
266
+ <CardHeader>
267
+ <CardTitle level={2}>tabular · 数字を桁で揃える</CardTitle>
268
+ <CardDescription>
269
+ 件数のチップが縦に並ぶときは tabular を。プロポーショナル数字だと 1 が 0
270
+ より狭いので、列で見たときに桁がずれてチップ幅も揺れます。既定はオフ — 語を載せる
271
+ チップまで幅の広い数字を払う必要はありません。
272
+ </CardDescription>
273
+ </CardHeader>
274
+ <CardContent>
275
+ <Flex direction="row" gap="lg">
276
+ <Flex direction="col" align="start" gap="xs">
277
+ <Text size="xs" tone="muted">
278
+ 既定(比例数字)
279
+ </Text>
280
+ <Badge tone="info">11</Badge>
281
+ <Badge tone="info">100</Badge>
282
+ <Badge tone="warning">1811</Badge>
283
+ </Flex>
284
+ <Flex direction="col" align="start" gap="xs">
285
+ <Text size="xs" tone="muted">
286
+ tabular
287
+ </Text>
288
+ <Badge tone="info" tabular>
289
+ 11
290
+ </Badge>
291
+ <Badge tone="info" tabular>
292
+ 100
293
+ </Badge>
294
+ <Badge tone="warning" tabular>
295
+ 1811
296
+ </Badge>
297
+ </Flex>
298
+ </Flex>
299
+ </CardContent>
300
+ </Card>
301
+
265
302
  <Card>
266
303
  <CardHeader>
267
304
  <CardTitle level={2}>Icon overrides</CardTitle>
@@ -140,6 +140,24 @@ export default function Demo() {
140
140
  getRowId={(row) => row.id}
141
141
  />
142
142
  </Flex>
143
+ {/* Row TONE — the leading-edge rail + wash for a row in a named state. The status Badge
144
+ stays in its own cell: the rail makes the row findable, it does not carry the meaning
145
+ (WCAG 1.4.1). */}
146
+ <Flex direction="col" gap="sm" id="row-tone">
147
+ <Text weight="medium">行のトーン(要対応の行)</Text>
148
+ <DataTable
149
+ data={invoices}
150
+ columns={columns}
151
+ getRowId={(row) => row.id}
152
+ rowTone={(row) =>
153
+ row.status === "failed"
154
+ ? "destructive"
155
+ : row.status === "pending"
156
+ ? "attention"
157
+ : undefined
158
+ }
159
+ />
160
+ </Flex>
143
161
  {/* Primary: sorted (amount desc), one row preselected, clickable rows,
144
162
  controlled density (comfortable), kebab row actions, pagination footer. */}
145
163
  <DataTable
@@ -15,7 +15,7 @@ import {
15
15
  PopoverTrigger,
16
16
  } from "@godxjp/ui/data-display";
17
17
  import { Checkbox, FormField, Input, Label } from "@godxjp/ui/data-entry";
18
- import { Button } from "@godxjp/ui/general";
18
+ import { Button, Text } from "@godxjp/ui/general";
19
19
  import { Flex, PageContainer } from "@godxjp/ui/layout";
20
20
  import { Info, SlidersHorizontal } from "lucide-react";
21
21
 
@@ -209,6 +209,46 @@ export default function Demo() {
209
209
  </CardContent>
210
210
  </Card>
211
211
 
212
+ <Card>
213
+ <CardHeader>
214
+ <CardTitle level={2}>width · パネルの測り方</CardTitle>
215
+ <CardDescription>
216
+ 既定の panel は --popover-width(18rem)。auto は中身に測らせます — 自前の幅を持つもの
217
+ (Calendar、チャート、固定幅のプレビュー)はこれ以外に正解がありません。trigger
218
+ はアンカーに合わせます。className に w-* を書くのは呼び出し側の定数で、どのテーマからも
219
+ retune できません。flush が padding 側で避けているのと同じ話です。
220
+ </CardDescription>
221
+ </CardHeader>
222
+ <CardContent>
223
+ <Flex direction="row" wrap gap="sm">
224
+ <Popover>
225
+ <PopoverTrigger asChild>
226
+ <Button variant="outline">panel(既定)</Button>
227
+ </PopoverTrigger>
228
+ <PopoverContent aria-label="既定の幅">
229
+ <Text size="sm">18rem のパネル幅。散文や、パネル自身が測るリスト向け。</Text>
230
+ </PopoverContent>
231
+ </Popover>
232
+ <Popover>
233
+ <PopoverTrigger asChild>
234
+ <Button variant="outline">auto</Button>
235
+ </PopoverTrigger>
236
+ <PopoverContent width="auto" aria-label="内容に合わせた幅">
237
+ <Text size="sm">中身が幅を決めます。</Text>
238
+ </PopoverContent>
239
+ </Popover>
240
+ <Popover>
241
+ <PopoverTrigger asChild>
242
+ <Button variant="outline">trigger に合わせる</Button>
243
+ </PopoverTrigger>
244
+ <PopoverContent width="trigger" aria-label="トリガーと同じ幅">
245
+ <Text size="sm">アンカーと同じ幅。</Text>
246
+ </PopoverContent>
247
+ </Popover>
248
+ </Flex>
249
+ </CardContent>
250
+ </Card>
251
+
212
252
  <Card>
213
253
  <CardHeader>
214
254
  <CardTitle level={2}>Modal</CardTitle>
@@ -33,6 +33,34 @@ export default function Demo() {
33
33
  </CardContent>
34
34
  </Card>
35
35
 
36
+ <Card>
37
+ <CardHeader>
38
+ <CardTitle level={2}>shape=&quot;ring&quot;</CardTitle>
39
+ <CardDescription>
40
+ 同じメーターを弧で描きます。バーと同じ value / tone / size / ARIA
41
+ のまま、label がリングの内側に入ります。狭いアプリバーで「18 / 42
42
+ 件」を見出しの横に出したいとき、バー+キャプションの 2
43
+ 段ではなく 1 マスで済むのが理由です。数値が重要なときではなく、
44
+ 場所が正方形のときに選びます。
45
+ </CardDescription>
46
+ </CardHeader>
47
+ <CardContent>
48
+ <Flex direction="row" wrap align="center" gap="lg">
49
+ {/* label はリング内の表示であり、同時に読み上げ名でもあります。「何の 18/42 か」を
50
+ 名前にしたいときは、画面にすでにある見出しを aria-labelledby で指します
51
+ — bar と同じ規約です。 */}
52
+ <Progress shape="ring" value={43} label="18/42" />
53
+ <Progress shape="ring" value={43} size="sm" label="18/42" />
54
+ <Progress shape="ring" value={0} label="0/42" />
55
+ <Progress shape="ring" value={100} label="42/42" />
56
+ <Progress shape="ring" value={62} tone="warning" label="26/42" />
57
+ <Progress shape="ring" value={88} tone="destructive" label="37/42" />
58
+ {/* label なし=読み出しのないリング。名前は aria-label から。 */}
59
+ <Progress shape="ring" value={43} aria-label="実施済みの点検" />
60
+ </Flex>
61
+ </CardContent>
62
+ </Card>
63
+
36
64
  <Card>
37
65
  <CardHeader>
38
66
  <CardTitle level={2}>Breakdown(segments)</CardTitle>
@@ -20,7 +20,7 @@ import type { DateRange } from "react-day-picker";
20
20
  /**
21
21
  * Calendar — styled react-day-picker grid for single, multiple, or range
22
22
  * selection. Embed inside a Popover for picker UX; use DatePicker /
23
- * DateRangePicker for form-submittable inputs instead. Composed only from
23
+ * `DatePicker range` for form-submittable inputs instead. Composed only from
24
24
  * real @godxjp/ui components.
25
25
  */
26
26
  export default function Demo() {
@@ -71,7 +71,7 @@ export default function Demo() {
71
71
  <CardHeader>
72
72
  <CardTitle level={2}>範囲選択 · Popover 内</CardTitle>
73
73
  <CardDescription>
74
- mode="range" でポップオーバー内に配置。DateRangePicker
74
+ mode="range" でポップオーバー内に配置。DatePicker range
75
75
  はこのパターンにフォーム送信を加えた上位コンポーネント。
76
76
  </CardDescription>
77
77
  </CardHeader>
@@ -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
@@ -271,6 +279,138 @@ export default function Demo() {
271
279
  </FormField>
272
280
  </CardContent>
273
281
  </Card>
282
+ <Card>
283
+ <CardHeader>
284
+ <CardTitle level={2}>期間 (range)</CardTitle>
285
+ <CardDescription>
286
+ range を付けると値は DateRange になり、開始/終了の 2 入力を 1
287
+ つのコントロールにまとめる。 name=&#34;period&#34; は period_from / period_to として
288
+ ISO 送信される。 開始 &gt; 終了で入力しても order (既定 true) が昇順に正規化する。
289
+ </CardDescription>
290
+ </CardHeader>
291
+ <CardContent data-axe-open>
292
+ <FormField id="period" label="会計期間" required>
293
+ <DatePicker
294
+ range
295
+ id="period"
296
+ name="period"
297
+ value={period}
298
+ onValueChange={setPeriod}
299
+ minDate={new Date(2020, 0, 1)}
300
+ maxDate={new Date(2030, 11, 31)}
301
+ />
302
+ </FormField>
303
+ </CardContent>
304
+ </Card>
305
+ <Card>
306
+ <CardHeader>
307
+ <CardTitle level={2}>月次の期間 (range × picker)</CardTitle>
308
+ <CardDescription>
309
+ range と picker は直交する。picker=&#34;month&#34; の期間は月グリッドで選び、
310
+ term_from / term_to を ISO yyyy-MM で送信する。
311
+ </CardDescription>
312
+ </CardHeader>
313
+ <CardContent>
314
+ <FormField id="term" label="対象期間">
315
+ <DatePicker
316
+ range
317
+ picker="month"
318
+ id="term"
319
+ name="term"
320
+ value={term}
321
+ onValueChange={setTerm}
322
+ minDate={new Date(2024, 0, 1)}
323
+ maxDate={new Date(2027, 11, 31)}
324
+ />
325
+ </FormField>
326
+ </CardContent>
327
+ </Card>
328
+ <Card>
329
+ <CardHeader>
330
+ <CardTitle level={2}>期間プリセットと確定</CardTitle>
331
+ <CardDescription>
332
+ presets · needConfirm · allowEmpty は range でも同じ綴りで効く。
333
+ </CardDescription>
334
+ </CardHeader>
335
+ <CardContent>
336
+ <FormField id="preset-range" label="集計期間">
337
+ <DatePicker
338
+ range
339
+ id="preset-range"
340
+ name="report"
341
+ format="yyyy/MM/dd"
342
+ needConfirm
343
+ allowEmpty={[false, true]}
344
+ showWeek
345
+ minDate={new Date(2026, 0, 1)}
346
+ maxDate={new Date(2026, 11, 31)}
347
+ presets={[
348
+ {
349
+ label: "9月",
350
+ value: () => ({ from: new Date(2026, 8, 1), to: new Date(2026, 8, 30) }),
351
+ },
352
+ ]}
353
+ />
354
+ </FormField>
355
+ </CardContent>
356
+ </Card>
357
+ <Card>
358
+ <CardHeader>
359
+ <CardTitle level={2}>パネルの初期表示 (defaultPickerValue)</CardTitle>
360
+ <CardDescription>
361
+ 値とは独立に「パネルがどの期間を開くか」を指定する。会計年度の開始月で開く、
362
+ 編集中の行の月で開く、といった要求はこれでしか表現できない。antd と同じく
363
+ 開くたびに再適用される。
364
+ </CardDescription>
365
+ </CardHeader>
366
+ <CardContent>
367
+ <FormField id="fiscal-open" label="会計年度開始月">
368
+ <DatePicker
369
+ id="fiscal-open"
370
+ name="fiscal_open"
371
+ picker="month"
372
+ defaultPickerValue={new Date(2027, 3, 1)}
373
+ />
374
+ </FormField>
375
+ </CardContent>
376
+ </Card>
377
+ <Card>
378
+ <CardHeader>
379
+ <CardTitle level={2}>片側だけ固定した期間</CardTitle>
380
+ <CardDescription>
381
+ range のとき disabled は [from, to] のタプルを取り、片方だけロックできる。
382
+ 「開始日は契約で確定、終了日だけ交渉中」がこれで表現できる。
383
+ </CardDescription>
384
+ </CardHeader>
385
+ <CardContent>
386
+ <FormField id="contract-period" label="契約期間">
387
+ <DatePicker
388
+ range
389
+ id="contract-period"
390
+ name="contract_period"
391
+ defaultValue={{ from: new Date(2026, 3, 1), to: new Date(2027, 2, 31) }}
392
+ disabled={[true, false]}
393
+ />
394
+ </FormField>
395
+ </CardContent>
396
+ </Card>
397
+ <Card>
398
+ <CardHeader>
399
+ <CardTitle level={2}>disabled な期間</CardTitle>
400
+ <CardDescription>確定済み期間や読み取り専用フィールドに使用。</CardDescription>
401
+ </CardHeader>
402
+ <CardContent>
403
+ <FormField id="locked-period" label="確定期間">
404
+ <DatePicker
405
+ range
406
+ id="locked-period"
407
+ name="locked_period"
408
+ value={{ from: new Date(2025, 3, 1), to: new Date(2026, 2, 31) }}
409
+ disabled
410
+ />
411
+ </FormField>
412
+ </CardContent>
413
+ </Card>
274
414
  </Flex>
275
415
  </PageContainer>
276
416
  );
@@ -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>
@@ -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>
@@ -2,7 +2,7 @@ import { useState } from "react";
2
2
 
3
3
  import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@godxjp/ui/data-display";
4
4
  import { FormField, Segmented } from "@godxjp/ui/data-entry";
5
- import { Text } from "@godxjp/ui/general";
5
+ import { Text, VisuallyHidden } from "@godxjp/ui/general";
6
6
  import { Flex, PageContainer } from "@godxjp/ui/layout";
7
7
  import { CalendarDays, Columns3, LayoutList, Monitor, Moon, Sun } from "lucide-react";
8
8
 
@@ -84,6 +84,108 @@ export default function Demo() {
84
84
  </CardContent>
85
85
  </Card>
86
86
 
87
+ <Card>
88
+ <CardHeader>
89
+ <CardTitle level={2}>size · vertical · block</CardTitle>
90
+ <CardDescription>
91
+ size は 3 段(sm / md / lg)。トラックの高さは --control-height そのもので、
92
+ MobileShell のようにその変数をスコープする領域では自動的にタッチ段(44px)になります。
93
+ vertical では 1 行ぶんが 1 コントロールの高さになります — 横 1 行のときだけトラック
94
+ 内側の余白を引くので、積んだときは引きません。block は幅いっぱいに広げます。
95
+ </CardDescription>
96
+ </CardHeader>
97
+ <CardContent>
98
+ <Flex direction="col" gap="md" align="start">
99
+ <Segmented
100
+ aria-label="サイズ sm"
101
+ size="sm"
102
+ defaultValue="a"
103
+ options={[
104
+ { value: "a", label: "小" },
105
+ { value: "b", label: "中" },
106
+ { value: "c", label: "大" },
107
+ ]}
108
+ />
109
+ <Segmented
110
+ aria-label="サイズ md"
111
+ defaultValue="b"
112
+ options={[
113
+ { value: "a", label: "小" },
114
+ { value: "b", label: "中" },
115
+ { value: "c", label: "大" },
116
+ ]}
117
+ />
118
+ <Segmented
119
+ aria-label="サイズ lg"
120
+ size="lg"
121
+ defaultValue="c"
122
+ options={[
123
+ { value: "a", label: "小" },
124
+ { value: "b", label: "中" },
125
+ { value: "c", label: "大" },
126
+ ]}
127
+ />
128
+ <Segmented
129
+ aria-label="縦積み"
130
+ vertical
131
+ block
132
+ defaultValue="ok"
133
+ options={[
134
+ { value: "ok", label: "実施" },
135
+ { value: "warn", label: "要改善" },
136
+ { value: "none", label: "未実施" },
137
+ ]}
138
+ />
139
+ </Flex>
140
+ </CardContent>
141
+ </Card>
142
+
143
+ <Card>
144
+ <CardHeader>
145
+ <CardTitle level={2}>短い記号を出して、長い名前を読ませる</CardTitle>
146
+ <CardDescription>
147
+ `label` は ReactNode なので、見える記号を aria-hidden にして VisuallyHidden に
148
+ 読み上げ名を置けます。専用の prop は要りません。○ と読み上げられても意味が伝わらない
149
+ ため、色や記号だけに意味を持たせないという WCAG 1.4.1 の要求そのものです。
150
+ </CardDescription>
151
+ </CardHeader>
152
+ <CardContent>
153
+ <Segmented
154
+ aria-label="監査結果"
155
+ defaultValue="ok"
156
+ options={[
157
+ {
158
+ value: "ok",
159
+ label: (
160
+ <>
161
+ <span aria-hidden="true">○</span>
162
+ <VisuallyHidden>実施</VisuallyHidden>
163
+ </>
164
+ ),
165
+ },
166
+ {
167
+ value: "warn",
168
+ label: (
169
+ <>
170
+ <span aria-hidden="true">△</span>
171
+ <VisuallyHidden>要改善</VisuallyHidden>
172
+ </>
173
+ ),
174
+ },
175
+ {
176
+ value: "none",
177
+ label: (
178
+ <>
179
+ <span aria-hidden="true">×</span>
180
+ <VisuallyHidden>未実施</VisuallyHidden>
181
+ </>
182
+ ),
183
+ },
184
+ ]}
185
+ />
186
+ </CardContent>
187
+ </Card>
188
+
87
189
  <Card>
88
190
  <CardHeader>
89
191
  <CardTitle level={2}>フォームの中で</CardTitle>