@godxjp/ui 22.0.0 → 23.1.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 (115) hide show
  1. package/README.md +4 -4
  2. package/dist/app/date-format-labels.d.ts +14 -2
  3. package/dist/app/date-format-labels.js +1 -1
  4. package/dist/app/date-formats.d.ts +14 -2
  5. package/dist/app/date-formats.js +9 -2
  6. package/dist/components/data-display/data-table.d.ts +8 -1
  7. package/dist/components/data-display/data-table.js +9 -2
  8. package/dist/components/data-display/scroll-area.d.ts +24 -4
  9. package/dist/components/data-display/scroll-area.js +34 -72
  10. package/dist/components/data-entry/cascader.js +8 -6
  11. package/dist/components/data-entry/checkbox.js +3 -1
  12. package/dist/components/data-entry/choice-hit-target.d.ts +24 -0
  13. package/dist/components/data-entry/choice-hit-target.js +12 -0
  14. package/dist/components/data-entry/control-surface.d.ts +8 -0
  15. package/dist/components/data-entry/control-surface.js +10 -1
  16. package/dist/components/data-entry/form-field.js +5 -4
  17. package/dist/components/data-entry/input.js +1 -1
  18. package/dist/components/data-entry/number-input.js +27 -6
  19. package/dist/components/data-entry/radio.js +4 -3
  20. package/dist/components/data-entry/search-select.js +225 -95
  21. package/dist/components/data-entry/select.d.ts +76 -14
  22. package/dist/components/data-entry/select.js +495 -163
  23. package/dist/components/data-entry/slider.d.ts +3 -11
  24. package/dist/components/data-entry/slider.js +582 -93
  25. package/dist/components/data-entry/switch.js +3 -1
  26. package/dist/components/feedback/dialog.js +2 -2
  27. package/dist/components/feedback/sheet.js +2 -22
  28. package/dist/components/navigation/dropdown-menu.d.ts +11 -3
  29. package/dist/components/navigation/dropdown-menu.js +190 -15
  30. package/dist/components/navigation/filter-bar.js +2 -1
  31. package/dist/components/navigation/index.d.ts +0 -3
  32. package/dist/components/navigation/index.js +0 -78
  33. package/dist/components/navigation/tabs.js +16 -4
  34. package/dist/components/ui/index.d.ts +0 -3
  35. package/dist/components/ui/index.js +0 -3
  36. package/dist/components/ui/input-otp.d.ts +1 -1
  37. package/dist/components/ui/segmented.js +3 -1
  38. package/dist/components/ui/tag-input.js +3 -5
  39. package/dist/i18n/messages/en.json +5 -1
  40. package/dist/i18n/messages/ja.json +5 -1
  41. package/dist/i18n/messages/vi.json +5 -1
  42. package/dist/lib/breakpoint-token.d.ts +8 -0
  43. package/dist/lib/breakpoint-token.js +29 -0
  44. package/dist/lib/select-options.d.ts +21 -0
  45. package/dist/lib/select-options.js +46 -0
  46. package/dist/props/components/data-display.prop.d.ts +17 -7
  47. package/dist/props/components/data-entry.prop.d.ts +323 -30
  48. package/dist/props/components/index.d.ts +1 -1
  49. package/dist/props/components/navigation.prop.d.ts +25 -1
  50. package/dist/props/registry.d.ts +69 -1
  51. package/dist/props/registry.js +75 -0
  52. package/dist/props/vocabulary/data.prop.d.ts +5 -0
  53. package/dist/props/vocabulary/index.d.ts +1 -1
  54. package/dist/styles/control.css +246 -28
  55. package/dist/styles/data-display-layout.css +18 -10
  56. package/dist/styles/focus-ring.css +28 -0
  57. package/dist/styles/fonts.css +88 -4
  58. package/dist/styles/form-layout.css +4 -0
  59. package/dist/styles/layout.css +2 -1
  60. package/dist/styles/navigation-layout.css +24 -238
  61. package/dist/styles/shell-layout.css +148 -139
  62. package/dist/styles/table-layout.css +12 -5
  63. package/dist/tokens/components/control.css +12 -3
  64. package/dist/tokens/components/data-display.css +0 -5
  65. package/dist/tokens/components/navigation.css +5 -5
  66. package/dist/tokens/components/scroll-area.css +7 -0
  67. package/docs/CONSUMER-RULES.md +7 -2
  68. package/docs/CUSTOMER-THEMING.md +25 -9
  69. package/docs/DATETIME.md +11 -2
  70. package/docs/FRAME-COVERAGE-REPORT.md +4 -7
  71. package/docs/SPACING.md +35 -5
  72. package/docs/TOKENS.md +1 -1
  73. package/docs/data-display/data-table/index.tsx +44 -3
  74. package/docs/data-display/popover.tsx +4 -6
  75. package/docs/data-display/scroll-area.tsx +51 -45
  76. package/docs/data-display/table.tsx +1 -1
  77. package/docs/data-entry/date-picker.tsx +2 -3
  78. package/docs/data-entry/input-otp.tsx +5 -2
  79. package/docs/data-entry/segmented-in-filter-row.tsx +77 -0
  80. package/docs/data-entry/segmented.tsx +40 -1
  81. package/docs/data-entry/select-matrix.tsx +1 -2
  82. package/docs/data-entry/select.tsx +108 -3
  83. package/docs/data-entry/slider.tsx +125 -47
  84. package/docs/feedback/dialog.tsx +1 -2
  85. package/docs/feedback/sheet.tsx +1 -2
  86. package/docs/layout/app-shell-states.tsx +143 -0
  87. package/docs/navigation/dropdown-menu.tsx +77 -6
  88. package/docs/navigation/tabs.tsx +72 -0
  89. package/docs/roadmap/ai-chat-components.md +1 -2
  90. package/docs/roadmap/parity-audit-data-entry.md +44 -33
  91. package/docs/roadmap/parity-audit-layout-navigation-general.md +304 -310
  92. package/docs/roadmap/parity-backlog.md +2 -3
  93. package/docs/roadmap/tree-components.md +0 -1
  94. package/package.json +25 -55
  95. package/scripts/_agent-setup.mjs +18 -0
  96. package/scripts/guinea-pig-skill.md +12 -6
  97. package/scripts/ui-audit.mjs +185 -7
  98. package/scripts/visual-audit-rules.mjs +0 -7
  99. package/scripts/visual-audit.mjs +6 -27
  100. package/dist/components/navigation/context-menu.d.ts +0 -21
  101. package/dist/components/navigation/context-menu.js +0 -149
  102. package/dist/components/navigation/menubar.d.ts +0 -28
  103. package/dist/components/navigation/menubar.js +0 -136
  104. package/dist/components/navigation/navigation-menu.d.ts +0 -10
  105. package/dist/components/navigation/navigation-menu.js +0 -91
  106. package/dist/components/ui/context-menu.d.ts +0 -1
  107. package/dist/components/ui/context-menu.js +0 -2
  108. package/dist/components/ui/menubar.d.ts +0 -1
  109. package/dist/components/ui/menubar.js +0 -2
  110. package/dist/components/ui/navigation-menu.d.ts +0 -1
  111. package/dist/components/ui/navigation-menu.js +0 -2
  112. package/docs/FRAME-A11Y-CI.md +0 -349
  113. package/docs/navigation/context-menu.tsx +0 -128
  114. package/docs/navigation/menubar.tsx +0 -141
  115. package/docs/navigation/navigation-menu.tsx +0 -158
