@godxjp/ui 20.2.1 → 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 (141) 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/chat-bubble.d.ts +30 -0
  5. package/dist/components/data-display/chat-bubble.js +229 -0
  6. package/dist/components/data-display/data-table.d.ts +18 -2
  7. package/dist/components/data-display/data-table.js +4 -0
  8. package/dist/components/data-display/descriptions.js +4 -1
  9. package/dist/components/data-display/index.d.ts +5 -3
  10. package/dist/components/data-display/index.js +5 -2
  11. package/dist/components/data-display/popover.d.ts +19 -1
  12. package/dist/components/data-display/popover.js +12 -3
  13. package/dist/components/data-display/progress.d.ts +37 -0
  14. package/dist/components/data-display/progress.js +57 -5
  15. package/dist/components/data-display/scroll-area.js +26 -1
  16. package/dist/components/data-display/tree.d.ts +8 -0
  17. package/dist/components/data-display/tree.js +426 -0
  18. package/dist/components/data-entry/chat-composer.d.ts +50 -0
  19. package/dist/components/data-entry/chat-composer.js +163 -0
  20. package/dist/components/data-entry/chat-suggestion.d.ts +28 -0
  21. package/dist/components/data-entry/chat-suggestion.js +285 -0
  22. package/dist/components/data-entry/date-picker.d.ts +10 -3
  23. package/dist/components/data-entry/date-picker.js +503 -238
  24. package/dist/components/data-entry/form-field.d.ts +1 -1
  25. package/dist/components/data-entry/form-field.js +4 -1
  26. package/dist/components/data-entry/form.js +10 -1
  27. package/dist/components/data-entry/index.d.ts +4 -6
  28. package/dist/components/data-entry/index.js +4 -6
  29. package/dist/components/data-entry/label.d.ts +14 -1
  30. package/dist/components/data-entry/label.js +6 -1
  31. package/dist/components/data-entry/radio.d.ts +27 -6
  32. package/dist/components/data-entry/radio.js +88 -43
  33. package/dist/components/data-entry/search-select.js +8 -1
  34. package/dist/components/data-entry/select.js +2 -0
  35. package/dist/components/data-entry/switch.d.ts +6 -3
  36. package/dist/components/data-entry/switch.js +34 -11
  37. package/dist/components/data-entry/textarea.js +3 -1
  38. package/dist/components/data-entry/tree-utils.d.ts +10 -48
  39. package/dist/components/data-entry/tree-utils.js +1 -154
  40. package/dist/components/layout/mobile-shell.d.ts +1 -1
  41. package/dist/components/layout/mobile-shell.js +2 -0
  42. package/dist/components/navigation/menubar.d.ts +10 -3
  43. package/dist/components/navigation/steps.d.ts +1 -1
  44. package/dist/components/navigation/steps.js +3 -1
  45. package/dist/components/navigation/tabs-scroll.d.ts +27 -0
  46. package/dist/components/navigation/tabs-scroll.js +52 -1
  47. package/dist/components/navigation/tabs.d.ts +2 -2
  48. package/dist/components/navigation/tabs.js +88 -23
  49. package/dist/components/ui/index.d.ts +0 -1
  50. package/dist/components/ui/index.js +0 -1
  51. package/dist/components/ui/segmented.d.ts +15 -1
  52. package/dist/components/ui/segmented.js +13 -6
  53. package/dist/i18n/messages/en.json +48 -2
  54. package/dist/i18n/messages/ja.json +46 -2
  55. package/dist/i18n/messages/vi.json +47 -2
  56. package/dist/lib/datetime/picker-format.d.ts +18 -0
  57. package/dist/lib/datetime/picker-format.js +35 -1
  58. package/dist/lib/field-a11y.d.ts +12 -0
  59. package/dist/lib/field-a11y.js +14 -1
  60. package/dist/lib/tree.d.ts +53 -0
  61. package/dist/lib/tree.js +155 -0
  62. package/dist/props/components/data-display.prop.d.ts +194 -1
  63. package/dist/props/components/data-entry.prop.d.ts +307 -99
  64. package/dist/props/components/index.d.ts +1 -1
  65. package/dist/props/components/layout.prop.d.ts +12 -1
  66. package/dist/props/components/navigation.prop.d.ts +76 -1
  67. package/dist/props/registry.d.ts +156 -19
  68. package/dist/props/registry.js +218 -18
  69. package/dist/props/vocabulary/data.prop.d.ts +12 -0
  70. package/dist/props/vocabulary/index.d.ts +2 -2
  71. package/dist/props/vocabulary/layout.prop.d.ts +11 -0
  72. package/dist/styles/badge-layout.css +4 -0
  73. package/dist/styles/card-layout.css +6 -6
  74. package/dist/styles/control.css +22 -6
  75. package/dist/styles/data-display-layout.css +385 -68
  76. package/dist/styles/data-entry-layout.css +64 -0
  77. package/dist/styles/focus-ring.css +4 -2
  78. package/dist/styles/navigation-layout.css +25 -0
  79. package/dist/styles/shell-layout.css +7 -1
  80. package/dist/styles/table-layout.css +34 -0
  81. package/dist/tokens/base.css +3 -0
  82. package/dist/tokens/components/chat-bubble.css +36 -0
  83. package/dist/tokens/components/chat-composer.css +19 -0
  84. package/dist/tokens/components/data-display.css +9 -6
  85. package/dist/tokens/components/descriptions.css +4 -0
  86. package/dist/tokens/components/navigation.css +6 -0
  87. package/dist/tokens/components/segmented.css +0 -2
  88. package/dist/tokens/components/shell.css +3 -0
  89. package/dist/tokens/components/table.css +3 -0
  90. package/dist/tokens/components/tree.css +27 -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/FRAME-COVERAGE-LEDGER.md +1 -1
  95. package/docs/FRAME-COVERAGE-REPORT.md +9 -7
  96. package/docs/TOKENS.md +65 -0
  97. package/docs/data-display/badge.tsx +37 -0
  98. package/docs/data-display/chat-bubble.tsx +397 -0
  99. package/docs/data-display/data-table/index.tsx +18 -0
  100. package/docs/data-display/popover.tsx +41 -1
  101. package/docs/data-display/progress.tsx +28 -0
  102. package/docs/data-display/timeline.tsx +46 -0
  103. package/docs/data-display/tree.tsx +394 -0
  104. package/docs/data-entry/calendar.tsx +2 -2
  105. package/docs/data-entry/chat-composer.tsx +464 -0
  106. package/docs/data-entry/chat-suggestion.tsx +301 -0
  107. package/docs/data-entry/date-picker.tsx +143 -3
  108. package/docs/data-entry/form/examples/invoice-form.tsx +2 -2
  109. package/docs/data-entry/form-field/examples/a11y-contract.tsx +3 -5
  110. package/docs/data-entry/form.tsx +4 -4
  111. package/docs/data-entry/number-input.tsx +31 -0
  112. package/docs/data-entry/segmented.tsx +103 -1
  113. package/docs/layout/mobile-shell.tsx +5 -0
  114. package/docs/navigation/tabs.tsx +149 -0
  115. package/docs/roadmap/ai-chat-components.md +207 -0
  116. package/docs/roadmap/antd-parity.md +155 -0
  117. package/docs/roadmap/badge-tag-chip-count.md +172 -0
  118. package/docs/roadmap/list-masonry.md +159 -0
  119. package/docs/roadmap/parity-audit-data-display-feedback.md +567 -0
  120. package/docs/roadmap/parity-audit-data-entry.md +357 -0
  121. package/docs/roadmap/parity-audit-layout-navigation-general.md +464 -0
  122. package/docs/roadmap/parity-backlog.md +78 -0
  123. package/docs/roadmap/tree-components.md +151 -0
  124. package/docs/showcase/acme-portal.tsx +7 -0
  125. package/docs/showcase/table-tree-rows.tsx +4 -4
  126. package/package.json +13 -23
  127. package/scripts/ui-audit.mjs +17 -4
  128. package/dist/components/data-display/tree-list.d.ts +0 -13
  129. package/dist/components/data-display/tree-list.js +0 -26
  130. package/dist/components/data-entry/date-range-picker.d.ts +0 -11
  131. package/dist/components/data-entry/date-range-picker.js +0 -349
  132. package/dist/components/data-entry/month-picker.d.ts +0 -10
  133. package/dist/components/data-entry/month-picker.js +0 -241
  134. package/dist/components/data-entry/month-range-picker.d.ts +0 -10
  135. package/dist/components/data-entry/month-range-picker.js +0 -301
  136. package/dist/components/ui/date-range-picker.d.ts +0 -1
  137. package/dist/components/ui/date-range-picker.js +0 -2
  138. package/docs/data-display/tree-list.tsx +0 -107
  139. package/docs/data-entry/date-range-picker.tsx +0 -175
  140. package/docs/data-entry/month-picker.tsx +0 -101
  141. package/docs/data-entry/month-range-picker.tsx +0 -115
