@godxjp/ui 28.4.0 → 28.6.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 (49) hide show
  1. package/README.md +58 -0
  2. package/dist/app/theme-axes.js +6 -0
  3. package/dist/components/data-entry/field.d.ts +1 -1
  4. package/dist/components/data-entry/field.js +35 -4
  5. package/dist/components/data-entry/index.d.ts +1 -0
  6. package/dist/components/data-entry/index.js +2 -0
  7. package/dist/components/data-entry/input.d.ts +1 -1
  8. package/dist/components/layout/index.d.ts +1 -1
  9. package/dist/components/layout/nav-list.d.ts +9 -1
  10. package/dist/components/layout/nav-list.js +22 -11
  11. package/dist/components/layout/sidebar.d.ts +16 -2
  12. package/dist/components/layout/sidebar.js +1 -0
  13. package/dist/components/ui/password-input.d.ts +1 -1
  14. package/dist/contracts/measurement.json +1 -1
  15. package/dist/lib/control-styles.d.ts +18 -3
  16. package/dist/lib/control-styles.js +1 -1
  17. package/dist/props/components/data-entry.prop.d.ts +36 -2
  18. package/dist/props/components/layout.prop.d.ts +16 -5
  19. package/dist/props/registry.d.ts +1 -1
  20. package/dist/props/registry.js +8 -1
  21. package/dist/styles/base.css +5 -3
  22. package/dist/styles/card-layout.css +11 -6
  23. package/dist/styles/control.css +20 -8
  24. package/dist/styles/data-display-layout.css +51 -20
  25. package/dist/styles/data-entry-layout.css +3 -3
  26. package/dist/styles/dialog-layout.css +12 -0
  27. package/dist/styles/layout.css +1 -1
  28. package/dist/styles/table-layout.css +9 -6
  29. package/dist/styles/text-layout.css +1 -1
  30. package/dist/theme/famgia.service.css +2 -4
  31. package/dist/tokens/components/control.css +4 -3
  32. package/dist/tokens/components/data-display.css +2 -1
  33. package/dist/tokens/components/feedback.css +2 -0
  34. package/dist/tokens/derived.css +20 -0
  35. package/dist/tokens/foundation.css +6 -13
  36. package/docs/CUSTOMER-THEMING.md +82 -3
  37. package/docs/FRAME-COVERAGE-REPORT.md +2 -2
  38. package/docs/data-display/collapsible.tsx +5 -5
  39. package/docs/data-display/descriptions.tsx +188 -109
  40. package/docs/data-display/legend.tsx +17 -4
  41. package/docs/data-display/scroll-area.tsx +75 -5
  42. package/docs/data-display/stat-card.tsx +319 -64
  43. package/docs/data-entry/form.tsx +700 -6
  44. package/docs/data-entry/input.tsx +663 -53
  45. package/docs/data-entry/radio-group.tsx +216 -2
  46. package/docs/data-entry/switch.tsx +210 -4
  47. package/docs/data-entry/upload-crop-dialog.tsx +109 -0
  48. package/docs/layout/nav-list.tsx +556 -35
  49. package/package.json +99 -91
@@ -1,16 +1,33 @@
1
1
  import { useState } from "react";
2
2
 
3
+ import { Building2, CalendarDays, Hash, Link2, Mail, MapPin, Phone } from "lucide-react";
4
+
3
5
  import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@godxjp/ui/data-display";
4
- import { FormField, Input } from "@godxjp/ui/data-entry";
5
- import { Flex, PageContainer } from "@godxjp/ui/layout";
6
+ import { FormField, Input, NumberInput, PasswordInput, SearchInput } from "@godxjp/ui/data-entry";
7
+ import { Icon, Text } from "@godxjp/ui/general";
8
+ import { Flex, PageContainer, ResponsiveGrid } from "@godxjp/ui/layout";
6
9
 
7
10
  /**
8
11
  * Input — styled wrapper around the native input. Always pair with FormField for
9
12
  * a labelled, a11y-wired field. Never a raw <input>. Composed only from real
10
13
  * @godxjp/ui components.
14
+ *
15
+ * This page is deliberately long: a text field is trivial with a short placeholder
16
+ * and an empty box, so every section below puts something in it that a real
17
+ * enterprise form puts in it — a 40-character machine id, a three-line Japanese
18
+ * label, an address that overflows the box, zero versus empty, Arabic.
11
19
  */