@@ -21,7 +21,9 @@ import {
21
21
  SelectTrigger,
22
22
  SelectValue,
23
23
  } from "@godxjp/ui/data-entry";
24
+ import { Button } from "@godxjp/ui/general";
24
25
  import { Flex, PageContainer } from "@godxjp/ui/layout";
26
+ import { X } from "lucide-react";
25
27
 
26
28
  /**
27
29
  * Select — the single-select control. Prefer the data-driven `options` API
@@ -38,6 +40,12 @@ export default function Demo() {
38
40
  const [reviewerAsync, setReviewerAsync] = useState("tanaka");
39
41
  const [reviewerCustom, setReviewerCustom] = useState("tanaka");
40
42
  const [reviewerLabel, setReviewerLabel] = useState("tanaka");
43
+ const [prefixed, setPrefixed] = useState("JPY");
44
+ const [tags, setTags] = useState<string[]>(["至急", "社内便"]);
45
+ // antd `labelInValue`: the record arrives carrying its own label, before any option list loads.
46
+ const [assignedTo, setAssignedTo] = useState<
47
+ { value: string; label: React.ReactNode } | undefined
48
+ >({ value: "52", label: "東京本社" });
41
49
 
42
50
  const people = [
43
51
  { value: "tanaka", label: "田中 太郎", sublabel: "tanaka@example.com" },
@@ -82,9 +90,7 @@ export default function Demo() {
82
90
  Pass an options array; name= submits the value with the form.
83
91
  </CardDescription>
84
92
  </CardHeader>
85
- {/* data-axe-open: check-frame-axe opens this Select before scanning, so the
86
- overlay-open a11y state is measured. See docs/FRAME-A11Y-CI.md. */}
87
- <CardContent data-axe-open>
93
+ <CardContent>
88
94
  <FormField id="status" label="状態">
