@godxjp/ui 30.8.0 → 31.0.2

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 (56) hide show
  1. package/README.md +25 -2
  2. package/agent/START-HERE.md +1 -1
  3. package/agent/components/PrefetchLink.json +3 -3
  4. package/agent/components-index.json +1 -1
  5. package/agent/components.json +3 -3
  6. package/agent/index.json +2 -2
  7. package/agent/llms.txt +3 -3
  8. package/agent/patterns/settings-page-responsive.json +1 -1
  9. package/agent/patterns.json +1 -1
  10. package/dist/components/query/index.d.ts +0 -2
  11. package/dist/components/query/index.js +0 -2
  12. package/dist/components/react-router/index.d.ts +2 -0
  13. package/dist/components/react-router/index.js +4 -0
  14. package/dist/contracts/measurement.json +1 -1
  15. package/dist/styles/alert-layout.css +4 -2
  16. package/dist/styles/layers.json +1182 -0
  17. package/dist/styles/vendor-day-picker.css +2 -0
  18. package/dist/styles/vendor-sonner.css +2 -0
  19. package/docs/CONSUMER-RULES.md +1 -1
  20. package/docs/data-display/permission-matrix.tsx +30 -28
  21. package/docs/data-entry/cascader.tsx +22 -20
  22. package/docs/data-entry/date-picker.tsx +47 -43
  23. package/docs/data-entry/form-field/examples/a11y-contract.tsx +80 -76
  24. package/docs/data-entry/form-field/examples/create-form.tsx +33 -31
  25. package/docs/data-entry/form-field/index.tsx +61 -57
  26. package/docs/data-entry/form.tsx +112 -106
  27. package/docs/data-entry/input.tsx +297 -270
  28. package/docs/data-entry/label.tsx +11 -9
  29. package/docs/data-entry/number-input.tsx +86 -78
  30. package/docs/data-entry/radio-group.tsx +33 -26
  31. package/docs/data-entry/rating.tsx +29 -25
  32. package/docs/data-entry/select-matrix.tsx +49 -47
  33. package/docs/data-entry/select.tsx +38 -35
  34. package/docs/data-entry/slider.tsx +45 -41
  35. package/docs/data-entry/textarea.tsx +44 -42
  36. package/docs/data-entry/time-picker.tsx +51 -47
  37. package/docs/feedback/sheet.tsx +84 -79
  38. package/docs/foundation/theme-editor.tsx +133 -124
  39. package/docs/general/button/examples/form-actions.tsx +17 -15
  40. package/docs/layout/auth-recovery/examples/password-recovery.tsx +31 -29
  41. package/docs/layout/auth-shell-registration.tsx +75 -72
  42. package/docs/layout/auth-shell-variants.tsx +55 -51
  43. package/docs/layout/space-compact.tsx +11 -8
  44. package/docs/{query → react-router}/prefetch-link.tsx +1 -1
  45. package/docs/showcase/caimono-price-comparison.tsx +59 -51
  46. package/docs/showcase/case3-approval-workflow.tsx +27 -25
  47. package/docs/showcase/case4-login.tsx +37 -34
  48. package/docs/showcase/permission-matrix.tsx +44 -42
  49. package/docs/showcase/theme-customization.tsx +108 -99
  50. package/package.json +10 -3
  51. package/scripts/cli.mjs +15 -0
  52. package/scripts/consumer-rule.md +14 -0
  53. package/scripts/prune-css.mjs +221 -0
  54. package/scripts/ui-audit.mjs +99 -2
  55. /package/dist/components/{query → react-router}/prefetch-link.d.ts +0 -0
  56. /package/dist/components/{query → react-router}/prefetch-link.js +0 -0
@@ -1,7 +1,7 @@
1
1
  import { useState } from "react";
2
2
 
3
3
  import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@godxjp/ui/data-display";
4
- import { FormField, Input, PasswordInput, RadioGroup } from "@godxjp/ui/data-entry";
4
+ import { Form, FormField, Input, PasswordInput, RadioGroup } from "@godxjp/ui/data-entry";
5
5
  import { Button, Logo, Text } from "@godxjp/ui/general";
