@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.
- package/dist/app/app-provider.js +2 -1
- package/dist/components/data-display/badge.d.ts +28 -1
- package/dist/components/data-display/badge.js +2 -0
- package/dist/components/data-display/chat-bubble.d.ts +30 -0
- package/dist/components/data-display/chat-bubble.js +229 -0
- package/dist/components/data-display/data-table.d.ts +18 -2
- package/dist/components/data-display/data-table.js +4 -0
- package/dist/components/data-display/descriptions.js +4 -1
- package/dist/components/data-display/index.d.ts +5 -3
- package/dist/components/data-display/index.js +5 -2
- package/dist/components/data-display/popover.d.ts +19 -1
- package/dist/components/data-display/popover.js +12 -3
- package/dist/components/data-display/progress.d.ts +37 -0
- package/dist/components/data-display/progress.js +57 -5
- package/dist/components/data-display/scroll-area.js +26 -1
- package/dist/components/data-display/tree.d.ts +8 -0
- package/dist/components/data-display/tree.js +426 -0
- package/dist/components/data-entry/chat-composer.d.ts +50 -0
- package/dist/components/data-entry/chat-composer.js +163 -0
- package/dist/components/data-entry/chat-suggestion.d.ts +28 -0
- package/dist/components/data-entry/chat-suggestion.js +285 -0
- package/dist/components/data-entry/date-picker.d.ts +10 -3
- package/dist/components/data-entry/date-picker.js +503 -238
- package/dist/components/data-entry/form-field.d.ts +1 -1
- package/dist/components/data-entry/form-field.js +4 -1
- package/dist/components/data-entry/form.js +10 -1
- package/dist/components/data-entry/index.d.ts +4 -6
- package/dist/components/data-entry/index.js +4 -6
- package/dist/components/data-entry/label.d.ts +14 -1
- package/dist/components/data-entry/label.js +6 -1
- package/dist/components/data-entry/radio.d.ts +27 -6
- package/dist/components/data-entry/radio.js +88 -43
- package/dist/components/data-entry/search-select.js +8 -1
- package/dist/components/data-entry/select.js +2 -0
- package/dist/components/data-entry/switch.d.ts +6 -3
- package/dist/components/data-entry/switch.js +34 -11
- package/dist/components/data-entry/textarea.js +3 -1
- package/dist/components/data-entry/tree-utils.d.ts +10 -48
- package/dist/components/data-entry/tree-utils.js +1 -154
- package/dist/components/layout/mobile-shell.d.ts +1 -1
- package/dist/components/layout/mobile-shell.js +2 -0
- package/dist/components/navigation/menubar.d.ts +10 -3
- package/dist/components/navigation/steps.d.ts +1 -1
- package/dist/components/navigation/steps.js +3 -1
- package/dist/components/navigation/tabs-scroll.d.ts +27 -0
- package/dist/components/navigation/tabs-scroll.js +52 -1
- package/dist/components/navigation/tabs.d.ts +2 -2
- package/dist/components/navigation/tabs.js +88 -23
- package/dist/components/ui/index.d.ts +0 -1
- package/dist/components/ui/index.js +0 -1
- package/dist/components/ui/segmented.d.ts +15 -1
- package/dist/components/ui/segmented.js +13 -6
- package/dist/i18n/messages/en.json +48 -2
- package/dist/i18n/messages/ja.json +46 -2
- package/dist/i18n/messages/vi.json +47 -2
- package/dist/lib/datetime/picker-format.d.ts +18 -0
- package/dist/lib/datetime/picker-format.js +35 -1
- package/dist/lib/field-a11y.d.ts +12 -0
- package/dist/lib/field-a11y.js +14 -1
- package/dist/lib/tree.d.ts +53 -0
- package/dist/lib/tree.js +155 -0
- package/dist/props/components/data-display.prop.d.ts +194 -1
- package/dist/props/components/data-entry.prop.d.ts +307 -99
- package/dist/props/components/index.d.ts +1 -1
- package/dist/props/components/layout.prop.d.ts +12 -1
- package/dist/props/components/navigation.prop.d.ts +76 -1
- package/dist/props/registry.d.ts +156 -19
- package/dist/props/registry.js +218 -18
- package/dist/props/vocabulary/data.prop.d.ts +12 -0
- package/dist/props/vocabulary/index.d.ts +2 -2
- package/dist/props/vocabulary/layout.prop.d.ts +11 -0
- package/dist/styles/badge-layout.css +4 -0
- package/dist/styles/card-layout.css +6 -6
- package/dist/styles/control.css +22 -6
- package/dist/styles/data-display-layout.css +385 -68
- package/dist/styles/data-entry-layout.css +64 -0
- package/dist/styles/focus-ring.css +4 -2
- package/dist/styles/navigation-layout.css +25 -0
- package/dist/styles/shell-layout.css +7 -1
- package/dist/styles/table-layout.css +34 -0
- package/dist/tokens/base.css +3 -0
- package/dist/tokens/components/chat-bubble.css +36 -0
- package/dist/tokens/components/chat-composer.css +19 -0
- package/dist/tokens/components/data-display.css +9 -6
- package/dist/tokens/components/descriptions.css +4 -0
- package/dist/tokens/components/navigation.css +6 -0
- package/dist/tokens/components/segmented.css +0 -2
- package/dist/tokens/components/shell.css +3 -0
- package/dist/tokens/components/table.css +3 -0
- package/dist/tokens/components/tree.css +27 -0
- package/dist/tokens/foundation.css +7 -0
- package/docs/COMPONENTS.md +14 -14
- package/docs/CONSUMER-RULES.md +24 -1
- package/docs/FRAME-COVERAGE-LEDGER.md +1 -1
- package/docs/FRAME-COVERAGE-REPORT.md +9 -7
- package/docs/TOKENS.md +65 -0
- package/docs/data-display/badge.tsx +37 -0
- package/docs/data-display/chat-bubble.tsx +397 -0
- package/docs/data-display/data-table/index.tsx +18 -0
- package/docs/data-display/popover.tsx +41 -1
- package/docs/data-display/progress.tsx +28 -0
- package/docs/data-display/timeline.tsx +46 -0
- package/docs/data-display/tree.tsx +394 -0
- package/docs/data-entry/calendar.tsx +2 -2
- package/docs/data-entry/chat-composer.tsx +464 -0
- package/docs/data-entry/chat-suggestion.tsx +301 -0
- package/docs/data-entry/date-picker.tsx +143 -3
- package/docs/data-entry/form/examples/invoice-form.tsx +2 -2
- package/docs/data-entry/form-field/examples/a11y-contract.tsx +3 -5
- package/docs/data-entry/form.tsx +4 -4
- package/docs/data-entry/number-input.tsx +31 -0
- package/docs/data-entry/segmented.tsx +103 -1
- package/docs/layout/mobile-shell.tsx +5 -0
- package/docs/navigation/tabs.tsx +149 -0
- package/docs/roadmap/ai-chat-components.md +207 -0
- package/docs/roadmap/antd-parity.md +155 -0
- package/docs/roadmap/badge-tag-chip-count.md +172 -0
- package/docs/roadmap/list-masonry.md +159 -0
- package/docs/roadmap/parity-audit-data-display-feedback.md +567 -0
- package/docs/roadmap/parity-audit-data-entry.md +357 -0
- package/docs/roadmap/parity-audit-layout-navigation-general.md +464 -0
- package/docs/roadmap/parity-backlog.md +78 -0
- package/docs/roadmap/tree-components.md +151 -0
- package/docs/showcase/acme-portal.tsx +7 -0
- package/docs/showcase/table-tree-rows.tsx +4 -4
- package/package.json +13 -23
- package/scripts/ui-audit.mjs +17 -4
- package/dist/components/data-display/tree-list.d.ts +0 -13
- package/dist/components/data-display/tree-list.js +0 -26
- package/dist/components/data-entry/date-range-picker.d.ts +0 -11
- package/dist/components/data-entry/date-range-picker.js +0 -349
- package/dist/components/data-entry/month-picker.d.ts +0 -10
- package/dist/components/data-entry/month-picker.js +0 -241
- package/dist/components/data-entry/month-range-picker.d.ts +0 -10
- package/dist/components/data-entry/month-range-picker.js +0 -301
- package/dist/components/ui/date-range-picker.d.ts +0 -1
- package/dist/components/ui/date-range-picker.js +0 -2
- package/docs/data-display/tree-list.tsx +0 -107
- package/docs/data-entry/date-range-picker.tsx +0 -175
- package/docs/data-entry/month-picker.tsx +0 -101
- 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="@"
|
|
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="#"
|
|
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 —
|
|
10
|
-
*
|
|
11
|
-
*
|
|
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="period" は period_from / period_to として
|
|
288
|
+
ISO 送信される。 開始 > 終了で入力しても 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="month" の期間は月グリッドで選び、
|
|
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
|
-
<
|
|
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
|
|
133
|
-
<
|
|
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
|
-
<
|
|
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={() => {}} />
|
package/docs/data-entry/form.tsx
CHANGED
|
@@ -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 /
|
|
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
|
-
<
|
|
485
|
+
<DatePicker
|
|
486
|
+
range
|
|
487
487
|
id="f-period"
|
|
488
488
|
name="period"
|
|
489
489
|
value={period}
|
|
490
490
|
onValueChange={setPeriod}
|
|
491
|
-
// 土日は選べない。
|
|
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>
|