89
95
  <Select
90
96
  id="status"
@@ -392,6 +398,105 @@ export default function Demo() {
392
398
  </FormField>
393
399
  </CardContent>
394
400
  </Card>
401
+ <Card>
402
+ <CardHeader>
403
+ <CardTitle level={2}>
404
+ Ant Design 6 · prefix・suffixIcon・placement・listHeight
405
+ </CardTitle>
406
+ <CardDescription>
407
+ グループは options のネスト({`{ label, options }`})でも、行の group
408
+ でも同じ一覧になります。
409
+ </CardDescription>
410
+ </CardHeader>
411
+ <CardContent>
412
+ <FormField id="prefixed" label="通貨">
413
+ <Select
414
+ id="prefixed"
415
+ name="currency_prefixed"
416
+ value={prefixed}
417
+ onValueChange={setPrefixed}
418
+ prefix={<Badge tone="muted">¥</Badge>}
419
+ placement="bottomEnd"
420
+ listHeight={220}
421
+ options={[
422
+ {
423
+ label: "アジア",
424
+ options: [
425
+ { value: "JPY", label: "日本円" },
426
+ { value: "VND", label: "ベトナムドン" },
427
+ ],
428
+ },
429
+ {
430
+ label: "ヨーロッパ",
431
+ options: [{ value: "EUR", label: "ユーロ" }],
432
+ },
433
+ ]}
434
+ />
435
+ </FormField>
436
+ </CardContent>
437
+ </Card>
438
+
439
+ <Card>
440
+ <CardHeader>
441
+ <CardTitle level={2}>mode=&quot;tags&quot; · tokenSeparators・tagRender</CardTitle>
442
+ <CardDescription>
443
+ 一覧にない値も入力できます。カンマ区切りの貼り付けは一度にまとめて確定します。
444
+ </CardDescription>
445
+ </CardHeader>
446
+ <CardContent>
447
+ <FormField id="tags" label="ラベル">
448
+ <Select
449
+ id="tags"
450
+ name="labels[]"
451
+ mode="tags"
452
+ value={tags}
453
+ onValueChange={setTags}
454
+ tokenSeparators={[",", "\n"]}
455
+ maxTagTextLength={6}
456
+ options={[
457
+ { value: "至急", label: "至急" },
458
+ { value: "要確認", label: "要確認" },
459
+ ]}
460
+ tagRender={({ label, onClose }) => (
461
+ <Badge tone="info">
462
+ {label}
463
+ <Button
464
+ variant="ghost"
465
+ size="icon-xs"
466
+ aria-label={`${String(label)} を外す`}
467
+ onClick={onClose}
468
+ >
469
+ <X aria-hidden="true" />
470
+ </Button>
471
+ </Badge>
472
+ )}
473
+ />
474
+ </FormField>
475
+ </CardContent>
476
+ </Card>
477
+
478
+ <Card>
479
+ <CardHeader>
480
+ <CardTitle level={2}>labelInValue · 読み込み前でも名前が出る</CardTitle>
481
+ <CardDescription>
482
+ 値が {`{ value, label }`} を持つので、選択肢がまだ届いていなくても ID
483
+ ではなく名前を表示できます。
484
+ </CardDescription>
485
+ </CardHeader>
486
+ <CardContent>
487
+ <FormField id="assigned" label="担当拠点">
488
+ <Select
489
+ id="assigned"
490
+ name="office_id"
491
+ labelInValue
492
+ value={assignedTo}
493
+ onValueChange={setAssignedTo}
494
+ notFoundContent="拠点を読み込み中"
495
+ options={[]}
496
+ />
497
+ </FormField>
498
+ </CardContent>
499
+ </Card>
395
500
  </Flex>
396
501
  </PageContainer>
397
502
  );
