@godxjp/ui 27.0.0 → 27.3.1
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/components/data-display/badge.d.ts +10 -3
- package/dist/components/data-display/badge.js +14 -3
- package/dist/components/data-display/data-table.d.ts +20 -12
- package/dist/components/data-display/data-table.js +53 -5
- package/dist/components/data-display/range-timeline.d.ts +16 -0
- package/dist/components/data-display/range-timeline.js +115 -80
- package/dist/components/data-display/table.d.ts +16 -0
- package/dist/components/data-display/table.js +2 -0
- package/dist/components/data-entry/checkbox.js +23 -5
- package/dist/components/data-entry/command.d.ts +3 -1
- package/dist/components/data-entry/command.js +10 -2
- package/dist/components/data-entry/form-errors.d.ts +5 -0
- package/dist/components/data-entry/form-errors.js +6 -0
- package/dist/components/data-entry/form-field.js +9 -8
- package/dist/components/data-entry/index.d.ts +1 -0
- package/dist/components/data-entry/search-select.js +145 -136
- package/dist/components/data-entry/select.js +38 -3
- package/dist/components/feedback/dialog.d.ts +4 -3
- package/dist/components/feedback/dialog.js +46 -19
- package/dist/components/feedback/non-modal-layer.d.ts +16 -0
- package/dist/components/feedback/non-modal-layer.js +37 -0
- package/dist/components/feedback/sheet.d.ts +7 -4
- package/dist/components/feedback/sheet.js +63 -40
- package/dist/components/layout/org-switcher.js +1 -1
- package/dist/components/layout/responsive-grid.d.ts +8 -2
- package/dist/components/layout/responsive-grid.js +2 -0
- package/dist/components/query/mutation-feedback.js +2 -4
- package/dist/contracts/measurement.json +1 -1
- package/dist/form/form-field-control.d.ts +1 -1
- package/dist/form/form-field-control.js +32 -23
- package/dist/form/form-root.d.ts +1 -1
- package/dist/form/form-root.js +25 -6
- package/dist/i18n/messages/en.json +2 -1
- package/dist/i18n/messages/ja.json +2 -1
- package/dist/i18n/messages/vi.json +2 -1
- package/dist/props/components/data-display.prop.d.ts +12 -0
- package/dist/props/components/data-entry.prop.d.ts +10 -0
- package/dist/props/components/feedback.prop.d.ts +7 -1
- package/dist/props/components/form.prop.d.ts +52 -3
- package/dist/props/components/index.d.ts +1 -1
- package/dist/props/components/layout.prop.d.ts +7 -0
- package/dist/props/registry.d.ts +26 -3
- package/dist/props/registry.js +25 -2
- package/dist/props/vocabulary/data.prop.d.ts +27 -1
- package/dist/props/vocabulary/index.d.ts +1 -1
- package/dist/styles/control.css +44 -1
- package/dist/styles/data-display-layout.css +58 -1
- package/dist/styles/form-layout.css +21 -0
- package/dist/styles/layout.css +7 -0
- package/dist/styles/navigation-layout.css +5 -1
- package/dist/styles/shell-layout.css +1 -14
- package/dist/styles/table-layout.css +80 -8
- package/dist/tokens/components/control.css +5 -0
- package/dist/tokens/components/data-display.css +6 -0
- package/dist/tokens/components/navigation.css +1 -1
- package/dist/tokens/components/table.css +2 -0
- package/docs/DESIGN-AUTHORITY.md +32 -1
- package/docs/FORMS.md +11 -10
- package/docs/data-display/data-table/examples/server-paged.tsx +156 -0
- package/docs/data-display/data-table/examples/striped-list.tsx +173 -0
- package/docs/data-display/popover.tsx +43 -1
- package/docs/data-display/timeline.tsx +73 -9
- package/docs/data-entry/command.tsx +80 -1
- package/docs/data-entry/form-root.tsx +12 -2
- package/docs/feedback/dialog.tsx +82 -0
- package/docs/feedback/sheet.tsx +84 -0
- package/docs/layout/responsive-grid.tsx +100 -0
- package/package.json +4 -2
|
@@ -91,6 +91,10 @@ export default function Demo() {
|
|
|
91
91
|
new Intl.DateTimeFormat(locale, { month: "short", day: "numeric", timeZone: "UTC" }).format(
|
|
92
92
|
Date.UTC(2026, 8, day),
|
|
93
93
|
);
|
|
94
|
+
const monthLabel = (month: number) =>
|
|
95
|
+
new Intl.DateTimeFormat(locale, { year: "numeric", month: "long", timeZone: "UTC" }).format(
|
|
96
|
+
Date.UTC(2026, month, 1),
|
|
97
|
+
);
|
|
94
98
|
return (
|
|
95
99
|
<PageContainer title="Timeline" subtitle="現在のステップを強調する縦型のイベント一覧">
|
|
96
100
|
<Flex direction="col" gap="lg">
|
|
@@ -162,23 +166,83 @@ export default function Demo() {
|
|
|
162
166
|
<Card>
|
|
163
167
|
<CardHeader>
|
|
164
168
|
<CardTitle level={2}>RangeTimeline</CardTitle>
|
|
169
|
+
<CardDescription>
|
|
170
|
+
日単位の軸。行ごとの区切り線と、列ごとの縦線が本体全体に引かれます(bordered は既定で
|
|
171
|
+
true)。土日は columns[].muted
|
|
172
|
+
で本体の高さいっぱいに塗り分け、表示範囲の外にある期間は、その方向の端に矢印付きで示します。
|
|
173
|
+
</CardDescription>
|
|
165
174
|
</CardHeader>
|
|
166
175
|
<CardContent>
|
|
167
176
|
<RangeTimeline
|
|
168
177
|
label={t("rangeTimeline.schedule")}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}
|
|
178
|
+
today={8}
|
|
179
|
+
bands={[{ label: monthLabel(8), units: 14 }]}
|
|
180
|
+
columns={Array.from({ length: 14 }, (_, index) => {
|
|
181
|
+
const weekday = new Date(Date.UTC(2026, 8, index + 1)).getUTCDay();
|
|
182
|
+
return {
|
|
183
|
+
label: new Intl.NumberFormat(locale).format(index + 1),
|
|
184
|
+
units: 1,
|
|
185
|
+
muted: weekday === 0 || weekday === 6,
|
|
186
|
+
};
|
|
187
|
+
})}
|
|
188
|
+
rows={[
|
|
189
|
+
{ id: "design", label: "要件定義", start: 0, end: 3 },
|
|
190
|
+
{ id: "build", label: "実装", start: 3, end: 10 },
|
|
191
|
+
{ id: "review", label: "レビュー", start: 9, end: 12 },
|
|
192
|
+
{ id: "kickoff", label: "キックオフ(前月)", start: -12, end: -9 },
|
|
193
|
+
{ id: "release", label: "リリース(来月)", start: 20, end: 22 },
|
|
194
|
+
].map((row) => ({
|
|
195
|
+
...row,
|
|
196
|
+
startLabel: dayLabel(row.start + 1),
|
|
197
|
+
endLabel: dayLabel(row.end + 1),
|
|
198
|
+
}))}
|
|
199
|
+
/>
|
|
200
|
+
</CardContent>
|
|
201
|
+
</Card>
|
|
202
|
+
|
|
203
|
+
<Card>
|
|
204
|
+
<CardHeader>
|
|
205
|
+
<CardTitle level={2}>月単位の軸(列ごとに units が異なる)</CardTitle>
|
|
206
|
+
<CardDescription>
|
|
207
|
+
units
|
|
208
|
+
は日数です。9月30・10月31・11月30と幅が異なっても、本体の縦線はヘッダーの列の境界に揃います。
|
|
209
|
+
</CardDescription>
|
|
210
|
+
</CardHeader>
|
|
211
|
+
<CardContent>
|
|
212
|
+
<RangeTimeline
|
|
213
|
+
label={t("rangeTimeline.schedule")}
|
|
214
|
+
columns={[
|
|
215
|
+
{ label: monthLabel(8), units: 30 },
|
|
216
|
+
{ label: monthLabel(9), units: 31 },
|
|
217
|
+
{ label: monthLabel(10), units: 30 },
|
|
178
218
|
]}
|
|
219
|
+
rows={[
|
|
220
|
+
{ id: "phase-1", label: "第1期", start: 0, end: 44 },
|
|
221
|
+
{ id: "phase-2", label: "第2期", start: 45, end: 90 },
|
|
222
|
+
].map((row) => ({
|
|
223
|
+
...row,
|
|
224
|
+
startLabel: dayLabel(row.start + 1),
|
|
225
|
+
endLabel: dayLabel(row.end + 1),
|
|
226
|
+
}))}
|
|
227
|
+
/>
|
|
228
|
+
</CardContent>
|
|
229
|
+
</Card>
|
|
230
|
+
|
|
231
|
+
<Card>
|
|
232
|
+
<CardHeader>
|
|
233
|
+
<CardTitle level={2}>罫線なし(bordered=false)</CardTitle>
|
|
234
|
+
<CardDescription>
|
|
235
|
+
ヘッダーの列線だけを残し、本体の罫線を外します。muted の列の塗りは残ります。
|
|
236
|
+
</CardDescription>
|
|
237
|
+
</CardHeader>
|
|
238
|
+
<CardContent>
|
|
239
|
+
<RangeTimeline
|
|
240
|
+
label={t("rangeTimeline.schedule")}
|
|
241
|
+
bordered={false}
|
|
179
242
|
columns={Array.from({ length: 7 }, (_, index) => ({
|
|
180
243
|
label: dayLabel(index + 1),
|
|
181
244
|
units: 1,
|
|
245
|
+
muted: index === 5,
|
|
182
246
|
}))}
|
|
183
247
|
rows={[
|
|
184
248
|
{
|
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
import { useEffect, useState } from "react";
|
|
2
2
|
|
|
3
|
-
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@godxjp/ui/data-display";
|
|
4
3
|
import {
|
|
4
|
+
Card,
|
|
5
|
+
CardContent,
|
|
6
|
+
CardDescription,
|
|
7
|
+
CardHeader,
|
|
8
|
+
CardTitle,
|
|
9
|
+
Popover,
|
|
10
|
+
PopoverContent,
|
|
11
|
+
PopoverTrigger,
|
|
12
|
+
} from "@godxjp/ui/data-display";
|
|
13
|
+
import {
|
|
14
|
+
Checkbox,
|
|
5
15
|
Command,
|
|
6
16
|
CommandEmpty,
|
|
7
17
|
CommandGroup,
|
|
@@ -10,7 +20,9 @@ import {
|
|
|
10
20
|
CommandList,
|
|
11
21
|
} from "@godxjp/ui/data-entry";
|
|
12
22
|
import { Skeleton } from "@godxjp/ui/feedback";
|
|
23
|
+
import { Button } from "@godxjp/ui/general";
|
|
13
24
|
import { Flex, PageContainer } from "@godxjp/ui/layout";
|
|
25
|
+
import { ChevronDown } from "lucide-react";
|
|
14
26
|
|
|
15
27
|
/**
|
|
16
28
|
* Command — キーボードナビゲーション対応のコマンドパレット。
|
|
@@ -30,6 +42,14 @@ const VENDOR_DIRECTORY: Suggestion[] = [
|
|
|
30
42
|
{ value: "v-005", label: "田中物産" },
|
|
31
43
|
];
|
|
32
44
|
|
|
45
|
+
const MEMBERS: Suggestion[] = [
|
|
46
|
+
{ value: "m-001", label: "山田 太郎" },
|
|
47
|
+
{ value: "m-002", label: "佐藤 花子" },
|
|
48
|
+
{ value: "m-003", label: "鈴木 一郎" },
|
|
49
|
+
{ value: "m-004", label: "高橋 美咲" },
|
|
50
|
+
{ value: "m-005", label: "PHAM THAI DUONG(プロジェクト外)" },
|
|
51
|
+
];
|
|
52
|
+
|
|
33
53
|
export default function Demo() {
|
|
34
54
|
// Card 1: grouped quick-select. Controlled `value` pre-highlights an item so the
|
|
35
55
|
// [aria-selected=true] accent is visible at rest, without keyboard interaction.
|
|
@@ -44,6 +64,13 @@ export default function Demo() {
|
|
|
44
64
|
const [loading, setLoading] = useState<boolean>(true);
|
|
45
65
|
const [results, setResults] = useState<Suggestion[]>([]);
|
|
46
66
|
|
|
67
|
+
// Card 4: filter facet — a checklist, so the list is `split` (ruled rows, 0 list padding).
|
|
68
|
+
const [assignees, setAssignees] = useState<string[]>(["m-002"]);
|
|
69
|
+
const toggleAssignee = (member: string) =>
|
|
70
|
+
setAssignees((current) =>
|
|
71
|
+
current.includes(member) ? current.filter((m) => m !== member) : [...current, member],
|
|
72
|
+
);
|
|
73
|
+
|
|
47
74
|
useEffect(() => {
|
|
48
75
|
let active = true;
|
|
49
76
|
setLoading(true);
|
|
@@ -131,6 +158,58 @@ export default function Demo() {
|
|
|
131
158
|
</CardContent>
|
|
132
159
|
</Card>
|
|
133
160
|
|
|
161
|
+
<Card>
|
|
162
|
+
<CardHeader>
|
|
163
|
+
<CardTitle level={2}>担当者フィルター(split)</CardTitle>
|
|
164
|
+
<CardDescription>
|
|
165
|
+
Popover + Command に split を付けると、行間に区切り線・リスト余白 0・行は popover
|
|
166
|
+
の端まで届く一つの箱として描画されます。チェックボックスで複数選択(選択中:{" "}
|
|
167
|
+
{assignees.length} 名)。
|
|
168
|
+
</CardDescription>
|
|
169
|
+
</CardHeader>
|
|
170
|
+
<CardContent>
|
|
171
|
+
<Popover>
|
|
172
|
+
<PopoverTrigger asChild>
|
|
173
|
+
<Button variant="outline">
|
|
174
|
+
担当者{assignees.length > 0 ? ` (${assignees.length})` : ""}
|
|
175
|
+
<ChevronDown aria-hidden="true" />
|
|
176
|
+
</Button>
|
|
177
|
+
</PopoverTrigger>
|
|
178
|
+
<PopoverContent flush align="start">
|
|
179
|
+
<Command label="担当者を絞り込む" split>
|
|
180
|
+
<CommandInput placeholder="担当者 を絞り込む" />
|
|
181
|
+
<CommandList>
|
|
182
|
+
<CommandEmpty>該当する担当者がいません。</CommandEmpty>
|
|
183
|
+
<CommandGroup>
|
|
184
|
+
{MEMBERS.map((member) => {
|
|
185
|
+
const checked = assignees.includes(member.value);
|
|
186
|
+
return (
|
|
187
|
+
<CommandItem
|
|
188
|
+
key={member.value}
|
|
189
|
+
value={member.label}
|
|
190
|
+
aria-checked={checked}
|
|
191
|
+
onSelect={() => toggleAssignee(member.value)}
|
|
192
|
+
>
|
|
193
|
+
{/* The ROW is the option and owns the click; the box only mirrors it,
|
|
194
|
+
so it leaves the tab ring (TreeSelect does the same). */}
|
|
195
|
+
<Checkbox
|
|
196
|
+
checked={checked}
|
|
197
|
+
tabIndex={-1}
|
|
198
|
+
onCheckedChange={() => toggleAssignee(member.value)}
|
|
199
|
+
aria-labelledby={`assignee-${member.value}`}
|
|
200
|
+
/>
|
|
201
|
+
<span id={`assignee-${member.value}`}>{member.label}</span>
|
|
202
|
+
</CommandItem>
|
|
203
|
+
);
|
|
204
|
+
})}
|
|
205
|
+
</CommandGroup>
|
|
206
|
+
</CommandList>
|
|
207
|
+
</Command>
|
|
208
|
+
</PopoverContent>
|
|
209
|
+
</Popover>
|
|
210
|
+
</CardContent>
|
|
211
|
+
</Card>
|
|
212
|
+
|
|
134
213
|
<Card>
|
|
135
214
|
<CardHeader>
|
|
136
215
|
<CardTitle level={2}>勤怠アクションパレット(Cmd+K 想定)</CardTitle>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useState } from "react";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { Alert, AlertDescription } from "@godxjp/ui/feedback";
|
|
4
|
-
import { Input } from "@godxjp/ui/data-entry";
|
|
4
|
+
import { Checkbox, Input } from "@godxjp/ui/data-entry";
|
|
5
5
|
import { Button } from "@godxjp/ui/general";
|
|
6
6
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@godxjp/ui/data-display";
|
|
7
7
|
import { Flex, PageContainer } from "@godxjp/ui/layout";
|
|
@@ -10,12 +10,13 @@ import { FormFieldControl, FormRoot, useZodForm } from "@godxjp/ui/form";
|
|
|
10
10
|
const schema = z.object({
|
|
11
11
|
name: z.string().min(1, "組織名を入力してください"),
|
|
12
12
|
email: z.string().email("有効なメールアドレスを入力してください"),
|
|
13
|
+
is_shared: z.boolean(),
|
|
13
14
|
});
|
|
14
15
|
|
|
15
16
|
export default function Demo() {
|
|
16
17
|
const [status, setStatus] = useState<"idle" | "pending" | "success" | "server-error">("idle");
|
|
17
18
|
const form = useZodForm(schema, {
|
|
18
|
-
defaultValues: { name: "株式会社アクメ", email: "billing@example.jp" },
|
|
19
|
+
defaultValues: { name: "株式会社アクメ", email: "billing@example.jp", is_shared: false },
|
|
19
20
|
});
|
|
20
21
|
|
|
21
22
|
async function submit() {
|
|
@@ -70,6 +71,15 @@ export default function Demo() {
|
|
|
70
71
|
/>
|
|
71
72
|
)}
|
|
72
73
|
</FormFieldControl>
|
|
74
|
+
{/* Boolean field (antd `valuePropName="checked"`): the label is the Checkbox's children,
|
|
75
|
+
on the same line as the box — no label row above it. */}
|
|
76
|
+
<FormFieldControl name="is_shared" valuePropName="checked">
|
|
77
|
+
{(field) => (
|
|
78
|
+
<Checkbox {...field} disabled={status === "pending"}>
|
|
79
|
+
請求書をプロジェクトメンバーに共有する
|
|
80
|
+
</Checkbox>
|
|
81
|
+
)}
|
|
82
|
+
</FormFieldControl>
|
|
73
83
|
<Flex gap="sm" wrap>
|
|
74
84
|
<Button type="submit" loading={status === "pending"}>
|
|
75
85
|
保存
|
package/docs/feedback/dialog.tsx
CHANGED
|
@@ -34,6 +34,15 @@ const headerTones = [
|
|
|
34
34
|
"neutral",
|
|
35
35
|
] as const;
|
|
36
36
|
|
|
37
|
+
/** Order lines behind the non-modal payment dialog — they stay editable while it is open. */
|
|
38
|
+
const initialLines = [
|
|
39
|
+
{ id: "coffee", name: "ブレンドコーヒー", unitPrice: 480, quantity: 2 },
|
|
40
|
+
{ id: "sandwich", name: "ミックスサンド", unitPrice: 650, quantity: 1 },
|
|
41
|
+
{ id: "cake", name: "チーズケーキ", unitPrice: 520, quantity: 1 },
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
const yen = new Intl.NumberFormat("ja-JP", { style: "currency", currency: "JPY" });
|
|
45
|
+
|
|
37
46
|
/**
|
|
38
47
|
* Dialog · compound controlled modal for form-style flows. Always control via
|
|
39
48
|
* open + onOpenChange. Include DialogHeader > DialogTitle (required for a11y).
|
|
@@ -46,6 +55,16 @@ export default function Demo() {
|
|
|
46
55
|
const [headerTone, setHeaderTone] = useState<(typeof headerTones)[number]>("default");
|
|
47
56
|
const [terminateOpen, setTerminateOpen] = useState(false);
|
|
48
57
|
const [reason, setReason] = useState("");
|
|
58
|
+
const [paymentOpen, setPaymentOpen] = useState(false);
|
|
59
|
+
const [lines, setLines] = useState(initialLines);
|
|
60
|
+
const total = lines.reduce((sum, line) => sum + line.unitPrice * line.quantity, 0);
|
|
61
|
+
const changeQuantity = (id: string, delta: number) => {
|
|
62
|
+
setLines((current) =>
|
|
63
|
+
current.map((line) =>
|
|
64
|
+
line.id === id ? { ...line, quantity: Math.max(0, line.quantity + delta) } : line,
|
|
65
|
+
),
|
|
66
|
+
);
|
|
67
|
+
};
|
|
49
68
|
|
|
50
69
|
return (
|
|
51
70
|
<PageContainer
|
|
@@ -249,6 +268,69 @@ export default function Demo() {
|
|
|
249
268
|
</Dialog>
|
|
250
269
|
</CardContent>
|
|
251
270
|
</Card>
|
|
271
|
+
|
|
272
|
+
<Card>
|
|
273
|
+
<CardHeader>
|
|
274
|
+
<CardTitle level={2}>Non-modal dialog: the list behind stays editable</CardTitle>
|
|
275
|
+
<CardDescription>
|
|
276
|
+
modal={false} renders a non-modal dialog, a pattern WAI-ARIA APG allows. The
|
|
277
|
+
page behind keeps working: no scrim, no scroll lock, nothing hidden from assistive
|
|
278
|
+
tech, and a press outside does not close the dialog. Open the payment dialog, then
|
|
279
|
+
change a quantity below. The total inside the dialog follows. Focus moves into the
|
|
280
|
+
dialog on open, Tab can leave it, Escape closes it while focus is inside, and focus
|
|
281
|
+
returns to the trigger.
|
|
282
|
+
</CardDescription>
|
|
283
|
+
</CardHeader>
|
|
284
|
+
<CardContent>
|
|
285
|
+
<Flex direction="col" gap="md">
|
|
286
|
+
{lines.map((line) => (
|
|
287
|
+
<Flex key={line.id} direction="row" align="center" justify="between" gap="sm">
|
|
288
|
+
<Text>{line.name}</Text>
|
|
289
|
+
<Flex direction="row" align="center" gap="sm">
|
|
290
|
+
<Button
|
|
291
|
+
variant="outline"
|
|
292
|
+
size="sm"
|
|
293
|
+
aria-label={`${line.name}を減らす`}
|
|
294
|
+
disabled={line.quantity === 0}
|
|
295
|
+
onClick={() => changeQuantity(line.id, -1)}
|
|
296
|
+
>
|
|
297
|
+
−
|
|
298
|
+
</Button>
|
|
299
|
+
<Text aria-live="polite">{line.quantity}</Text>
|
|
300
|
+
<Button
|
|
301
|
+
variant="outline"
|
|
302
|
+
size="sm"
|
|
303
|
+
aria-label={`${line.name}を増やす`}
|
|
304
|
+
onClick={() => changeQuantity(line.id, 1)}
|
|
305
|
+
>
|
|
306
|
+
+
|
|
307
|
+
</Button>
|
|
308
|
+
</Flex>
|
|
309
|
+
</Flex>
|
|
310
|
+
))}
|
|
311
|
+
<Dialog modal={false} open={paymentOpen} onOpenChange={setPaymentOpen}>
|
|
312
|
+
<DialogTrigger asChild>
|
|
313
|
+
<Button size="sm">お会計へ</Button>
|
|
314
|
+
</DialogTrigger>
|
|
315
|
+
<DialogContent className="max-w-sm">
|
|
316
|
+
<DialogHeader title="お会計" subtitle="注文明細は背面で編集を続けられます。" />
|
|
317
|
+
<DialogBody>
|
|
318
|
+
<Flex direction="row" justify="between">
|
|
319
|
+
<Text tone="muted">合計</Text>
|
|
320
|
+
<Text>{yen.format(total)}</Text>
|
|
321
|
+
</Flex>
|
|
322
|
+
</DialogBody>
|
|
323
|
+
<DialogFooter>
|
|
324
|
+
<Button variant="outline" onClick={() => setPaymentOpen(false)}>
|
|
325
|
+
キャンセル
|
|
326
|
+
</Button>
|
|
327
|
+
<Button onClick={() => setPaymentOpen(false)}>支払いを確定</Button>
|
|
328
|
+
</DialogFooter>
|
|
329
|
+
</DialogContent>
|
|
330
|
+
</Dialog>
|
|
331
|
+
</Flex>
|
|
332
|
+
</CardContent>
|
|
333
|
+
</Card>
|
|
252
334
|
</Flex>
|
|
253
335
|
</PageContainer>
|
|
254
336
|
);
|
package/docs/feedback/sheet.tsx
CHANGED
|
@@ -42,6 +42,15 @@ const headerTones = [
|
|
|
42
42
|
"neutral",
|
|
43
43
|
] as const;
|
|
44
44
|
|
|
45
|
+
/** Order lines behind the non-modal sheet — they stay editable while it is open. */
|
|
46
|
+
const initialLines = [
|
|
47
|
+
{ id: "coffee", name: "ブレンドコーヒー", unitPrice: 480, quantity: 2 },
|
|
48
|
+
{ id: "sandwich", name: "ミックスサンド", unitPrice: 650, quantity: 1 },
|
|
49
|
+
{ id: "cake", name: "チーズケーキ", unitPrice: 520, quantity: 1 },
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
const yen = new Intl.NumberFormat("ja-JP", { style: "currency", currency: "JPY" });
|
|
53
|
+
|
|
45
54
|
/**
|
|
46
55
|
* Sheet · side-panel drawer (Radix Dialog). Slides in from an edge. Compose
|
|
47
56
|
* Sheet > SheetTrigger (asChild) > SheetContent(side) > SheetHeader >
|
|
@@ -84,6 +93,18 @@ export default function Demo() {
|
|
|
84
93
|
const [toneOpen, setToneOpen] = useState(false);
|
|
85
94
|
const [headerTone, setHeaderTone] = useState<(typeof headerTones)[number]>("default");
|
|
86
95
|
|
|
96
|
+
// Card 6 · non-modal sheet: the order lines behind it stay editable while it is open.
|
|
97
|
+
const [summaryOpen, setSummaryOpen] = useState(false);
|
|
98
|
+
const [lines, setLines] = useState(initialLines);
|
|
99
|
+
const total = lines.reduce((sum, line) => sum + line.unitPrice * line.quantity, 0);
|
|
100
|
+
const changeQuantity = (id: string, delta: number) => {
|
|
101
|
+
setLines((current) =>
|
|
102
|
+
current.map((line) =>
|
|
103
|
+
line.id === id ? { ...line, quantity: Math.max(0, line.quantity + delta) } : line,
|
|
104
|
+
),
|
|
105
|
+
);
|
|
106
|
+
};
|
|
107
|
+
|
|
87
108
|
return (
|
|
88
109
|
<PageContainer
|
|
89
110
|
title="Sheet"
|
|
@@ -415,6 +436,69 @@ export default function Demo() {
|
|
|
415
436
|
</Flex>
|
|
416
437
|
</CardContent>
|
|
417
438
|
</Card>
|
|
439
|
+
|
|
440
|
+
<Card>
|
|
441
|
+
<CardHeader>
|
|
442
|
+
<CardTitle level={2}>Non-modal sheet: the list behind stays editable</CardTitle>
|
|
443
|
+
<CardDescription>
|
|
444
|
+
modal={false} on Sheet renders a non-modal panel, a pattern WAI-ARIA APG
|
|
445
|
+
allows. The page behind keeps working: no scrim, no scroll lock, nothing hidden from
|
|
446
|
+
assistive tech, and a press outside does not close the panel. Open the order summary
|
|
447
|
+
(side="left", so the quantity controls stay uncovered), then change a quantity
|
|
448
|
+
below. The total inside the panel follows. Focus moves into the
|
|
449
|
+
panel on open, Tab can leave it, Escape closes it while focus is inside, and focus
|
|
450
|
+
returns to the trigger.
|
|
451
|
+
</CardDescription>
|
|
452
|
+
</CardHeader>
|
|
453
|
+
<CardContent>
|
|
454
|
+
<Flex direction="col" gap="md">
|
|
455
|
+
{lines.map((line) => (
|
|
456
|
+
<Flex key={line.id} direction="row" align="center" justify="between" gap="sm">
|
|
457
|
+
<Text>{line.name}</Text>
|
|
458
|
+
<Flex direction="row" align="center" gap="sm">
|
|
459
|
+
<Button
|
|
460
|
+
variant="outline"
|
|
461
|
+
size="sm"
|
|
462
|
+
aria-label={`${line.name}を減らす`}
|
|
463
|
+
disabled={line.quantity === 0}
|
|
464
|
+
onClick={() => changeQuantity(line.id, -1)}
|
|
465
|
+
>
|
|
466
|
+
−
|
|
467
|
+
</Button>
|
|
468
|
+
<Text aria-live="polite">{line.quantity}</Text>
|
|
469
|
+
<Button
|
|
470
|
+
variant="outline"
|
|
471
|
+
size="sm"
|
|
472
|
+
aria-label={`${line.name}を増やす`}
|
|
473
|
+
onClick={() => changeQuantity(line.id, 1)}
|
|
474
|
+
>
|
|
475
|
+
+
|
|
476
|
+
</Button>
|
|
477
|
+
</Flex>
|
|
478
|
+
</Flex>
|
|
479
|
+
))}
|
|
480
|
+
<Sheet modal={false} open={summaryOpen} onOpenChange={setSummaryOpen}>
|
|
481
|
+
<SheetTrigger asChild>
|
|
482
|
+
<Button variant="outline" size="sm">
|
|
483
|
+
注文サマリー
|
|
484
|
+
</Button>
|
|
485
|
+
</SheetTrigger>
|
|
486
|
+
<SheetContent side="left" width={360}>
|
|
487
|
+
<SheetHeader title="注文サマリー" subtitle="明細は背面で編集を続けられます。" />
|
|
488
|
+
<SheetBody>
|
|
489
|
+
<Flex direction="row" justify="between">
|
|
490
|
+
<Text tone="muted">合計</Text>
|
|
491
|
+
<Text>{yen.format(total)}</Text>
|
|
492
|
+
</Flex>
|
|
493
|
+
</SheetBody>
|
|
494
|
+
<SheetFooter>
|
|
495
|
+
<Button onClick={() => setSummaryOpen(false)}>完了</Button>
|
|
496
|
+
</SheetFooter>
|
|
497
|
+
</SheetContent>
|
|
498
|
+
</Sheet>
|
|
499
|
+
</Flex>
|
|
500
|
+
</CardContent>
|
|
501
|
+
</Card>
|
|
418
502
|
</Flex>
|
|
419
503
|
</PageContainer>
|
|
420
504
|
);
|
|
@@ -1,11 +1,20 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
1
2
|
import {
|
|
2
3
|
Card,
|
|
3
4
|
CardContent,
|
|
4
5
|
CardDescription,
|
|
5
6
|
CardHeader,
|
|
6
7
|
CardTitle,
|
|
8
|
+
ScrollArea,
|
|
7
9
|
StatCard,
|
|
8
10
|
} from "@godxjp/ui/data-display";
|
|
11
|
+
import {
|
|
12
|
+
Select,
|
|
13
|
+
SelectContent,
|
|
14
|
+
SelectItem,
|
|
15
|
+
SelectTrigger,
|
|
16
|
+
SelectValue,
|
|
17
|
+
} from "@godxjp/ui/data-entry";
|
|
9
18
|
import { Text } from "@godxjp/ui/general";
|
|
10
19
|
import { Flex, PageContainer, ResponsiveGrid, SplitPane } from "@godxjp/ui/layout";
|
|
11
20
|
|
|
@@ -20,7 +29,25 @@ import { Flex, PageContainer, ResponsiveGrid, SplitPane } from "@godxjp/ui/layou
|
|
|
20
29
|
* recognised collection shape instead of hand-rolling the breakpoint map. Composed only from
|
|
21
30
|
* real @godxjp/ui components.
|
|
22
31
|
*/
|
|
32
|
+
const KANBAN_LANES = [
|
|
33
|
+
{ key: "todo", title: "未着手" },
|
|
34
|
+
{ key: "doing", title: "進行中" },
|
|
35
|
+
{ key: "done", title: "完了" },
|
|
36
|
+
] as const;
|
|
37
|
+
|
|
38
|
+
type KanbanLane = (typeof KANBAN_LANES)[number]["key"];
|
|
39
|
+
|
|
40
|
+
const KANBAN_TASKS: { id: string; title: string; lane: KanbanLane; priority: string }[] = [
|
|
41
|
+
{ id: "T-101", title: "請求書テンプレートの改訂", lane: "todo", priority: "high" },
|
|
42
|
+
{ id: "T-102", title: "取引先マスタの重複整理", lane: "todo", priority: "medium" },
|
|
43
|
+
{ id: "T-103", title: "月次締めチェックリスト", lane: "doing", priority: "low" },
|
|
44
|
+
];
|
|
45
|
+
|
|
23
46
|
export default function Demo() {
|
|
47
|
+
// Kanban: which lane each card is in, moved by native drag and drop.
|
|
48
|
+
const [tasks, setTasks] = useState(KANBAN_TASKS);
|
|
49
|
+
const moveTask = (id: string, lane: KanbanLane) =>
|
|
50
|
+
setTasks((current) => current.map((task) => (task.id === id ? { ...task, lane } : task)));
|
|
24
51
|
return (
|
|
25
52
|
<PageContainer
|
|
26
53
|
title="ResponsiveGrid"
|
|
@@ -255,6 +282,79 @@ export default function Demo() {
|
|
|
255
282
|
</CardContent>
|
|
256
283
|
</Card>
|
|
257
284
|
|
|
285
|
+
<Card>
|
|
286
|
+
<CardHeader>
|
|
287
|
+
<CardTitle level={2}>
|
|
288
|
+
flow="columns" align="stretch" · カンバンのレーン
|
|
289
|
+
</CardTitle>
|
|
290
|
+
<CardDescription>
|
|
291
|
+
align="stretch"(antd Row
|
|
292
|
+
align)で全レーンが最も高いレーンと同じ高さになり、空の
|
|
293
|
+
「完了」レーンも全高のドロップ先になる。省略時は columns フローが start、rows フローが
|
|
294
|
+
stretch のまま。カードはドラッグでき、中の Select
|
|
295
|
+
はドラッグ画像をカードの外へ広げない。
|
|
296
|
+
</CardDescription>
|
|
297
|
+
</CardHeader>
|
|
298
|
+
<CardContent>
|
|
299
|
+
<ScrollArea orientation="horizontal">
|
|
300
|
+
<ResponsiveGrid flow="columns" align="stretch">
|
|
301
|
+
{KANBAN_LANES.map((lane) => (
|
|
302
|
+
<Card
|
|
303
|
+
key={lane.key}
|
|
304
|
+
data-lane={lane.key}
|
|
305
|
+
onDragOver={(event) => event.preventDefault()}
|
|
306
|
+
onDrop={(event) => {
|
|
307
|
+
event.preventDefault();
|
|
308
|
+
moveTask(event.dataTransfer.getData("text/plain"), lane.key);
|
|
309
|
+
}}
|
|
310
|
+
>
|
|
311
|
+
<CardHeader>
|
|
312
|
+
<CardTitle>{lane.title}</CardTitle>
|
|
313
|
+
</CardHeader>
|
|
314
|
+
<CardContent>
|
|
315
|
+
<Flex direction="col" gap="sm">
|
|
316
|
+
{tasks
|
|
317
|
+
.filter((task) => task.lane === lane.key)
|
|
318
|
+
.map((task) => (
|
|
319
|
+
<Card
|
|
320
|
+
key={task.id}
|
|
321
|
+
draggable
|
|
322
|
+
data-task={task.id}
|
|
323
|
+
onDragStart={(event) =>
|
|
324
|
+
event.dataTransfer.setData("text/plain", task.id)
|
|
325
|
+
}
|
|
326
|
+
>
|
|
327
|
+
<CardContent>
|
|
328
|
+
<Flex direction="col" gap="xs">
|
|
329
|
+
<Text tone="muted">{task.id}</Text>
|
|
330
|
+
<Text>{task.title}</Text>
|
|
331
|
+
<Select
|
|
332
|
+
aria-label={`${task.id} 優先度`}
|
|
333
|
+
name={`priority-${task.id}`}
|
|
334
|
+
defaultValue={task.priority}
|
|
335
|
+
>
|
|
336
|
+
<SelectTrigger aria-label={`${task.id} 優先度`} size="sm">
|
|
337
|
+
<SelectValue />
|
|
338
|
+
</SelectTrigger>
|
|
339
|
+
<SelectContent>
|
|
340
|
+
<SelectItem value="high">高</SelectItem>
|
|
341
|
+
<SelectItem value="medium">中</SelectItem>
|
|
342
|
+
<SelectItem value="low">低</SelectItem>
|
|
343
|
+
</SelectContent>
|
|
344
|
+
</Select>
|
|
345
|
+
</Flex>
|
|
346
|
+
</CardContent>
|
|
347
|
+
</Card>
|
|
348
|
+
))}
|
|
349
|
+
</Flex>
|
|
350
|
+
</CardContent>
|
|
351
|
+
</Card>
|
|
352
|
+
))}
|
|
353
|
+
</ResponsiveGrid>
|
|
354
|
+
</ScrollArea>
|
|
355
|
+
</CardContent>
|
|
356
|
+
</Card>
|
|
357
|
+
|
|
258
358
|
<Card>
|
|
259
359
|
<CardHeader>
|
|
260
360
|
<CardTitle level={2}>一覧と補助情報 2:1</CardTitle>
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@godxjp/ui",
|
|
3
|
-
"version": "27.
|
|
4
|
-
"godxUiMcp": "27.
|
|
3
|
+
"version": "27.3.1",
|
|
4
|
+
"godxUiMcp": "27.3.1",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -317,6 +317,7 @@
|
|
|
317
317
|
"test:visual:topbar-collision": "node scripts/topbar-collision-visual.mjs",
|
|
318
318
|
"test:visual:table-collection-cjk": "node scripts/table-collection-cjk-visual.mjs",
|
|
319
319
|
"test:visual:chart-cjk-axis": "node scripts/chart-cjk-axis-visual.mjs",
|
|
320
|
+
"test:visual:kanban-select": "node scripts/kanban-select-visual.mjs",
|
|
320
321
|
"test:watch": "vitest",
|
|
321
322
|
"test:coverage": "vitest run --coverage",
|
|
322
323
|
"check:example-imports": "node scripts/check-example-imports.mjs",
|
|
@@ -460,6 +461,7 @@
|
|
|
460
461
|
"input-otp": "^1.5.0",
|
|
461
462
|
"lucide-react": "^1.37.0",
|
|
462
463
|
"qrcode.react": "4.2.0",
|
|
464
|
+
"react-aria": "3.52.1",
|
|
463
465
|
"react-aria-components": "^1.21.1",
|
|
464
466
|
"react-day-picker": "^10.0.1",
|
|
465
467
|
"react-resizable-panels": "^4.12.3",
|