@@ -0,0 +1,301 @@
1
+ import { useState } from "react";
2
+
3
+ import {
4
+ Avatar,
5
+ AvatarFallback,
6
+ Card,
7
+ CardContent,
8
+ CardDescription,
9
+ CardHeader,
10
+ CardTitle,
11
+ } from "@godxjp/ui/data-display";
12
+ import {
13
+ ChatComposer,
14
+ ChatSuggestion,
15
+ type ChatSuggestionItemProp,
16
+ } from "@godxjp/ui/data-entry";
17
+ import { Text } from "@godxjp/ui/general";
18
+ import {
19
+ AppShell,
20
+ Flex,
21
+ PageContainer,
22
+ Sidebar,
23
+ type SidebarSectionProp,
24
+ Topbar,
25
+ } from "@godxjp/ui/layout";
26
+ import { AtSign, Bot, Hash, MessageSquare, Settings, Slash, Users } from "lucide-react";
27
+
28
+ /**
29
+ * ChatSuggestion — the trigger-character list that completes a draft in place.
30
+ *
31
+ * It owns the part `Command` does not: watching a TEXTAREA for a trigger character at the caret,
32
+ * tracking the query as the caret moves, and closing on Escape, on blur and on a word break —
33
+ * while the list itself stays the real `Command` inside a real `Popover`, so the listbox ARIA is
34
+ * cmdk's and not hand-rolled.
35
+ *
36
+ * Every card here is one axis at rest, so the whole API is visible without clicking:
37
+ * `triggerCharacter`, one level of `children`, a disabled row, an empty result, and the
38
+ * open/defaultOpen/onOpenChange triad. `ChatComposer`'s own page shows it wired into a live
39
+ * transcript; this page is about the completion behaviour alone.
40
+ *
41
+ * Composed only from real @godxjp/ui components.
42
+ */
43
+ const sections: SidebarSectionProp[] = [
44
+ {
45
+ label: "アシスタント",
46
+ items: [
47
+ { id: "chat", label: "チャット", icon: MessageSquare },
48
+ { id: "agents", label: "エージェント", icon: Bot },
49
+ { id: "members", label: "メンバー", icon: Users },
50
+ ],
51
+ },
52
+ { label: "管理", items: [{ id: "settings", label: "設定", icon: Settings }] },
53
+ ];
54
+
55
+ /** Slash commands — the `template` row carries one level of `children`. */
56
+ const COMMANDS: ChatSuggestionItemProp[] = [
57
+ { value: "summarize", label: "要約する", description: "このスレッドを3行で要約します" },
58
+ { value: "translate", label: "翻訳する", description: "英語 ⇄ 日本語" },
59
+ { value: "explain", label: "詳しく説明する" },
60
+ {
61
+ value: "archive",
62
+ label: "アーカイブする",
63
+ description: "権限がないため選択できません",
64
+ disabled: true,
65
+ },
66
+ {
67
+ value: "template",
68
+ label: "テンプレート",
69
+ description: "定型文を挿入します",
70
+ children: [
71
+ { value: "minutes", label: "議事録" },
72
+ { value: "weekly", label: "週報" },
73
+ ],
74
+ },
75
+ ];
76
+
77
+ const MEMBERS: ChatSuggestionItemProp[] = [
78
+ { value: "sato", label: "佐藤 花子", description: "経理" },
79
+ { value: "tanaka", label: "田中 太郎", description: "開発" },
80
+ { value: "nguyen", label: "Nguyễn Minh", description: "Design" },
81
+ ];
82
+
83
+ const CHANNELS: ChatSuggestionItemProp[] = [
84
+ { value: "general", label: "general", description: "全社アナウンス" },
85
+ { value: "accounting", label: "accounting", description: "経理チーム" },
86
+ ];
87
+
88
+ export default function Demo() {
89
+ const [slashDraft, setSlashDraft] = useState("");
90
+ const [mentionDraft, setMentionDraft] = useState("");
91
+ const [channelDraft, setChannelDraft] = useState("");
92
+ const [emptyDraft, setEmptyDraft] = useState("");
93
+ const [controlledDraft, setControlledDraft] = useState("");
94
+ const [controlledOpen, setControlledOpen] = useState(false);
95
+
96
+ return (
97
+ <AppShell
98
+ sidebar={
99
+ <Sidebar
100
+ activeId="chat"
101
+ sections={sections}
102
+ onSelect={() => {}}
103
+ product={{ name: "CoreDesk", role: "アシスタント", color: "hsl(var(--primary))" }}
104
+ />
105
+ }
106
+ topbar={
107
+ <Topbar
108
+ start={
109
+ <Avatar className="rounded-md">
110
+ <AvatarFallback className="bg-primary text-primary-foreground font-bold">
111
+ C
112
+ </AvatarFallback>
113
+ </Avatar>
114
+ }
115
+ />
116
+ }
117
+ >
118
+ <PageContainer
119
+ title="入力補完"
120
+ subtitle="ChatSuggestion · トリガー文字 / 階層 / 無効行 / 該当なし / 制御された開閉"
121
+ breadcrumb={[{ label: "アシスタント", to: "#" }, { label: "入力補完" }]}
122
+ >
123
+ <Flex direction="col" gap="lg">
124
+ {/* ── 1. The default trigger, and one level of children ────────────────────────── */}
125
+ <Card>
126
+ <CardHeader>
127
+ <CardTitle level={2}>
128
+ <Slash aria-hidden="true" />
129
+ スラッシュコマンド
130
+ </CardTitle>
131
+ <CardDescription>
132
+ 「/」を入力すると開きます。「テンプレート」は子を持つので、Enter
133
+ はまだ確定せず一段深く入ります。Escape
134
+ は閉じるだけで、入力済みの文字はそのまま残ります。
135
+ </CardDescription>
136
+ </CardHeader>
137
+ <CardContent>
138
+ <ChatSuggestion
139
+ items={COMMANDS}
140
+ onValueChange={(value) => setSlashDraft(`/${value} `)}
141
+ >
142
+ {({ onTrigger, onKeyDown }) => (
143
+ <ChatComposer
144
+ id="slash-composer"
145
+ aria-label="スラッシュコマンドを試す"
146
+ value={slashDraft}
147
+ onValueChange={(next) => {
148
+ setSlashDraft(next);
149
+ onTrigger(next);
150
+ }}
151
+ onKeyDown={onKeyDown}
152
+ onSubmit={() => setSlashDraft("")}
153
+ placeholder="「/」でコマンド一覧"
154
+ />
155
+ )}
156
+ </ChatSuggestion>
157
+ </CardContent>
158
+ </Card>
159
+
160
+ {/* ── 2. triggerCharacter="@" ──────────────────────────────────────────────────── */}
161
+ <Card>
162
+ <CardHeader>
163
+ <CardTitle level={2}>
164
+ <AtSign aria-hidden="true" />
165
+ triggerCharacter=&quot;@&quot;
166
+ </CardTitle>
167
+ <CardDescription>
168
+ トリガー文字は差し替えられます。1つの入力欄に対して1つの
169
+ ChatSuggestion — 2種類を同時に見張るのではなく、用途ごとに別の欄にします。
170
+ </CardDescription>
171
+ </CardHeader>
172
+ <CardContent>
173
+ <ChatSuggestion
174
+ items={MEMBERS}
175
+ triggerCharacter="@"
176
+ onValueChange={(value) => setMentionDraft(`@${value} `)}
177
+ >
178
+ {({ onTrigger, onKeyDown }) => (
179
+ <ChatComposer
180
+ id="mention-composer"
181
+ aria-label="メンバーをメンション"
182
+ value={mentionDraft}
183
+ onValueChange={(next) => {
184
+ setMentionDraft(next);
185
+ onTrigger(next);
186
+ }}
187
+ onKeyDown={onKeyDown}
188
+ onSubmit={() => setMentionDraft("")}
189
+ placeholder="「@」でメンバー一覧"
190
+ />
191
+ )}
192
+ </ChatSuggestion>
193
+ </CardContent>
194
+ </Card>
195
+
196
+ {/* ── 3. triggerCharacter="#" ──────────────────────────────────────────────────── */}
197
+ <Card>
198
+ <CardHeader>
199
+ <CardTitle level={2}>
200
+ <Hash aria-hidden="true" />
201
+ triggerCharacter=&quot;#&quot;
202
+ </CardTitle>
203
+ <CardDescription>チャンネル参照。項目が少なくても扱いは同じです。</CardDescription>
204
+ </CardHeader>
205
+ <CardContent>
206
+ <ChatSuggestion
207
+ items={CHANNELS}
208
+ triggerCharacter="#"
209
+ onValueChange={(value) => setChannelDraft(`#${value} `)}
210
+ >
211
+ {({ onTrigger, onKeyDown }) => (
212
+ <ChatComposer
213
+ id="channel-composer"
214
+ aria-label="チャンネルを参照"
215
+ value={channelDraft}
216
+ onValueChange={(next) => {
217
+ setChannelDraft(next);
218
+ onTrigger(next);
219
+ }}
220
+ onKeyDown={onKeyDown}
221
+ onSubmit={() => setChannelDraft("")}
222
+ placeholder="「#」でチャンネル一覧"
223
+ size="sm"
224
+ />
225
+ )}
226
+ </ChatSuggestion>
227
+ </CardContent>
228
+ </Card>
229
+
230
+ {/* ── 4. No items — the empty state is the component's, not the caller's ───────── */}
231
+ <Card>
232
+ <CardHeader>
233
+ <CardTitle level={2}>該当なし</CardTitle>
234
+ <CardDescription>
235
+ 候補が0件でもパネルは開き、件数は読み上げ用の status
236
+ で通知されます。空の list を黙って閉じると、利用者には「効かない」ように見えます。
237
+ </CardDescription>
238
+ </CardHeader>
239
+ <CardContent>
240
+ <ChatSuggestion items={[]} onValueChange={() => {}}>
241
+ {({ onTrigger, onKeyDown }) => (
242
+ <ChatComposer
243
+ id="empty-composer"
244
+ aria-label="候補が0件の例"
245
+ value={emptyDraft}
246
+ onValueChange={(next) => {
247
+ setEmptyDraft(next);
248
+ onTrigger(next);
249
+ }}
250
+ onKeyDown={onKeyDown}
251
+ onSubmit={() => setEmptyDraft("")}
252
+ placeholder="「/」を押しても候補はありません"
253
+ />
254
+ )}
255
+ </ChatSuggestion>
256
+ </CardContent>
257
+ </Card>
258
+
259
+ {/* ── 5. open / onOpenChange — the controlled triad ────────────────────────────── */}
260
+ <Card>
261
+ <CardHeader>
262
+ <CardTitle level={2}>open · onOpenChange</CardTitle>
263
+ <CardDescription>
264
+ 開閉も制御できます。下の行は現在の状態をそのまま映したもので、
265
+ トリガー文字・Escape・語の区切りのいずれでも同じ値が動きます。
266
+ </CardDescription>
267
+ </CardHeader>
268
+ <CardContent>
269
+ <Flex direction="col" gap="sm">
270
+ <ChatSuggestion
271
+ items={COMMANDS}
272
+ open={controlledOpen}
273
+ onOpenChange={setControlledOpen}
274
+ onValueChange={(value) => setControlledDraft(`/${value} `)}
275
+ >
276
+ {({ onTrigger, onKeyDown }) => (
277
+ <ChatComposer
278
+ id="controlled-composer"
279
+ aria-label="開閉を制御した例"
280
+ value={controlledDraft}
281
+ onValueChange={(next) => {
282
+ setControlledDraft(next);
283
+ onTrigger(next);
284
+ }}
285
+ onKeyDown={onKeyDown}
286
+ onSubmit={() => setControlledDraft("")}
287
+ placeholder="「/」で開き、Escape で閉じます"
288
+ />
289
+ )}
290
+ </ChatSuggestion>
291
+ <Text size="xs" tone="muted">
292
+ open = {controlledOpen ? "true" : "false"}
293
+ </Text>
294
+ </Flex>
295
+ </CardContent>
296
+ </Card>
297
+ </Flex>
298
+ </PageContainer>
299
+ </AppShell>
300
+ );
301
+ }
@@ -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>