@@ -2,82 +2,111 @@ import { useState } from "react";
2
2
 
3
3
  import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@godxjp/ui/data-display";
4
4
  import { FormField, Slider } from "@godxjp/ui/data-entry";
5
- import { Flex, PageContainer } from "@godxjp/ui/layout";
5
+ import { Flex, PageContainer, ResponsiveGrid } from "@godxjp/ui/layout";
6
+ import { Text } from "@godxjp/ui/general";
6
7
 
7
8
  /**
8
- * Slider — Radix Slider wrapper. value/defaultValue MUST be number[] not a plain
9
- * number. Single-thumb: [50], dual-thumb range: [20, 80].
10
- * Use onValueCommit (not onValueChange) for expensive side-effects.
9
+ * Slider — react-aria-components underneath, the antd 6 API on top.
10
+ * `value` is a plain number for one thumb and an array for a `range`; the Radix-era
11
+ * `number[]` + `onValueChange` spelling still compiles and still works.
12
+ * Use `onChange` for live UI and `onChangeComplete` for the expensive work.
11
13
  * Composed only from real @godxjp/ui components.
12
14
  */
13
- const yen = (n: number) =>
14
- new Intl.NumberFormat("ja-JP", { style: "currency", currency: "JPY" }).format(n);
15
+ const yen = (value: number) =>
16
+ new Intl.NumberFormat("ja-JP", { style: "currency", currency: "JPY" }).format(value);
15
17
 