6
6
  import {
7
7
  AuthDivider,
@@ -68,19 +68,21 @@ export default function Demo() {
68
68
  <CardDescription>組織アカウントの資格情報を入力してください。</CardDescription>
69
69
  </CardHeader>
70
70
  <CardContent>
71
- <AuthStack>
72
- <FormField id="auth-axis-email" label="メールアドレス" layout="vertical" required>
73
- <Input id="auth-axis-email" type="email" placeholder="you@example.co.jp" />
74
- </FormField>
75
- <FormField id="auth-axis-password" label="パスワード" layout="vertical" required>
76
- <PasswordInput id="auth-axis-password" />
77
- </FormField>
78
- <Button fullWidth>サインイン</Button>
79
- <AuthDivider label="または" />
80
- <Button variant="outline" fullWidth>
81
- パスキーでサインイン
82
- </Button>
83
- </AuthStack>
71
+ <Form>
72
+ <AuthStack>
73
+ <FormField id="auth-axis-email" label="メールアドレス" layout="vertical" required>
74
+ <Input id="auth-axis-email" type="email" placeholder="you@example.co.jp" />
75
+ </FormField>
76
+ <FormField id="auth-axis-password" label="パスワード" layout="vertical" required>
77
+ <PasswordInput id="auth-axis-password" />
78
+ </FormField>
79
+ <Button fullWidth>サインイン</Button>
80
+ <AuthDivider label="または" />
81
+ <Button variant="outline" fullWidth>
82
+ パスキーでサインイン
83
+ </Button>
84
+ </AuthStack>
85
+ </Form>
84
86
  </CardContent>
85
87
  </Card>
86
88
 
@@ -94,43 +96,45 @@ export default function Demo() {
94
96
  </CardDescription>
95
97
  </CardHeader>
96
98
  <CardContent>
97
- <Flex direction="col" gap="md">
98
- <FormField
99
- id="auth-axis-variant"
100
- label="variant · シェルの寸法"
101
- layout="vertical"
102
- helper="canonical は GoDX ID の正準寸法、default は素の中央寄せシェル。"
103
- >
104
- <RadioGroup
105
- orientation="horizontal"
106
- value={variant}
107
- onValueChange={(next) => setVariant(next as AuthShellVariant)}
108
- options={[
109
- { value: "default", label: "default(既定)" },
110
- { value: "canonical", label: "canonical(正準)" },
111
- ]}
112
- />
113
- </FormField>
114
- <FormField
115
- id="auth-axis-density"
116
- label="density · 操作要素の段"
117
- layout="vertical"
118
- helper="comfortable は 44px のタッチ下限、compact は情報密度を優先した段。"
119
- >
120
- <RadioGroup
121
- orientation="horizontal"
122
- value={density}
123
- onValueChange={(next) => setDensity(next as AuthShellDensity)}
124
- options={[
125
- { value: "comfortable", label: "comfortable(44px)" },
126
- { value: "compact", label: "compact" },
127
- ]}
128
- />
129
- </FormField>
130
- <Text size="sm" tone="muted">
131
- 現在の組み合わせ: variant=&quot;{variant}&quot; · density=&quot;{density}&quot;
132
- </Text>
133
- </Flex>
99
+ <Form>
100
+ <Flex direction="col" gap="md">
101
+ <FormField
102
+ id="auth-axis-variant"
103
+ label="variant · シェルの寸法"
104
+ layout="vertical"
105
+ helper="canonical は GoDX ID の正準寸法、default は素の中央寄せシェル。"
106
+ >
107
+ <RadioGroup
108
+ orientation="horizontal"
109
+ value={variant}
110
+ onValueChange={(next) => setVariant(next as AuthShellVariant)}
111
+ options={[
112
+ { value: "default", label: "default(既定)" },
113
+ { value: "canonical", label: "canonical(正準)" },
114
+ ]}
115
+ />
116
+ </FormField>
117
+ <FormField
118
+ id="auth-axis-density"
119
+ label="density · 操作要素の段"
120
+ layout="vertical"
121
+ helper="comfortable は 44px のタッチ下限、compact は情報密度を優先した段。"
122
+ >
123
+ <RadioGroup
124
+ orientation="horizontal"
125
+ value={density}
126
+ onValueChange={(next) => setDensity(next as AuthShellDensity)}
127
+ options={[
128
+ { value: "comfortable", label: "comfortable(44px)" },
129
+ { value: "compact", label: "compact" },
130
+ ]}
131
+ />
132
+ </FormField>
133
+ <Text size="sm" tone="muted">
134
+ 現在の組み合わせ: variant=&quot;{variant}&quot; · density=&quot;{density}&quot;
135
+ </Text>
136
+ </Flex>
137
+ </Form>
134
138
  </CardContent>
135
139
  </Card>
136
140
  </Flex>
@@ -9,6 +9,7 @@ import {
9
9
  Descriptions,
10
10
  } from "@godxjp/ui/data-display";
11
11
  import {
12
+ Form,
12
13
  FormField,
13
14
  Input,
14
15
  NumberInput,
@@ -183,14 +184,16 @@ export default function SpaceCompactShowcase() {
183
184
  <CardDescription>{t("spaceCompactDocs.dont.body")}</CardDescription>
184
185
  </CardHeader>
185
186
  <CardContent>
186
- <Flex direction="row" gap="md" wrap>
187
- <FormField label={t("spaceCompactDocs.dont.dept")}>
188
- <Input placeholder="D-1024" aria-label={t("spaceCompactDocs.dont.dept")} />
189
- </FormField>
190
- <FormField label={t("spaceCompactDocs.dont.ext")}>
191
- <Input placeholder="2831" aria-label={t("spaceCompactDocs.dont.ext")} />
192
- </FormField>
193
- </Flex>
187
+ <Form>
188
+ <Flex direction="row" gap="md" wrap>
189
+ <FormField label={t("spaceCompactDocs.dont.dept")}>
190
+ <Input placeholder="D-1024" aria-label={t("spaceCompactDocs.dont.dept")} />
191
+ </FormField>
192
+ <FormField label={t("spaceCompactDocs.dont.ext")}>
193
+ <Input placeholder="2831" aria-label={t("spaceCompactDocs.dont.ext")} />
194
+ </FormField>
195
+ </Flex>
196
+ </Form>
194
197
  </CardContent>
195
198
  </Card>
196
199
  </Flex>
@@ -18,7 +18,7 @@ import {
18
18
  TableRow,
19
19
  } from "@godxjp/ui/data-display";
20
20
  import { Flex, PageContainer } from "@godxjp/ui/layout";
21
- import { PrefetchLink } from "@godxjp/ui/query";
21
+ import { PrefetchLink } from "@godxjp/ui/react-router";
22
22
 
23
23
  /**
24
24
  * PrefetchLink · a React Router Link that fires queryClient.prefetchQuery on
@@ -17,6 +17,7 @@ import {
17
17
  type ColumnDef,
18
18
  } from "@godxjp/ui/data-display";
19
19
  import {
20
+ Form,
20
21
  Checkbox,
21
22
  FormField,
22
23
  NumberInput,
@@ -468,31 +469,33 @@ export default function CaimonoPriceComparison() {
468
469
  <Card style={{ "--card-radius": "var(--radius-2xl)" } as React.CSSProperties}>
469
470
  <CardContent solo>
470
471
  <ResponsiveGrid columns={{ base: 1, md: 2 }} gap="xl">
471
- <Flex direction="col" gap="md">
472
- <Badge tone="neutral">{t("caimono.synthetic")}</Badge>
473
- <Heading level={1}>{t("caimono.product")}</Heading>
474
- <Text tone="muted">{t("caimono.productMeta", { count: number(1) })}</Text>
475
- <FormField label={t("caimono.variant")}>
476
- <Select
477
- value={variant}
478
- onValueChange={setVariant}
479
- options={["black", "cream"].map((value) => ({
480
- value,
481
- label: t(`caimono.${value}`),
482
- }))}
483
- />
484
- </FormField>
485
- <FormField label={t("caimono.condition")}>
486
- <Segmented
487
- value={condition}
488
- onValueChange={setCondition}
489
- options={["new", "used"].map((value) => ({
490
- value,
491
- label: t(`caimono.${value}`),
492
- }))}
493
- />
494
- </FormField>
495
- </Flex>
472
+ <Form>
473
+ <Flex direction="col" gap="md">
474
+ <Badge tone="neutral">{t("caimono.synthetic")}</Badge>
475
+ <Heading level={1}>{t("caimono.product")}</Heading>
476
+ <Text tone="muted">{t("caimono.productMeta", { count: number(1) })}</Text>
477
+ <FormField label={t("caimono.variant")}>
478
+ <Select
479
+ value={variant}
480
+ onValueChange={setVariant}
481
+ options={["black", "cream"].map((value) => ({
482
+ value,
483
+ label: t(`caimono.${value}`),
484
+ }))}
485
+ />
486
+ </FormField>
487
+ <FormField label={t("caimono.condition")}>
488
+ <Segmented
489
+ value={condition}
490
+ onValueChange={setCondition}
491
+ options={["new", "used"].map((value) => ({
492
+ value,
493
+ label: t(`caimono.${value}`),
494
+ }))}
495
+ />
496
+ </FormField>
497
+ </Flex>
498
+ </Form>
496
499
  <Flex direction="col" gap="md">
497
500
  <Text tone="muted">{t("caimono.lowest")}</Text>
498
501
  <Text size="4xl" weight="bold" tabular>
@@ -544,32 +547,37 @@ export default function CaimonoPriceComparison() {
544
547
  <Text tone="muted" size="sm">
545
548
  {t("caimono.snapshot", { date: date(snapshot) })}
546
549
  </Text>
547
- <ResponsiveGrid columns={{ base: 1, md: 3 }} gap="md">
548
- <FormField label={t("caimono.findShop")}>
549
- <SearchInput
550
- value={query}
551
- onValueChange={setQuery}
552
- placeholder={t("caimono.searchPlaceholder")}
553
- />
554
- </FormField>
555
- <FormField label={t("caimono.deliverTo")}>
556
- <Select
557
- value={destination}
558
- onValueChange={(v: string) => setDestination(v === "Osaka" ? "Osaka" : "Tokyo")}
559
- options={["Tokyo", "Osaka"].map((value) => ({ value, label: t(`caimono.${value}`) }))}
560
- />
561
- </FormField>
562
- <FormField label={t("caimono.sort")}>
563
- <Select
564
- value={sort}
565
- onValueChange={setSort}
566
- options={[
567
- { value: "total", label: t("caimono.totalSort") },
568
- { value: "delivery", label: t("caimono.deliverySort") },
569
- ]}
570
- />
571
- </FormField>
572
- </ResponsiveGrid>
550
+ <Form>
551
+ <ResponsiveGrid columns={{ base: 1, md: 3 }} gap="md">
552
+ <FormField label={t("caimono.findShop")}>
553
+ <SearchInput
554
+ value={query}
555
+ onValueChange={setQuery}
556
+ placeholder={t("caimono.searchPlaceholder")}
557
+ />
558
+ </FormField>
559
+ <FormField label={t("caimono.deliverTo")}>
560
+ <Select
561
+ value={destination}
562
+ onValueChange={(v: string) => setDestination(v === "Osaka" ? "Osaka" : "Tokyo")}
563
+ options={["Tokyo", "Osaka"].map((value) => ({
564
+ value,
565
+ label: t(`caimono.${value}`),
566
+ }))}
567
+ />
568
+ </FormField>
569
+ <FormField label={t("caimono.sort")}>
570
+ <Select
571
+ value={sort}
572
+ onValueChange={setSort}
573
+ options={[
574
+ { value: "total", label: t("caimono.totalSort") },
575
+ { value: "delivery", label: t("caimono.deliverySort") },
576
+ ]}
577
+ />
578
+ </FormField>
579
+ </ResponsiveGrid>
580
+ </Form>
573
581
  <Flex gap="md" wrap align="center">
574
582
  <Checkbox checked={free} onCheckedChange={(v) => setFree(v === true)}>
575
583
  {t("caimono.free")}
@@ -43,7 +43,7 @@ import {
43
43
  AvatarFallback,
44
44
  } from "@godxjp/ui/data-display";
45
45
  import { Alert, AlertDescription, SkeletonTable } from "@godxjp/ui/feedback";
46
- import { DatePicker, FormField, Input, Select } from "@godxjp/ui/data-entry";
46
+ import { Form, DatePicker, FormField, Input, Select } from "@godxjp/ui/data-entry";
47
47
  import { toIsoDate } from "@godxjp/ui/datetime";
48
48
  import {
49
49
  AppShell,
@@ -353,30 +353,32 @@ export default function Demo() {
353
353
  <Card className="self-start">
354
354
  <CardContent>
355
355
  <Flex direction="col" gap="md">
356
- <ResponsiveGrid columns={{ sm: 1, md: 2, lg: 4 }}>
357
- <FormField id="f-type" label="区分">
358
- <Select
359
- options={TYPE_OPTIONS}
360
- value={type}
361
- onValueChange={setType}
362
- placeholder="すべて"
363
- />
364
- </FormField>
365
- <FormField id="f-status" label="状態">
366
- <Select
367
- options={STATUS_OPTIONS}
368
- value={status}
369
- onValueChange={setStatus}
370
- placeholder="すべて"
371
- />
372
- </FormField>
373
- <FormField id="f-from" label="期間 (開始)">
374
- <DatePicker value={from} onValueChange={setFrom} placeholder="YYYY-MM-DD" />
375
- </FormField>
376
- <FormField id="f-to" label="期間 (終了)">
377
- <DatePicker value={to} onValueChange={setTo} placeholder="YYYY-MM-DD" />
378
- </FormField>
379
- </ResponsiveGrid>
356
+ <Form>
357
+ <ResponsiveGrid columns={{ sm: 1, md: 2, lg: 4 }}>
358
+ <FormField id="f-type" label="区分">
359
+ <Select
360
+ options={TYPE_OPTIONS}
361
+ value={type}
362
+ onValueChange={setType}
363
+ placeholder="すべて"
364
+ />
365
+ </FormField>
366
+ <FormField id="f-status" label="状態">
367
+ <Select
368
+ options={STATUS_OPTIONS}
369
+ value={status}
370
+ onValueChange={setStatus}
371
+ placeholder="すべて"
372
+ />
373
+ </FormField>
374
+ <FormField id="f-from" label="期間 (開始)">
375
+ <DatePicker value={from} onValueChange={setFrom} placeholder="YYYY-MM-DD" />
376
+ </FormField>
377
+ <FormField id="f-to" label="期間 (終了)">
378
+ <DatePicker value={to} onValueChange={setTo} placeholder="YYYY-MM-DD" />
379
+ </FormField>
380
+ </ResponsiveGrid>
381
+ </Form>
380
382
  {hasActiveFilters && (
381
383
  <Flex direction="row" justify="end">
382
384
  <Button variant="ghost" size="sm" onClick={clearFilters}>
@@ -29,6 +29,7 @@ import { Languages, Monitor, Moon, Sun } from "lucide-react";
29
29
 
30
30
  import { Button, Heading, Text } from "@godxjp/ui/general";
31
31
  import {
32
+ Form,
32
33
  FormField,
33
34
  Input,
34
35
  PasswordInput,
@@ -243,42 +244,44 @@ export default function LoginShowcase() {
243
244
  e.preventDefault();
244
245
  }}
245
246
  >
246
- <Flex direction="col" gap="md">
247
- <FormField id="login-email" label="メールアドレス" required>
248
- <Input
249
- type="email"
250
- name="email"
251
- autoComplete="email"
252
- inputMode="email"
253
- placeholder="name@example.com"
254
- defaultValue="m.tanaka@famgia.com"
255
- />
256
- </FormField>
247
+ <Form>
248
+ <Flex direction="col" gap="md">
249
+ <FormField id="login-email" label="メールアドレス" required>
250
+ <Input
251
+ type="email"
252
+ name="email"
253
+ autoComplete="email"
254
+ inputMode="email"
255
+ placeholder="name@example.com"
256
+ defaultValue="m.tanaka@famgia.com"
257
+ />
258
+ </FormField>
257
259
 
258
- {/* Password — labelAddon hosts the right-aligned "忘れた場合" link. */}
259
- <FormField
260
- id="login-password"
261
- label="パスワード"
262
- required
263
- labelAddon={
264
- <Button type="button" variant="link" size="sm" className="ms-auto text-xs">
265
- お忘れの場合
266
- </Button>
267
- }
268
- >
269
- <PasswordInput
270
- name="password"
271
- autoComplete="current-password"
272
- placeholder="パスワードを入力"
273
- defaultValue="example-pass"
274
- />
275
- </FormField>
260
+ {/* Password — labelAddon hosts the right-aligned "忘れた場合" link. */}
261
+ <FormField
262
+ id="login-password"
263
+ label="パスワード"
264
+ required
265
+ labelAddon={
266
+ <Button type="button" variant="link" size="sm" className="ms-auto text-xs">
267
+ お忘れの場合
268
+ </Button>
269
+ }
270
+ >
271
+ <PasswordInput
272
+ name="password"
273
+ autoComplete="current-password"
274
+ placeholder="パスワードを入力"
275
+ defaultValue="example-pass"
276
+ />
277
+ </FormField>
276
278
 
277
- {/* The single --primary action of the view. */}
278
- <Button type="submit" className="w-full justify-center">
279
- ログイン
280
- </Button>
281
- </Flex>
279
+ {/* The single --primary action of the view. */}
280
+ <Button type="submit" className="w-full justify-center">
281
+ ログイン
282
+ </Button>
283
+ </Flex>
284
+ </Form>
282
285
  </form>
283
286
 
284
287
  <Text as="p" size="2xs" tone="muted" align="center" className="leading-relaxed">
@@ -45,7 +45,7 @@ import {
45
45
  TableHeader,
46
46
  TableRow,
47
47
  } from "@godxjp/ui/data-display";
48
- import { FormField, Label, Select, Switch } from "@godxjp/ui/data-entry";
48
+ import { Form, FormField, Label, Select, Switch } from "@godxjp/ui/data-entry";
49
49
  import { Text } from "@godxjp/ui/general";
50
50
  import { Flex, PageContainer } from "@godxjp/ui/layout";
51
51
  import { cn } from "@godxjp/ui/lib/utils";
@@ -170,50 +170,52 @@ export default function Demo() {
170
170
  </Text>
171
171
  </CardHeader>
172
172
  <CardContent>
173
- <Flex direction="row" wrap align="end" gap="md">
174
- <FormField label="比較 A">
175
- <Select
176
- value={compareA}
177
- onValueChange={setCompareA}
178
- options={roleOptions}
179
- className="w-40"
180
- />
181
- </FormField>
182
- <FormField label="比較 B">
183
- <Select
184
- value={compareB}
185
- onValueChange={setCompareB}
186
- options={roleOptions}
187
- className="w-40"
188
- />
189
- </FormField>
190
- <Flex direction="row" align="center" gap="sm">
191
- <Switch
192
- id="pm-diff-only"
193
- size="sm"
194
- checked={diffOnly}
195
- onCheckedChange={setDiffOnly}
196
- disabled={sameRole}
197
- />
198
- <Label htmlFor="pm-diff-only">差分のみ</Label>
199
- </Flex>
200
- <Flex direction="row" align="center" gap="xs" aria-live="polite">
201
- {sameRole ? (
202
- <Text size="xs" tone="muted">
203
- 同一ロールを比較中
204
- </Text>
205
- ) : (
206
- <>
207
- <Badge tone={diffN > 0 ? "warning" : "success"} variant="outline">
208
- 差分 {diffN} 件
209
- </Badge>
173
+ <Form>
174
+ <Flex direction="row" wrap align="end" gap="md">
175
+ <FormField label="比較 A">
176
+ <Select
177
+ value={compareA}
178
+ onValueChange={setCompareA}
179
+ options={roleOptions}
180
+ className="w-40"
181
+ />
182
+ </FormField>
183
+ <FormField label="比較 B">
184
+ <Select
185
+ value={compareB}
186
+ onValueChange={setCompareB}
187
+ options={roleOptions}
188
+ className="w-40"
189
+ />
190
+ </FormField>
191
+ <Flex direction="row" align="center" gap="sm">
192
+ <Switch
193
+ id="pm-diff-only"
194
+ size="sm"
195
+ checked={diffOnly}
196
+ onCheckedChange={setDiffOnly}
197
+ disabled={sameRole}
198
+ />
199
+ <Label htmlFor="pm-diff-only">差分のみ</Label>
200
+ </Flex>
201
+ <Flex direction="row" align="center" gap="xs" aria-live="polite">
202
+ {sameRole ? (
210
203
  <Text size="xs" tone="muted">
211
- {roleName(compareA)} vs {roleName(compareB)}
204
+ 同一ロールを比較中
212
205
  </Text>
213
- </>
214
- )}
206
+ ) : (
207
+ <>
208
+ <Badge tone={diffN > 0 ? "warning" : "success"} variant="outline">
209
+ 差分 {diffN} 件
210
+ </Badge>
211
+ <Text size="xs" tone="muted">
212
+ {roleName(compareA)} vs {roleName(compareB)}
213
+ </Text>
214
+ </>
215
+ )}
216
+ </Flex>
215
217
  </Flex>
216
- </Flex>
218
+ </Form>
217
219
  </CardContent>
218
220
  </Card>
219
221