12
20
  export default function Demo() {
13
21
  const [contractValue, setContractValue] = useState("制御値");
22
+ const [emptyText, setEmptyText] = useState("");
23
+ const [zeroText, setZeroText] = useState("0");
24
+ const [emptyNumber, setEmptyNumber] = useState<number | null>(null);
25
+ const [zeroNumber, setZeroNumber] = useState<number | null>(0);
26
+ const [overflow, setOverflow] = useState(
27
+ "東京都港区芝公園四丁目2番8号 東京タワーフットタウン3階 株式会社ゴドー商事 経理部 請求管理課",
28
+ );
29
+ const [partnerQuery, setPartnerQuery] = useState("ゴドー");
30
+
14
31
  return (
15
32
  <PageContainer
16
33
  title="Input"
@@ -19,21 +36,71 @@ export default function Demo() {
19
36
  <Flex direction="col" gap="lg">
20
37
  <Card>
21
38
  <CardHeader>
22
- <CardTitle level={2}>States</CardTitle>
39
+ <CardTitle level={2}>サイズ · 高さラダー</CardTitle>
40
+ <CardDescription>
41
+ WHY: 一行に複数のコントロールが並ぶツールバーやフィルタ行では、高さが 1px
42
+ でもずれると行が波打つ。横に並べて初めて段差が見える。size は xs / sm / md / lg
43
+ の4段で、既定は md。xs は長らく Input の型にだけ無く、CSS もトークンも揃っていたのに
44
+ union が塞いでいた。
45
+ </CardDescription>
46
+ </CardHeader>
47
+ <CardContent>
48
+ <Flex direction="col" gap="md">
49
+ <Flex direction="row" gap="sm" align="center" wrap>
50
+ <Flex width={140}>
51
+ <Input aria-label="極小 (xs)" size="xs" defaultValue="xs · 極小" />
52
+ </Flex>
53
+ <Flex width={140}>
54
+ <Input aria-label="小 (sm)" size="sm" defaultValue="sm · 小" />
55
+ </Flex>
56
+ <Flex width={140}>
57
+ <Input aria-label="標準 (md)" size="md" defaultValue="md · 標準" />
58
+ </Flex>
59
+ <Flex width={140}>
60
+ <Input aria-label="大 (lg)" size="lg" defaultValue="lg · 大" />
61
+ </Flex>
62
+ </Flex>
63
+ <Text size="sm" tone="muted">
64
+ 同じ段をアフィックス付きでも揃える。prefix / addon
65
+ は箱の高さを継承するので、段が変わっても行は揃ったまま。
66
+ </Text>
67
+ <Flex direction="row" gap="sm" align="center" wrap>
68
+ <Flex width={180}>
69
+ <Input aria-label="極小 (xs) 金額" size="xs" prefix="¥" defaultValue="12,000" />
70
+ </Flex>
71
+ <Flex width={180}>
72
+ <Input aria-label="小 (sm) 金額" size="sm" prefix="¥" defaultValue="12,000" />
73
+ </Flex>
74
+ <Flex width={180}>
75
+ <Input aria-label="標準 (md) 金額" size="md" prefix="¥" defaultValue="12,000" />
76
+ </Flex>
77
+ <Flex width={180}>
78
+ <Input aria-label="大 (lg) 金額" size="lg" prefix="¥" defaultValue="12,000" />
79
+ </Flex>
80
+ </Flex>
81
+ </Flex>
82
+ </CardContent>
83
+ </Card>
84
+
85
+ <Card>
86
+ <CardHeader>
87
+ <CardTitle level={2}>状態 · 既定 / 入力済み / disabled / readOnly</CardTitle>
23
88
  <CardDescription>
24
- Placeholder, filled, disabled, readOnly, and invalid (aria-invalid). Focus an empty
25
- field to see the focus-visible ring.
89
+ WHY: disabled と readOnly を取り違えると、送信されるはずの値が消える。disabled
90
+ は操作もフォーカスも送信もしない。readOnly
91
+ はフォーカスでき、選択・コピーでき、値はそのまま送信される。空のフィールドに Tab
92
+ で入るとフォーカスリングが出る(静的な画面では再現できないので自分で試すこと)。
26
93
  </CardDescription>
27
94
  </CardHeader>
28
95
  <CardContent>
29
96
  <Flex direction="col" gap="md">
30
- <Input aria-label="プレースホルダー状態" placeholder="プレースホルダー" />
31
- <Input aria-label="入力済み状態" defaultValue="入力済みの値" />
32
- <Input aria-label="無効状態" disabled defaultValue="無効 (disabled)" />
97
+ <Input aria-label="プレースホルダー状態" placeholder="株式会社ゴドー商事" />
98
+ <Input aria-label="入力済み状態" defaultValue="株式会社ゴドー商事" />
99
+ <Input aria-label="無効状態" disabled defaultValue="無効 (disabled) · 送信されない" />
33
100
  <Input
34
101
  aria-label="読み取り専用状態"
35
102
  readOnly
36
- defaultValue="読み取り専用 (readOnly)"
103
+ defaultValue="読み取り専用 (readOnly) · 送信される"
37
104
  />
38
105
  <Input
39
106
  aria-label="不正な値の状態"
@@ -46,110 +113,653 @@ export default function Demo() {
46
113
 
47
114
  <Card>
48
115
  <CardHeader>
49
- <CardTitle level={2}>Owned affordances · controlled and uncontrolled</CardTitle>
116
+ <CardTitle level={2}>検証状態 · error / warning / success / validating</CardTitle>
117
+ <CardDescription>
118
+ WHY: 赤い枠だけでは「何が悪いのか」は伝わらない。error は枠と文言の両方が要る。success
119
+ と validating(サーバー照会中)は Input の status には無く、FormField の
120
+ validateStatus + hasFeedback
121
+ が持つ。理由は非対称ではなく分担で、アイコン付きフィードバックの欄は FormField
122
+ の持ち物だから。
123
+ </CardDescription>
124
+ </CardHeader>
125
+ <CardContent>
126
+ <Flex direction="col" gap="md">
127
+ <FormField
128
+ id="state-error"
129
+ label="メールアドレス"
130
+ required
131
+ error="メールアドレスの形式が正しくありません"
132
+ >
133
+ <Input id="state-error" type="email" defaultValue="keiri@godo-shoji" />
134
+ </FormField>
135
+ <FormField
136
+ id="state-warning"
137
+ label="請求先メールアドレス"
138
+ validateStatus="warning"
139
+ hasFeedback
140
+ helper="フリーメールは請求書の到達率が下がる場合がある"
141
+ >
142
+ <Input id="state-warning" type="email" defaultValue="godo.keiri@example.com" />
143
+ </FormField>
144
+ <FormField
145
+ id="state-success"
146
+ label="法人番号"
147
+ validateStatus="success"
148
+ hasFeedback
149
+ helper="国税庁の法人番号システムと照合済み"
150
+ >
151
+ <Input id="state-success" defaultValue="7010001008844" inputMode="numeric" />
152
+ </FormField>
153
+ <FormField
154
+ id="state-validating"
155
+ label="サブドメイン"
156
+ validateStatus="validating"
157
+ hasFeedback
158
+ helper="使用可能かどうかを照会中"
159
+ >
160
+ <Input id="state-validating" addonAfter=".godx.jp" defaultValue="godo-shoji" />
161
+ </FormField>
162
+ <Text size="sm" tone="muted">
163
+ status は Input が自分で塗る2値(error / warning)。error だけが aria-invalid
164
+ も立てるので、赤い枠とスクリーンリーダーが聞く内容が一致する。
165
+ </Text>
166
+ <Input aria-label="status=error" status="error" defaultValue="status=error" />
167
+ <Input aria-label="status=warning" status="warning" defaultValue="status=warning" />
168
+ </Flex>
169
+ </CardContent>
170
+ </Card>
171
+
172
+ <Card>
173
+ <CardHeader>
174
+ <CardTitle level={2}>アフィックス · 箱の内側 (prefix / suffix)</CardTitle>
175
+ <CardDescription>
176
+ WHY: 単位のない数字は読み手に単位を推測させる。prefix / suffix
177
+ は箱のパディングの内側に入るので、値と同じ一つの塊に見える。prefix は aria-hidden
178
+ ではない(単位は意味であって装飾ではない)。
179
+ </CardDescription>
180
+ </CardHeader>
181
+ <CardContent>
182
+ <Flex direction="col" gap="md">
183
+ <Input aria-label="金額 (prefix)" prefix="¥" defaultValue="1,234,567" />
184
+ <Input aria-label="割引率 (suffix)" suffix="%" defaultValue="15" />
185
+ <Input
186
+ aria-label="税込金額 (prefix + suffix)"
187
+ prefix="¥"
188
+ suffix="円"
189
+ defaultValue="12,000"
190
+ />
191
+ <Input aria-label="重量" suffix="kg" defaultValue="18.5" />
192
+ </Flex>
193
+ </CardContent>
194
+ </Card>
195
+
196
+ <Card>
197
+ <CardHeader>
198
+ <CardTitle level={2}>アドオン · 箱の外側 (addonBefore / addonAfter)</CardTitle>
199
+ <CardDescription>
200
+ WHY: プロトコルやドメイン接尾辞はユーザーが打つ値ではない。addon
201
+ は独立した面として境界の外側に溶接され、接合側の角を閉じる。つまり「打つ場所」と「打たない場所」が形で分かれる。
202
+ </CardDescription>
203
+ </CardHeader>
204
+ <CardContent>
205
+ <Flex direction="col" gap="md">
206
+ <Input
207
+ aria-label="サイトURL"
208
+ addonBefore="https://"
209
+ addonAfter=".co.jp"
210
+ defaultValue="godo-shoji"
211
+ />
212
+ <Input
213
+ aria-label="社内ポータル"
214
+ addonBefore="https://"
215
+ defaultValue="portal.godx.jp/invoice"
216
+ />
217
+ <Input
218
+ aria-label="メール (ドメイン固定)"
219
+ addonAfter="@godo-shoji.co.jp"
220
+ defaultValue="keiri"
221
+ />
222
+ <Input
223
+ aria-label="単価 (アドオンとアフィックスの併用)"
224
+ addonBefore="¥"
225
+ addonAfter="/ 件"
226
+ prefix={<Icon as={Hash} size="sm" tone="muted" />}
227
+ defaultValue="480"
228
+ />
229
+ </Flex>
230
+ </CardContent>
231
+ </Card>
232
+
233
+ <Card>
234
+ <CardHeader>
235
+ <CardTitle level={2}>アイコンとクリア · 先頭・末尾は1つずつ</CardTitle>
50
236
  <CardDescription>
51
- Leading/trailing content and allowClear/onClear are rendered with both value modes.
237
+ WHY: 末尾のスロットは1つしかない。allowClear に値があるとき、クリアの✕は設定した
238
+ trailingIcon
239
+ を「置き換える」。両方が同時に出ることはない。これを知らずに自前の✕を重ねると二重になる。
52
240
  </CardDescription>
53
241
  </CardHeader>
54
242
  <CardContent>
55
243
  <Flex direction="col" gap="md">
244
+ <Input
245
+ aria-label="先頭アイコンのみ"
246
+ leadingIcon={<Icon as={Building2} size="sm" tone="muted" />}
247
+ placeholder="取引先名で絞り込む"
248
+ />
249
+ <Input
250
+ aria-label="末尾アイコンのみ"
251
+ trailingIcon={<Icon as={CalendarDays} size="sm" tone="muted" />}
252
+ defaultValue="2026-09-30"
253
+ readOnly
254
+ />
255
+ <Input
256
+ aria-label="先頭と末尾の両方"
257
+ leadingIcon={<Icon as={Mail} size="sm" tone="muted" />}
258
+ trailingIcon={<Icon as={Hash} size="sm" tone="muted" />}
259
+ defaultValue="keiri@godo-shoji.co.jp"
260
+ />
56
261
  <Input
57
262
  aria-label="制御されたクリア可能入力"
58
263
  value={contractValue}
59
- onChange={(event) => setContractValue(event.target.value)}
264
+ onValueChange={setContractValue}
60
265
  allowClear
61
266
  onClear={() => setContractValue("")}
62
- leadingIcon={<span aria-hidden="true">¥</span>}
267
+ leadingIcon={<Icon as={Building2} size="sm" tone="muted" />}
63
268
  />
64
269
  <Input
65
- aria-label="非制御の装飾付き入力"
66
- defaultValue="INV-2026-001"
67
- allowClear={false}
68
- trailingIcon={<span aria-hidden="true">#</span>}
270
+ aria-label="非制御のクリア可能入力"
271
+ defaultValue="INV-2026-0912-0031"
272
+ allowClear
273
+ trailingIcon={<Icon as={Hash} size="sm" tone="muted" />}
69
274
  />
275
+ <Text size="sm" tone="muted">
276
+ すぐ上の2つは値を持っているので、設定した trailingIcon ではなく✕が出ている。✕
277
+ で空にすると trailingIcon が戻る。
278
+ </Text>
279
+ <Input
280
+ aria-label="文字数カウンタ"
281
+ count={{ max: 20 }}
282
+ defaultValue="東京都の請求書"
283
+ placeholder="件名"
284
+ />
285
+ <Input aria-label="文字数超過" count={{ max: 5 }} defaultValue="長すぎる件名です" />
286
+ <Text size="sm" tone="muted">
287
+ カウンタは超過を「報告」するだけで、値を切らない。入力中の IME
288
+ 変換を途中で刈り取らないための判断。
289
+ </Text>
70
290
  </Flex>
71
291
  </CardContent>
72
292
  </Card>
73
293
 
74
294
  <Card>
75
295
  <CardHeader>
76
- <CardTitle level={2}>Types</CardTitle>
296
+ <CardTitle level={2}>実データの型 · メール / 電話 / 郵便番号 / URL / 機械ID</CardTitle>
77
297
  <CardDescription>
78
- The native type attribute drives the input mode and built-in UI; type="file" uses the
79
- file: styling baked into the component.
298
+ WHY: type と inputMode
299
+ はモバイルのキーボードと自動入力を決める。既定の文字キーボードで電話番号を打たせるのは設計の放棄。40
300
+ 文字の機械 ID は箱より長いので、ここで初めて「はみ出したらどうなるか」が見える。
80
301
  </CardDescription>
81
302
  </CardHeader>
82
303
  <CardContent>
83
304
  <Flex direction="col" gap="md">
84
- <Input aria-label="パスワード型" type="password" defaultValue="secret-value" />
85
- <Input aria-label="数値型" type="number" defaultValue={42} />
86
- <Input aria-label="日付型" type="date" defaultValue="2026-06-04" />
87
- <Input type="file" aria-label="ファイルを選択" />
305
+ <FormField id="ct-email" label="担当者メールアドレス" required>
306
+ <Input
307
+ id="ct-email"
308
+ type="email"
309
+ name="contact_email"
310
+ autoComplete="email"
311
+ leadingIcon={<Icon as={Mail} size="sm" tone="muted" />}
312
+ defaultValue="keiri@godo-shoji.co.jp"
313
+ />
314
+ </FormField>
315
+ <FormField id="ct-tel" label="代表電話番号" helper="市外局番から半角で入力">
316
+ <Input
317
+ id="ct-tel"
318
+ type="tel"
319
+ name="tel"
320
+ inputMode="tel"
321
+ autoComplete="tel"
322
+ leadingIcon={<Icon as={Phone} size="sm" tone="muted" />}
323
+ defaultValue="03-6205-3371"
324
+ />
325
+ </FormField>
326
+ <FormField id="ct-postal" label="郵便番号" helper="ハイフンなし7桁でも可">
327
+ <Input
328
+ id="ct-postal"
329
+ name="postal_code"
330
+ inputMode="numeric"
331
+ autoComplete="postal-code"
332
+ leadingIcon={<Icon as={MapPin} size="sm" tone="muted" />}
333
+ addonBefore="〒"
334
+ defaultValue="105-0011"
335
+ />
336
+ </FormField>
337
+ <FormField id="ct-url" label="会社サイト">
338
+ <Input
339
+ id="ct-url"
340
+ type="url"
341
+ name="website"
342
+ autoComplete="url"
343
+ leadingIcon={<Icon as={Link2} size="sm" tone="muted" />}
344
+ addonBefore="https://"
345
+ defaultValue="www.godo-shoji.co.jp"
346
+ />
347
+ </FormField>
348
+ <FormField
349
+ id="ct-machine"
350
+ label="連携キー"
351
+ helper="40文字。箱より長い値は折り返さず、箱の中で横スクロールする"
352
+ >
353
+ <Input
354
+ id="ct-machine"
355
+ name="integration_key"
356
+ readOnly
357
+ allowClear={false}
358
+ className="font-mono"
359
+ defaultValue="a3f9c1e07b2d4856ab10ef93cd77420159be8d36"
360
+ />
361
+ </FormField>
88
362
  </Flex>
89
363
  </CardContent>
90
364
  </Card>
91
365
 
92
366
  <Card>
93
367
  <CardHeader>
94
- <CardTitle level={2}>In FormField · label / helper / required / error</CardTitle>
368
+ <CardTitle level={2}>数値と金額 · 桁が動かないこと</CardTitle>
95
369
  <CardDescription>
96
- FormField wires the label and aria-describedby for you.
370
+ WHY:
371
+ 金額を縦に並べたとき、プロポーショナル数字だと桁の位置が1行ごとにずれて比較できない。Input
372
+ には tabular
373
+ プロップが無いので等幅数字はクラスで足している(既知のギャップ)。刻み・クランプ・ロケール整形が要るなら
374
+ Input ではなく NumberInput が正解。
97
375
  </CardDescription>
98
376
  </CardHeader>
99
377
  <CardContent>
100
378
  <Flex direction="col" gap="md">
101
- <FormField id="partner" label="取引先名" required helper="最大50文字">
102
- <Input id="partner" placeholder="株式会社..." />
379
+ <Flex direction="col" gap="xs">
380
+ <Text size="sm" tone="muted">
381
+ Input · 等幅数字なし(桁が揃わない)
382
+ </Text>
383
+ <Input aria-label="金額 (等幅なし) 1" prefix="¥" defaultValue="1,234,567" />
384
+ <Input aria-label="金額 (等幅なし) 2" prefix="¥" defaultValue="9,888,111" />
385
+ </Flex>
386
+ <Flex direction="col" gap="xs">
387
+ <Text size="sm" tone="muted">
388
+ Input · 等幅数字あり(桁が揃う)
389
+ </Text>
390
+ <Input
391
+ aria-label="金額 (等幅あり) 1"
392
+ prefix="¥"
393
+ className="tabular-nums"
394
+ defaultValue="1,234,567"
395
+ />
396
+ <Input
397
+ aria-label="金額 (等幅あり) 2"
398
+ prefix="¥"
399
+ className="tabular-nums"
400
+ defaultValue="9,888,111"
401
+ />
402
+ </Flex>
403
+ <Flex direction="col" gap="xs">
404
+ <Text size="sm" tone="muted">
405
+ NumberInput · ステッパー・クランプ・Intl 整形つき
406
+ </Text>
407
+ <ResponsiveGrid columns={{ base: 1, md: 2 }} gap="md" align="start">
408
+ <FormField id="num-amount" label="請求金額">
409
+ <NumberInput
410
+ id="num-amount"
411
+ value={zeroNumber}
412
+ onValueChange={setZeroNumber}
413
+ min={0}
414
+ step={1000}
415
+ prefix="¥"
416
+ />
417
+ </FormField>
418
+ <FormField id="num-rate" label="割引率">
419
+ <NumberInput
420
+ id="num-rate"
421
+ value={emptyNumber}
422
+ onValueChange={setEmptyNumber}
423
+ min={0}
424
+ max={100}
425
+ step={5}
426
+ suffix="%"
427
+ placeholder="未設定"
428
+ />
429
+ </FormField>
430
+ </ResponsiveGrid>
431
+ </Flex>
432
+ </Flex>
433
+ </CardContent>
434
+ </Card>
435
+
436
+ <Card>
437
+ <CardHeader>
438
+ <CardTitle level={2}>専用コンポーネントに譲る場面</CardTitle>
439
+ <CardDescription>
440
+ WHY: Input
441
+ で代用すると必ず何かが抜ける。パスワードなら表示切替・CapsLock・autocomplete
442
+ 契約、検索ならデバウンスとクリアと role=searchbox。3つとも自前実装では落ちる。
443
+ </CardDescription>
444
+ </CardHeader>
445
+ <CardContent>
446
+ <Flex direction="col" gap="md">
447
+ <FormField id="pw-current" label="現在のパスワード" required>
448
+ <PasswordInput
449
+ id="pw-current"
450
+ name="current_password"
451
+ autoComplete="current-password"
452
+ placeholder="パスワードを入力"
453
+ />
103
454
  </FormField>
104
- <FormField id="email" label="メール" error="メールアドレスの形式が正しくありません">
105
- <Input id="email" type="email" defaultValue="invalid@" />
455
+ {/* xs を PasswordInput でも一度は描く。size は Input の型をそのまま継いでいるので、
456
+ 片方だけ描いて済ませると frame-contracts が「宣言した分岐に証拠が無い」と落ちる
457
+ ── 公開した分岐は描いて証明する、というのがこのリポジトリの契約。 */}
458
+ <Flex width={220}>
459
+ <PasswordInput
460
+ id="pw-pin"
461
+ size="xs"
462
+ name="pin"
463
+ aria-label="PIN (xs)"
464
+ autoComplete="off"
465
+ defaultValue="8412"
466
+ />
467
+ </Flex>
468
+ <FormField id="pw-api" label="APIシークレット" helper="発行後は再表示できない">
469
+ <PasswordInput
470
+ id="pw-api"
471
+ name="api_secret"
472
+ autoComplete="new-password"
473
+ defaultValue="sk_live_9f2c41ab77de"
474
+ />
106
475
  </FormField>
476
+ <SearchInput
477
+ label="取引先を検索"
478
+ value={partnerQuery}
479
+ onValueChange={setPartnerQuery}
480
+ onSearch={setPartnerQuery}
481
+ placeholder="取引先名・法人番号で検索"
482
+ />
483
+ <Text size="sm" tone="muted">
484
+ 逆に、フォーム送信で name を伴う検索欄なら SearchInput ではなく FormField + Input
485
+ が正しい。SearchInput は値を送らないフィルタ用のウィジェット。
486
+ </Text>
487
+ </Flex>
488
+ </CardContent>
489
+ </Card>
490
+
491
+ <Card>
492
+ <CardHeader>
493
+ <CardTitle level={2}>幅の振る舞い · 箱は常に親の幅を埋める</CardTitle>
494
+ <CardDescription>
495
+ WHY: Input に width プロップは無い(既知のギャップ)。箱は必ず親を 100%
496
+ 埋めるので、幅を決めるのは常に外側のレイアウト。これを知らないと「なぜツールバーで巨大になるのか」が分からない。
497
+ </CardDescription>
498
+ </CardHeader>
499
+ <CardContent>
500
+ <Flex direction="col" gap="md">
501
+ <Flex direction="col" gap="xs">
502
+ <Text size="sm" tone="muted">
503
+ フォームの中 · 全幅(FormField がそのまま親を埋める)
504
+ </Text>
505
+ <FormField id="w-full" label="取引先名" required helper="最大50文字">
506
+ <Input id="w-full" placeholder="株式会社ゴドー商事" />
507
+ </FormField>
508
+ </Flex>
509
+ <Flex direction="col" gap="xs">
510
+ <Text size="sm" tone="muted">
511
+ ツールバーの中 · 内容に合わせて親側で詰める
512
+ </Text>
513
+ <Flex direction="row" gap="sm" align="center" wrap>
514
+ <Flex width={220}>
515
+ <Input aria-label="請求番号で絞り込む" size="sm" placeholder="請求番号" />
516
+ </Flex>
517
+ <Flex width={120}>
518
+ <Input aria-label="年度" size="sm" defaultValue="2026" inputMode="numeric" />
519
+ </Flex>
520
+ <Flex width={160}>
521
+ <Input aria-label="担当者コード" size="sm" placeholder="担当者コード" />
522
+ </Flex>
523
+ </Flex>
524
+ </Flex>
525
+ <Flex direction="col" gap="xs">
526
+ <Text size="sm" tone="muted">
527
+ 短い項目と長い項目を同じ行に · 比率は親のグリッドが決める
528
+ </Text>
529
+ <ResponsiveGrid columns={{ base: 1, md: 4 }} gap="md" align="start">
530
+ <FormField id="w-short" label="階数" helper="数字のみ">
531
+ <Input id="w-short" inputMode="numeric" defaultValue="3" />
532
+ </FormField>
533
+ <FormField id="w-long" label="建物名・部屋番号" colSpan={3}>
534
+ <Input id="w-long" defaultValue="東京タワーフットタウン 経理部 請求管理課" />
535
+ </FormField>
536
+ </ResponsiveGrid>
537
+ </Flex>
107
538
  </Flex>
108
539
  </CardContent>
109
540
  </Card>
541
+
110
542
  <Card>
111
543
  <CardHeader>
112
- <CardTitle level={2}>Ant Design axes · status, variant, size</CardTitle>
544
+ <CardTitle level={2}>ストレス · 箱に収まらない値</CardTitle>
113
545
  <CardDescription>
114
- status is the validation state a form paints consistently (error also reports
115
- aria-invalid; warning does not, because a warning is not a validity failure). variant
116
- is how much chrome the field draws. size is the shared control-height ladder.
546
+ WHY: 実務の住所・部署名は必ず箱より長い。Input
547
+ は単一行なので折り返さず、省略記号も付けず、箱の中で横スクロールする。フォーカスを抜けると先頭に戻る。長さが問題になるなら
548
+ Textarea か、値を全部見せる Descriptions に譲る判断が要る。
117
549
  </CardDescription>
118
550
  </CardHeader>
119
551
  <CardContent>
120
552
  <Flex direction="col" gap="md">
121
- <Input aria-label="エラー状態" status="error" defaultValue="不正な値" />
122
- <Input aria-label="警告状態" status="warning" defaultValue="確認してください" />
123
- <Input aria-label="枠線あり" variant="outlined" defaultValue="outlined" />
124
- <Input aria-label="塗りつぶし" variant="filled" defaultValue="filled" />
125
- <Input aria-label="枠線なし" variant="borderless" defaultValue="borderless" />
126
- <Input aria-label="小さいサイズ" size="sm" placeholder="Compact tier (sm)" />
127
- <Input aria-label="標準サイズ" size="md" placeholder="Default tier (md)" />
128
- <Input aria-label="大きいサイズ" size="lg" placeholder="Comfortable tier (lg)" />
553
+ <FormField
554
+ id="stress-overflow"
555
+ label="請求書送付先"
556
+ helper="46文字。末尾までキャレットを送ると先頭が隠れる"
557
+ >
558
+ <Input
559
+ id="stress-overflow"
560
+ value={overflow}
561
+ onValueChange={setOverflow}
562
+ allowClear
563
+ onClear={() => setOverflow("")}
564
+ />
565
+ </FormField>
566
+ <FormField id="stress-overflow-sm" label="同じ値を sm の箱で">
567
+ <Input id="stress-overflow-sm" size="sm" readOnly defaultValue={overflow} />
568
+ </FormField>
569
+ <FormField
570
+ id="stress-overflow-addon"
571
+ label="アドオンがあると箱はさらに狭くなる"
572
+ helper="addon は固定幅を取り、残りが入力領域になる"
573
+ >
574
+ <Input
575
+ id="stress-overflow-addon"
576
+ addonBefore="送付先"
577
+ addonAfter="宛"
578
+ readOnly
579
+ defaultValue={overflow}
580
+ />
581
+ </FormField>
129
582
  </Flex>
130
583
  </CardContent>
131
584
  </Card>
132
585
 
133
586
  <Card>
134
587
  <CardHeader>
135
- <CardTitle level={2}>Affixes · prefix / suffix inside, addons outside</CardTitle>
588
+ <CardTitle level={2}>ストレス · 空とゼロは別物</CardTitle>
136
589
  <CardDescription>
137
- A prefix or suffix sits inside the field&apos;s own box, in its padding. An addon is a
138
- separate surface welded to the outside of the border: a protocol, a currency, a unit.
139
- The counter reports an overrun and never edits the value.
590
+ WHY:
591
+ 「0」と「未入力」を同じものとして扱うと、値引き0円と値引き未定の区別が消える。テキストの
592
+ Input では空文字が「未入力」、&quot;0&quot; は入力済み。NumberInput では null
593
+ が「未入力」で、0 に置き換えてはいけない。
140
594
  </CardDescription>
141
595
  </CardHeader>
142
596
  <CardContent>
143
597
  <Flex direction="col" gap="md">
144
- <Input aria-label="金額" prefix="¥" suffix="円" defaultValue="12000" />
598
+ <ResponsiveGrid columns={{ base: 1, md: 2 }} gap="md" align="start">
599
+ <FormField
600
+ id="empty-text"
601
+ label="値引き額(テキスト)"
602
+ helper={
603
+ emptyText === "" ? "現在の値: 空文字(未入力)" : `現在の値: ${emptyText}`
604
+ }
605
+ >
606
+ <Input
607
+ id="empty-text"
608
+ value={emptyText}
609
+ onValueChange={setEmptyText}
610
+ prefix="¥"
611
+ placeholder="未入力"
612
+ allowClear
613
+ />
614
+ </FormField>
615
+ <FormField
616
+ id="zero-text"
617
+ label="値引き額(0が入っている)"
618
+ helper={zeroText === "" ? "現在の値: 空文字(未入力)" : `現在の値: ${zeroText}`}
619
+ >
620
+ <Input
621
+ id="zero-text"
622
+ value={zeroText}
623
+ onValueChange={setZeroText}
624
+ prefix="¥"
625
+ placeholder="未入力"
626
+ allowClear
627
+ />
628
+ </FormField>
629
+ </ResponsiveGrid>
630
+ <Text size="sm" tone="muted">
631
+ プレースホルダーが見えている側が「未入力」。✕
632
+ で空にすると、両者は見た目でも状態でも同じになる。
633
+ </Text>
634
+ </Flex>
635
+ </CardContent>
636
+ </Card>
637
+
638
+ <Card>
639
+ <CardHeader>
640
+ <CardTitle level={2}>ストレス · ラベルの長さが揃わない行</CardTitle>
641
+ <CardDescription>
642
+ WHY:
643
+ 日本語のラベルは2文字にも3行にもなる。ラベル行が伸びても入力欄の上端が揃うのは、行の
644
+ align が start で、各セルが独立して縦に伸びるから。center
645
+ にすると短いラベルの箱が下がって崩れる。
646
+ </CardDescription>
647
+ </CardHeader>
648
+ <CardContent>
649
+ <ResponsiveGrid columns={{ base: 1, md: 3 }} gap="md" align="start">
650
+ <FormField id="label-short" label="都道" helper="2文字ラベル">
651
+ <Input id="label-short" defaultValue="東京都" />
652
+ </FormField>
653
+ <FormField
654
+ id="label-long"
655
+ label="請求書の送付先として登録されている経理担当部署の正式名称"
656
+ helper="3行になるラベル"
657
+ >
658
+ <Input id="label-long" defaultValue="経理部 請求管理課" />
659
+ </FormField>
660
+ <FormField id="label-mid" label="内線番号" helper="4桁">
661
+ <Input id="label-mid" inputMode="numeric" defaultValue="4182" />
662
+ </FormField>
663
+ </ResponsiveGrid>
664
+ </CardContent>
665
+ </Card>
666
+
667
+ <Card>
668
+ <CardHeader>
669
+ <CardTitle level={2}>ストレス · RTL</CardTitle>
670
+ <CardDescription>
671
+ WHY: prefix / addonBefore
672
+ は「開始側」であって「左」ではない。論理プロパティで書かれているので
673
+ dir=&quot;rtl&quot;
674
+ で自動的に右端へ移る。物理方向のクラスを1つでも混ぜると、ここで崩れる。
675
+ </CardDescription>
676
+ </CardHeader>
677
+ <CardContent>
678
+ <Flex direction="col" gap="md" dir="rtl" lang="ar">
679
+ <FormField id="rtl-name" label="اسم الشركة" required>
680
+ <Input
681
+ id="rtl-name"
682
+ defaultValue="شركة غودو التجارية"
683
+ leadingIcon={<Icon as={Building2} size="sm" tone="muted" />}
684
+ allowClear
685
+ />
686
+ </FormField>
687
+ <FormField id="rtl-site" label="الموقع الإلكتروني">
688
+ <Input
689
+ id="rtl-site"
690
+ addonBefore="https://"
691
+ addonAfter=".sa"
692
+ defaultValue="godo-shoji"
693
+ />
694
+ </FormField>
695
+ <FormField id="rtl-rate" label="نسبة الخصم">
696
+ <Input id="rtl-rate" prefix="%" suffix="خصم" defaultValue="15" />
697
+ </FormField>
698
+ <FormField id="rtl-subject" label="الموضوع">
699
+ <Input id="rtl-subject" count={{ max: 20 }} defaultValue="فاتورة سبتمبر" />
700
+ </FormField>
701
+ </Flex>
702
+ </CardContent>
703
+ </Card>
704
+
705
+ <Card>
706
+ <CardHeader>
707
+ <CardTitle level={2}>chrome の段 · variant</CardTitle>
708
+ <CardDescription>
709
+ WHY: 既に枠を描いている面(テーブルのセル、カードの中の小さな箱)に outlined
710
+ を入れると線が二重になる。borderless はその場合の答えで、密なフォームでは filled
711
+ が境界の数を減らす。
712
+ </CardDescription>
713
+ </CardHeader>
714
+ <CardContent>
715
+ <Flex direction="col" gap="md">
716
+ <Input aria-label="枠線あり" variant="outlined" defaultValue="outlined · 既定" />
145
717
  <Input
146
- aria-label="サイト"
147
- addonBefore="https://"
148
- addonAfter=".co.jp"
149
- defaultValue="example"
718
+ aria-label="塗りつぶし"
719
+ variant="filled"
720
+ defaultValue="filled · 密なフォーム"
150
721
  />
151
- <Input aria-label="件名" count={{ max: 20 }} defaultValue="東京都の請求書" />
152
- <Input aria-label="超過した件名" count={{ max: 5 }} defaultValue="長すぎる件名です" />
722
+ <Input
723
+ aria-label="枠線なし"
724
+ variant="borderless"
725
+ defaultValue="borderless · 既に枠のある面の中"
726
+ />
727
+ <Input
728
+ aria-label="塗りつぶし・エラー"
729
+ variant="filled"
730
+ status="error"
731
+ defaultValue="filled + status=error"
732
+ />
733
+ </Flex>
734
+ </CardContent>
735
+ </Card>
736
+
737
+ <Card>
738
+ <CardHeader>
739
+ <CardTitle level={2}>既知のギャップ</CardTitle>
740
+ <CardDescription>
741
+ このページを書いている途中で見つかった、Input 側の穴。回避策で隠さずここに書いておく。
742
+ </CardDescription>
743
+ </CardHeader>
744
+ <CardContent>
745
+ <Flex direction="col" gap="sm">
746
+ <Text size="sm">
747
+ 1. size に xs 段が無い。NumberInput・Select・Button は xs を受けるのに InputProp は
748
+ sm / md / lg だけ。CSS 側には .ui-control[data-size=&quot;xs&quot;] も
749
+ --control-height-xs もあるので、塞がっているのは型だけ。
750
+ </Text>
751
+ <Text size="sm">
752
+ 2. tabular プロップが無い。Text と Badge は tabular を持つのに、金額を縦に並べる
753
+ Input と NumberInput は等幅数字をクラスで足すしかない。
754
+ </Text>
755
+ <Text size="sm">
756
+ 3. width プロップが無い。箱は常に親を 100%
757
+ 埋めるので、ツールバーでは必ず外側に幅を持つ箱が要る。
758
+ </Text>
759
+ <Text size="sm">
760
+ 4. status に success / validating が無い。FormField の validateStatus + hasFeedback
761
+ で足りてはいるが、ラベルの無い裸の Input では成功も照会中も表現できない。
762
+ </Text>
153
763
  </Flex>
154
764
  </CardContent>
155
765
  </Card>