16
18
  export default function Demo() {
17
- const [taxRate, setTaxRate] = useState<number[]>([10]);
19
+ const [taxRate, setTaxRate] = useState(10);
18
20
  const [amountRange, setAmountRange] = useState<number[]>([50000, 300000]);
21
+ const [committedRange, setCommittedRange] = useState<number[]>([50000, 300000]);
22
+ const [plan, setPlan] = useState(30);
19
23
  const [allocation, setAllocation] = useState<number[]>([40]);
24
+ const [bands, setBands] = useState<number[]>([25, 60]);
25
+ const [volume, setVolume] = useState(60);
20
26
 
21
27
  return (
22
28
  <PageContainer
23
29
  title="Slider"
24
- subtitle="Numeric range slider · value/defaultValue must be number[] (single-thumb: [n], range: [min, max])"
30
+ subtitle="数値スライダー · 単一は number、range [number, number] · onChange は逐次、onChangeComplete は確定時"
25
31
  >
26
32
  <Flex direction="col" gap="lg">
27
33
  <Card>
28
34
  <CardHeader>
29
35
  <CardTitle level={2}>単一スライダー · 税率設定</CardTitle>
30
36
  <CardDescription>
31
- Single-thumb controlled slider. value={`[n]`} plain number breaks rendering.
32
- onValueChange fires on every drag; onValueCommit fires on release.
37
+ antd と同じ書き味: value に数値を渡し、onChange が数値を返す。tooltip.formatter
38
+ 吹き出しの表記であると同時に読み上げ (aria-valuetext) にもなる。
39
+ </CardDescription>
40
+ </CardHeader>
41
+ <CardContent>
42
+ <FormField id="tax-rate" label={`消費税率: ${taxRate}%`} helper="0% / 2% 刻みで選択">
43
+ <Slider
44
+ value={taxRate}
45
+ onChange={setTaxRate}
46
+ min={0}
47
+ max={10}
48
+ step={2}
49
+ name="tax_rate"
50
+ tooltip={{ formatter: (value) => `${value}%` }}
51
+ />
52
+ </FormField>
53
+ </CardContent>
54
+ </Card>
55
+
56
+ <Card>
57
+ <CardHeader>
58
+ <CardTitle level={2}>レンジスライダー · 請求金額フィルタ</CardTitle>
59
+ <CardDescription>
60
+ range を明示すると、値が未到着でも [min, max] の 2 つのつまみで描画される。
61
+ 再取得のような重い処理は onChangeComplete に置く。
33
62
  </CardDescription>
34
63
  </CardHeader>
35
64
  <CardContent>
36
65
  <Flex direction="col" gap="md">
37
66
  <FormField
38
- id="tax-rate"
39
- label={`消費税率: ${taxRate[0]}%`}
40
- helper="0% / 8% (軽減税率) / 10% から選択"
67
+ id="amount-range"
68
+ label={`請求金額: ${yen(amountRange[0])} 〜 ${yen(amountRange[1])}`}
41
69
  >
42
70
  <Slider
43
- value={taxRate}
44
- onValueChange={setTaxRate}
71
+ range
72
+ value={amountRange}
73
+ onChange={setAmountRange}
74
+ onChangeComplete={setCommittedRange}
45
75
  min={0}
46
- max={10}
47
- step={2}
48
- name="tax_rate"
49
- aria-label="消費税率"
76
+ max={1000000}
77
+ step={10000}
78
+ minStepsBetweenThumbs={1}
79
+ tooltip={{ formatter: yen }}
50
80
  />
51
81
  </FormField>
82
+ <Text tone="muted" size="sm">
83
+ 確定値 (onChangeComplete): {yen(committedRange[0])} 〜 {yen(committedRange[1])}
84
+ </Text>
52
85
  </Flex>
53
86
  </CardContent>
54
87
  </Card>
55
88
 
56
89
  <Card>
57
90
  <CardHeader>
58
- <CardTitle level={2}>レンジスライダー · 請求金額フィルタ</CardTitle>
91
+ <CardTitle level={2}>目盛りだけを値にする · step={"{null}"}</CardTitle>
59
92
  <CardDescription>
60
- Dual-thumb range slider: value={`[min, max]`}. Use minStepsBetweenThumbs to prevent
61
- thumbs overlapping.
93
+ marks step={"{null}"} を組み合わせると、つまみは目盛り・minmax にしか止まらない。
94
+ dots は目盛りごとの点で、included の範囲内だけが濃くなる。
62
95
  </CardDescription>
63
96
  </CardHeader>
64
97
  <CardContent>
65
- <FormField
66
- id="amount-range"
67
- label={`請求金額: ${yen(amountRange[0])} 〜 ${yen(amountRange[1])}`}
68
- >
98
+ <FormField id="plan" label={`料金プラン: ${plan}`} helper="無料 / 標準 / 上位 / 特別">
69
99
  <Slider
70
- value={amountRange}
71
- onValueChange={setAmountRange}
72
- onValueCommit={(v) => {
73
- // expensive filter refetch only on commit (pointer/key release)
74
- void v;
100
+ value={plan}
101
+ onChange={setPlan}
102
+ step={null}
103
+ dots
104
+ marks={{
105
+ 0: "無料",
106
+ 30: "標準",
107
+ 70: "上位",
108
+ 100: { style: { fontWeight: 700 }, label: "特別" },
75
109
  }}
76
- min={0}
77
- max={1000000}
78
- step={10000}
79
- minStepsBetweenThumbs={1}
80
- aria-label="請求金額範囲"
81
110
  />
82
111
  </FormField>
83
112
  </CardContent>
@@ -85,17 +114,17 @@ export default function Demo() {
85
114
 
86
115
  <Card>
87
116
  <CardHeader>
88
- <CardTitle level={2}>予算配分スライダー</CardTitle>
117
+ <CardTitle level={2}>予算配分 · Radix 時代の書き方も有効</CardTitle>
89
118
  <CardDescription>
90
- step=5 for coarse granularity. Use name= to submit with a native form without JS
91
- wiring.
119
+ number[] value onValueChange / onValueCommit はそのまま動く。name を付ければ
120
+ JavaScript なしでフォーム送信できる (つまみが複数なら name[])。
92
121
  </CardDescription>
93
122
  </CardHeader>
94
123
  <CardContent>
95
124
  <FormField
96
125
  id="allocation"
97
126
  label={`広告費配分: ${allocation[0]}%`}
98
- helper="残り ${100 - allocation[0]}% が運転資金へ充当されます"
127
+ helper={`残り ${100 - allocation[0]}% が運転資金へ充当されます`}
99
128
  >
100
129
  <Slider
101
130
  value={allocation}
@@ -104,7 +133,7 @@ export default function Demo() {
104
133
  max={100}
105
134
  step={5}
106
135
  name="ad_allocation"
107
- aria-label="広告費配分"
136
+ marks={{ 0: "0%", 50: "50%", 100: "100%" }}
108
137
  />
109
138
  </FormField>
110
139
  </CardContent>
@@ -112,16 +141,65 @@ export default function Demo() {
112
141
 
113
142
  <Card>
114
143
  <CardHeader>
115
- <CardTitle level={2}>Disabled (read-only indicator)</CardTitle>
144
+ <CardTitle level={2}>編集できるレンジ · range={"{{ editable: true }}"}</CardTitle>
116
145
  <CardDescription>
117
- Pass disabled with a controlled value to show a non-interactive bar. Use Progress for
118
- pure status display.
146
+ レール上を押すとつまみが増え、Delete / Backspace で減る (minCount maxCount)。
147
+ included=false は「ここまで」を塗らない目盛り専用のレール。
119
148
  </CardDescription>
120
149
  </CardHeader>
121
150
  <CardContent>
122
- <FormField id="locked-rate" label="確定済み税率 (変更不可)">
123
- <Slider value={[10]} disabled min={0} max={20} step={1} aria-label="確定済み税率" />
124
- </FormField>
151
+ <ResponsiveGrid columns={{ base: 1, md: 2 }} gap="lg">
152
+ <FormField id="bands" label={`区切り: ${bands.join(" / ")}`}>
153
+ <Slider
154
+ range={{ editable: true, minCount: 1, maxCount: 4, draggableTrack: false }}
155
+ value={bands}
156
+ onChange={setBands}
157
+ step={5}
158
+ />
159
+ </FormField>
160
+ <FormField id="scale" label="評価スケール (included={false})">
161
+ <Slider
162
+ defaultValue={50}
163
+ included={false}
164
+ marks={{ 0: "低", 50: "中", 100: "高" }}
165
+ />
166
+ </FormField>
167
+ </ResponsiveGrid>
168
+ </CardContent>
169
+ </Card>
170
+
171
+ <Card>
172
+ <CardHeader>
173
+ <CardTitle level={2}>縦・逆向き・無効</CardTitle>
174
+ <CardDescription>
175
+ vertical は下が min、reverse は向きを入れ替える (矢印キーも一緒に反転)。 disabled
176
+ は配列でつまみ単位にもできる。
177
+ </CardDescription>
178
+ </CardHeader>
179
+ <CardContent>
180
+ <ResponsiveGrid columns={{ base: 1, md: 3 }} gap="lg">
181
+ <FormField id="volume-vertical" label={`音量 (縦): ${volume}`}>
182
+ <Slider
183
+ vertical
184
+ value={volume}
185
+ onChange={setVolume}
186
+ marks={{ 0: "0", 50: "50", 100: "100" }}
187
+ tooltip
188
+ />
189
+ </FormField>
190
+ <FormField id="countdown" label="残り日数 (reverse)">
191
+ <Slider
192
+ reverse
193
+ defaultValue={30}
194
+ max={60}
195
+ step={5}
196
+ marks={{ 0: "0日", 30: "30日", 60: "60日" }}
197
+ />
198
+ </FormField>
199
+ <FormField id="locked-rate" label="確定済み税率 (変更不可)">
200
+ <Slider value={10} disabled min={0} max={20} step={1} />
201
+ </FormField>
202
+ </ResponsiveGrid>
125
203
  </CardContent>
126
204
  </Card>
127
205
  </Flex>
@@ -109,8 +109,7 @@ export default function Demo() {
109
109
  <CardContent>
110
110
  <Dialog open={detailOpen} onOpenChange={setDetailOpen}>
111
111
  <DialogTrigger asChild>
112
- {/* data-axe-open: opened by check-frame-axe before its overlay scan. */}
113
- <Button data-axe-open variant="outline" size="sm">
112
+ <Button variant="outline" size="sm">
114
113
  取引詳細を見る
115
114
  </Button>
116
115
  </DialogTrigger>
@@ -213,8 +213,7 @@ export default function Demo() {
213
213
  <CardContent>
214
214
  <Sheet open={editOpen} onOpenChange={setEditOpen}>
215
215
  <SheetTrigger asChild>
216
- {/* data-axe-open: opened by check-frame-axe before its overlay scan. */}
217
- <Button data-axe-open variant="ghost" size="sm">
216
+ <Button variant="ghost" size="sm">
218
217
  取引先を編集
219
218
  </Button>
220
219
  </SheetTrigger>
@@ -0,0 +1,143 @@
1
+ import { useState } from "react";
2
+ import { AppShell, Flex, PageContainer, Sidebar, Topbar } from "@godxjp/ui/layout";
3
+ import type { SidebarSectionProp } from "@godxjp/ui/layout";
4
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@godxjp/ui/data-display";
5
+ import { Button, Text } from "@godxjp/ui/general";
6
+ import {
7
+ Bell,
8
+ Building2,
9
+ FileText,
10
+ LayoutDashboard,
11
+ MessageSquare,
12
+ Plus,
13
+ Receipt,
14
+ Users,
15
+ } from "lucide-react";
16
+
17
+ /**
18
+ * AppShell · grid states, one per URL.
19
+ *
20
+ * The shell's grid is keyed on four attributes — `data-sidebar`, `data-nav-rail(-position)`,
21
+ * `data-collapsed` and `data-topbar-span` — and every combination is a real product shape. This
22
+ * frame renders ONE AppShell in whichever state the query names, so a browser gate can walk all of
23
+ * them (`scripts/check-app-shell-narrow-grid.mjs`) without stacking several `main` landmarks on a
24
+ * single page:
25
+ *
26
+ * ?topbarSpan=content|full ?navRail=start|end|top|bottom ?collapsed=1 ?sidebar=none
27
+ * ?responsive=docked
28
+ *
29
+ * With no query it is `topbarSpan="full"` without a rail — the state gh#474 found collapsing
30
+ * `main` into a 170px implicit track below 900px, with the page actions laid out past the edge.
31
+ */
32
+
33
+ const SECTIONS: SidebarSectionProp[] = [
34
+ {
35
+ label: "会計",
36
+ items: [
37
+ { id: "dashboard", label: "ダッシュボード", icon: LayoutDashboard },
38
+ { id: "journal", label: "仕訳", icon: FileText },
39
+ { id: "invoices", label: "請求書", icon: Receipt },
40
+ ],
41
+ },
42
+ {
43
+ label: "管理",
44
+ items: [
45
+ { id: "partners", label: "取引先", icon: Building2 },
46
+ { id: "users", label: "ユーザー", icon: Users },
47
+ ],
48
+ },
49
+ ];
50
+
51
+ type RailPosition = "start" | "end" | "top" | "bottom";
52
+
53
+ function readState() {
54
+ const params = new URLSearchParams(window.location.search);
55
+ const rail = params.get("navRail");
56
+ return {
57
+ topbarSpan: params.get("topbarSpan") === "content" ? ("content" as const) : ("full" as const),
58
+ navRail: (["start", "end", "top", "bottom"] as const).find((p) => p === rail) as
59
+ RailPosition | undefined,
60
+ collapsed: params.get("collapsed") === "1",
61
+ hasSidebar: params.get("sidebar") !== "none",
62
+ responsiveNavigation:
63
+ params.get("responsive") === "docked" ? ("docked" as const) : ("drawer" as const),
64
+ };
65
+ }
66
+
67
+ export default function Demo() {
68
+ const [{ topbarSpan, navRail, collapsed, hasSidebar, responsiveNavigation }] =
69
+ useState(readState);
70
+ const [activeId, setActiveId] = useState("dashboard");
71
+ const strip = navRail === "top" || navRail === "bottom";
72
+
73
+ return (
74
+ <AppShell
75
+ topbarSpan={topbarSpan}
76
+ responsiveNavigation={responsiveNavigation}
77
+ sidebarCollapsed={collapsed}
78
+ sidebar={
79
+ hasSidebar ? (
80
+ <Sidebar
81
+ activeId={activeId}
82
+ collapsed={collapsed}
83
+ onSelect={setActiveId}
84
+ sections={SECTIONS}
85
+ product={{ name: "CoreBooks", role: "株式会社アクメ" }}
86
+ />
87
+ ) : undefined
88
+ }
89
+ navRail={
90
+ navRail ? (
91
+ <Flex direction={strip ? "row" : "col"} gap="xs" align="center">
92
+ <Button variant="ghost" size="icon-sm" aria-label="会計">
93
+ <Receipt />
94
+ </Button>
95
+ <Button variant="ghost" size="icon-sm" aria-label="チャット">
96
+ <MessageSquare />
97
+ </Button>
98
+ </Flex>
99
+ ) : undefined
100
+ }
101
+ navRailPosition={navRail}
102
+ topbar={
103
+ <Topbar
104
+ start={<Text weight="medium">CoreBooks</Text>}
105
+ end={
106
+ <Button variant="ghost" size="icon-sm" aria-label="通知">
107
+ <Bell />
108
+ </Button>
109
+ }
110
+ />
111
+ }
112
+ >
113
+ <PageContainer
114
+ title="請求書"
115
+ subtitle="株式会社アクメ · 2026年5月"
116
+ extra={
117
+ <Flex gap="sm" wrap>
118
+ <Button size="sm" variant="outline">
119
+ 取引先へ一括送信する
120
+ </Button>
121
+ <Button size="sm">
122
+ <Plus />
123
+ 新しい請求書を作成する
124
+ </Button>
125
+ </Flex>
126
+ }
127
+ >
128
+ <Card>
129
+ <CardHeader>
130
+ <CardTitle level={2}>今月の請求</CardTitle>
131
+ <CardDescription>未送信 3 件 · 入金待ち 12 件</CardDescription>
132
+ </CardHeader>
133
+ <CardContent>
134
+ <Text tone="muted">
135
+ 状態はクエリで切り替えます。どの状態でも 900px 以下では 1 列になり、main
136
+ は画面幅いっぱいに広がります。
137
+ </Text>
138
+ </CardContent>
139
+ </Card>
140
+ </PageContainer>
141
+ </AppShell>
142
+ );
143
+ }