@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
@@ -13,6 +13,7 @@ import {
13
13
  type ColumnDef,
14
14
  } from "@godxjp/ui/data-display";
15
15
  import {
16
+ Form,
16
17
  Checkbox,
17
18
  ColorPicker,
18
19
  Field,
@@ -172,26 +173,28 @@ function PreviewPane({
172
173
  </AlertDescription>
173
174
  </Alert>
174
175
 
175
- <ResponsiveGrid columns={{ base: 1, md: 2 }}>
176
- <FormField
177
- id={`${idPrefix}-org`}
178
- label={t("themeEditor.preview.field.org")}
179
- helper={t("themeEditor.preview.field.orgHelper")}
180
- >
181
- <Input id={`${idPrefix}-org`} defaultValue="Acme 株式会社" />
182
- </FormField>
183
- <FormField id={`${idPrefix}-plan`} label={t("themeEditor.preview.field.plan")}>
184
- <Select
185
- id={`${idPrefix}-plan`}
186
- defaultValue="pro"
187
- options={[
188
- { value: "starter", label: t("themeEditor.preview.plan.starter") },
189
- { value: "pro", label: t("themeEditor.preview.plan.pro") },
190
- { value: "scale", label: t("themeEditor.preview.plan.scale") },
191
- ]}
192
- />
193
- </FormField>
194
- </ResponsiveGrid>
176
+ <Form>
177
+ <ResponsiveGrid columns={{ base: 1, md: 2 }}>
178
+ <FormField
179
+ id={`${idPrefix}-org`}
180
+ label={t("themeEditor.preview.field.org")}
181
+ helper={t("themeEditor.preview.field.orgHelper")}
182
+ >
183
+ <Input id={`${idPrefix}-org`} defaultValue="Acme 株式会社" />
184
+ </FormField>
185
+ <FormField id={`${idPrefix}-plan`} label={t("themeEditor.preview.field.plan")}>
186
+ <Select
187
+ id={`${idPrefix}-plan`}
188
+ defaultValue="pro"
189
+ options={[
190
+ { value: "starter", label: t("themeEditor.preview.plan.starter") },
191
+ { value: "pro", label: t("themeEditor.preview.plan.pro") },
192
+ { value: "scale", label: t("themeEditor.preview.plan.scale") },
193
+ ]}
194
+ />
195
+ </FormField>
196
+ </ResponsiveGrid>
197
+ </Form>
195
198
 
196
199
  <Flex gap="lg" wrap>
197
200
  <Field id={`${idPrefix}-tax`} label={t("themeEditor.preview.field.tax")}>
@@ -417,53 +420,55 @@ export default function Demo() {
417
420
  <CardDescription>{t("themeEditor.seed.description")}</CardDescription>
418
421
  </CardHeader>
419
422
  <CardContent>
420
- <Flex direction="col" gap="md">
421
- <FormField
422
- id="theme-editor-seed"
423
- label={t("themeEditor.seed.label")}
424
- helper={t("themeEditor.seed.helper")}
425
- >
426
- <ColorPicker id="theme-editor-seed" value={seed} onValueChange={setSeed} />
427
- </FormField>
423
+ <Form>
424
+ <Flex direction="col" gap="md">
425
+ <FormField
426
+ id="theme-editor-seed"
427
+ label={t("themeEditor.seed.label")}
428
+ helper={t("themeEditor.seed.helper")}
429
+ >
430
+ <ColorPicker id="theme-editor-seed" value={seed} onValueChange={setSeed} />
431
+ </FormField>
428
432
 
429
- <FormField
430
- id="theme-editor-preset"
431
- label={t("themeEditor.seed.presetLabel")}
432
- helper={t("themeEditor.seed.presetHelper")}
433
- >
434
- <Select
433
+ <FormField
435
434
  id="theme-editor-preset"
436
- value={SEED_PRESETS.find((p) => p.hex === seed)?.id ?? ""}
437
- placeholder={t("themeEditor.seed.presetPlaceholder")}
438
- onValueChange={(next: string) => {
439
- const preset = SEED_PRESETS.find((p) => p.id === next);
440
- if (!preset) return;
441
- setSeed(preset.hex);
442
- setDarkL(null);
443
- }}
444
- options={SEED_PRESETS.map((preset) => ({
445
- value: preset.id,
446
- label: t(`themeEditor.seed.preset.${preset.id}`),
447
- sublabel: preset.hex,
448
- }))}
449
- />
450
- </FormField>
451
-
452
- {/* The live region. It is VISIBLE as well as announced: the same sentence a screen
435
+ label={t("themeEditor.seed.presetLabel")}
436
+ helper={t("themeEditor.seed.presetHelper")}
437
+ >
438
+ <Select
439
+ id="theme-editor-preset"
440
+ value={SEED_PRESETS.find((p) => p.hex === seed)?.id ?? ""}
441
+ placeholder={t("themeEditor.seed.presetPlaceholder")}
442
+ onValueChange={(next: string) => {
443
+ const preset = SEED_PRESETS.find((p) => p.id === next);
444
+ if (!preset) return;
445
+ setSeed(preset.hex);
446
+ setDarkL(null);
447
+ }}
448
+ options={SEED_PRESETS.map((preset) => ({
449
+ value: preset.id,
450
+ label: t(`themeEditor.seed.preset.${preset.id}`),
451
+ sublabel: preset.hex,
452
+ }))}
453
+ />
454
+ </FormField>
455
+
456
+ {/* The live region. It is VISIBLE as well as announced: the same sentence a screen
453
457
  reader hears is the one the summary line needs anyway, so there is no hidden
454
458
  copy to drift out of sync with what is on screen. */}
455
- <Text as="div" size="xs" tone="muted" role="status" aria-live="polite">
456
- {brand && brand.dark
457
- ? t("themeEditor.seed.announce", {
458
- hex: toHex(brand.light.seed),
459
- light: toHex(brand.light.seed),
460
- dark: toHex(brand.dark.seed),
461
- pass: passing,
462
- count: report.length,
463
- })
464
- : t("themeEditor.seed.announceNoDark", { hex: seed })}
465
- </Text>
466
- </Flex>
459
+ <Text as="div" size="xs" tone="muted" role="status" aria-live="polite">
460
+ {brand && brand.dark
461
+ ? t("themeEditor.seed.announce", {
462
+ hex: toHex(brand.light.seed),
463
+ light: toHex(brand.light.seed),
464
+ dark: toHex(brand.dark.seed),
465
+ pass: passing,
466
+ count: report.length,
467
+ })
468
+ : t("themeEditor.seed.announceNoDark", { hex: seed })}
469
+ </Text>
470
+ </Flex>
471
+ </Form>
467
472
  </CardContent>
468
473
  </Card>
469
474
 
@@ -503,72 +508,76 @@ export default function Demo() {
503
508
  <CardDescription>{t("themeEditor.overrides.description")}</CardDescription>
504
509
  </CardHeader>
505
510
  <CardContent>
506
- <Flex direction="col" gap="md">
507
- <Alert tone="warning">
508
- <AlertTitle>{t("themeEditor.overrides.costTitle")}</AlertTitle>
509
- <AlertDescription>{t("themeEditor.overrides.costDescription")}</AlertDescription>
510
- </Alert>
511
+ <Form>
512
+ <Flex direction="col" gap="md">
513
+ <Alert tone="warning">
514
+ <AlertTitle>{t("themeEditor.overrides.costTitle")}</AlertTitle>
515
+ <AlertDescription>
516
+ {t("themeEditor.overrides.costDescription")}
517
+ </AlertDescription>
518
+ </Alert>
511
519
 
512
- <FormField
513
- id="theme-editor-label"
514
- label={t("themeEditor.overrides.labelTitle")}
515
- helper={t("themeEditor.overrides.labelHelper")}
516
- >
517
- <Segmented
520
+ <FormField
518
521
  id="theme-editor-label"
519
- aria-label={t("themeEditor.overrides.labelTitle")}
520
- value={labelMode}
521
- onValueChange={(next) => setLabelMode(next as LabelMode)}
522
- options={[
523
- { value: "auto", label: t("themeEditor.overrides.labelAuto") },
524
- { value: "light", label: t("themeEditor.overrides.labelLight") },
525
- { value: "dark", label: t("themeEditor.overrides.labelDark") },
526
- ]}
527
- />
528
- </FormField>
529
-
530
- <FormField
531
- id="theme-editor-dark-l"
532
- label={t("themeEditor.overrides.darkTitle")}
533
- helper={
534
- searchedL === null
535
- ? t("themeEditor.overrides.darkNoParity")
536
- : t("themeEditor.overrides.darkHelper", {
537
- parity: percentFormat.format(searchedL),
538
- })
539
- }
540
- >
541
- <Slider
522
+ label={t("themeEditor.overrides.labelTitle")}
523
+ helper={t("themeEditor.overrides.labelHelper")}
524
+ >
525
+ <Segmented
526
+ id="theme-editor-label"
527
+ aria-label={t("themeEditor.overrides.labelTitle")}
528
+ value={labelMode}
529
+ onValueChange={(next) => setLabelMode(next as LabelMode)}
530
+ options={[
531
+ { value: "auto", label: t("themeEditor.overrides.labelAuto") },
532
+ { value: "light", label: t("themeEditor.overrides.labelLight") },
533
+ { value: "dark", label: t("themeEditor.overrides.labelDark") },
534
+ ]}
535
+ />
536
+ </FormField>
537
+
538
+ <FormField
542
539
  id="theme-editor-dark-l"
543
- aria-label={t("themeEditor.overrides.darkTitle")}
544
- min={0}
545
- max={100}
546
- step={0.1}
547
- value={darkL ?? searchedL ?? 0}
548
- onChange={(next: number) => setDarkL(next)}
549
- tooltip={{ formatter: (value) => percentFormat.format(value) }}
550
- marks={searchedL === null ? undefined : { [searchedL]: "" }}
551
- />
552
- </FormField>
553
-
554
- <Flex gap="sm" wrap align="center">
555
- <Button
556
- variant="outline"
557
- size="sm"
558
- disabled={!darkOverridden}
559
- onClick={() => setDarkL(null)}
540
+ label={t("themeEditor.overrides.darkTitle")}
541
+ helper={
542
+ searchedL === null
543
+ ? t("themeEditor.overrides.darkNoParity")
544
+ : t("themeEditor.overrides.darkHelper", {
545
+ parity: percentFormat.format(searchedL),
546
+ })
547
+ }
560
548
  >
561
- {t("themeEditor.overrides.darkReset")}
562
- </Button>
563
- <Badge tone={darkOverridden ? "warning" : "neutral"} variant="outline">
564
- {t(
565
- darkOverridden
566
- ? "themeEditor.overrides.darkAuthored"
567
- : "themeEditor.overrides.darkParity",
568
- )}
569
- </Badge>
549
+ <Slider
550
+ id="theme-editor-dark-l"
551
+ aria-label={t("themeEditor.overrides.darkTitle")}
552
+ min={0}
553
+ max={100}
554
+ step={0.1}
555
+ value={darkL ?? searchedL ?? 0}
556
+ onChange={(next: number) => setDarkL(next)}
557
+ tooltip={{ formatter: (value) => percentFormat.format(value) }}
558
+ marks={searchedL === null ? undefined : { [searchedL]: "" }}
559
+ />
560
+ </FormField>
561
+
562
+ <Flex gap="sm" wrap align="center">
563
+ <Button
564
+ variant="outline"
565
+ size="sm"
566
+ disabled={!darkOverridden}
567
+ onClick={() => setDarkL(null)}
568
+ >
569
+ {t("themeEditor.overrides.darkReset")}
570
+ </Button>
571
+ <Badge tone={darkOverridden ? "warning" : "neutral"} variant="outline">
572
+ {t(
573
+ darkOverridden
574
+ ? "themeEditor.overrides.darkAuthored"
575
+ : "themeEditor.overrides.darkParity",
576
+ )}
577
+ </Badge>
578
+ </Flex>
570
579
  </Flex>
571
- </Flex>
580
+ </Form>
572
581
  </CardContent>
573
582
  </Card>
574
583
 
@@ -1,4 +1,4 @@
1
- import { FormField, Input } from "@godxjp/ui/data-entry";
1
+ import { Form, FormField, Input } from "@godxjp/ui/data-entry";
2
2
  import { Button } from "@godxjp/ui/general";
3
3
  import { Avatar, AvatarFallback } from "@godxjp/ui/data-display";
4
4
  import {
@@ -65,20 +65,22 @@ export default function Demo() {
65
65
  </Flex>
66
66
  }
67
67
  >
68
- <Flex direction="col" gap="md">
69
- <FormField id="name" label="取引先名">
70
- <Input id="name" defaultValue="株式会社ベトヤ" />
71
- </FormField>
72
- <FormField id="code" label="取引先コード">
73
- <Input id="code" defaultValue="BTY-0012" />
74
- </FormField>
75
- <FormField id="contact" label="担当者">
76
- <Input id="contact" defaultValue="グエン・ヴァン・A" />
77
- </FormField>
78
- <FormField id="email" label="メール">
79
- <Input id="email" type="email" defaultValue="ke-toan@betoya.test" />
80
- </FormField>
81
- </Flex>
68
+ <Form>
69
+ <Flex direction="col" gap="md">
70
+ <FormField id="name" label="取引先名">
71
+ <Input id="name" defaultValue="株式会社ベトヤ" />
72
+ </FormField>
73
+ <FormField id="code" label="取引先コード">
74
+ <Input id="code" defaultValue="BTY-0012" />
75
+ </FormField>
76
+ <FormField id="contact" label="担当者">
77
+ <Input id="contact" defaultValue="グエン・ヴァン・A" />
78
+ </FormField>
79
+ <FormField id="email" label="メール">
80
+ <Input id="email" type="email" defaultValue="ke-toan@betoya.test" />
81
+ </FormField>
82
+ </Flex>
83
+ </Form>
82
84
  </PageContainer>
83
85
  </AppShell>
84
86
  );
@@ -2,7 +2,7 @@ import { useState } from "react";
2
2
 
3
3
  import { Alert, AlertDescription, AlertTitle } from "@godxjp/ui/feedback";
4
4
  import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@godxjp/ui/data-display";
5
- import { FormField, Input, PasswordInput, PasswordStrength } from "@godxjp/ui/data-entry";
5
+ import { Form, FormField, Input, PasswordInput, PasswordStrength } from "@godxjp/ui/data-entry";
6
6
  import { Button, Logo, Text } from "@godxjp/ui/general";
7
7
  import { AuthFooter, AuthShell, AuthStack, Flex } from "@godxjp/ui/layout";
8
8
  import { AppSettingPicker } from "@godxjp/ui/navigation";
@@ -128,34 +128,36 @@ export default function Demo() {
128
128
  </CardDescription>
129
129
  </CardHeader>
130
130
  <CardContent>
131
- <AuthStack>
132
- <FormField id="recovery-new-password" label="新しいパスワード" required>
133
- <PasswordInput
134
- autoComplete="new-password"
135
- value={password}
136
- onChange={(event) => setPassword(event.target.value)}
137
- />
138
- </FormField>
139
- <PasswordStrength value={password} />
140
- <FormField
141
- id="recovery-confirm-password"
142
- label="新しいパスワード(確認)"
143
- error="パスワードが一致しません。"
144
- required
145
- >
146
- <PasswordInput
147
- autoComplete="new-password"
148
- value={confirmation}
149
- onChange={(event) => setConfirmation(event.target.value)}
150
- />
151
- </FormField>
152
- <Button fullWidth>パスワードを更新</Button>
153
- <Flex justify="between" gap="sm" wrap>
154
- <Button variant="ghost" size="sm">
155
- サインインに戻る
156
- </Button>
157
- </Flex>
158
- </AuthStack>
131
+ <Form>
132
+ <AuthStack>
133
+ <FormField id="recovery-new-password" label="新しいパスワード" required>
134
+ <PasswordInput
135
+ autoComplete="new-password"
136
+ value={password}
137
+ onChange={(event) => setPassword(event.target.value)}
138
+ />
139
+ </FormField>
140
+ <PasswordStrength value={password} />
141
+ <FormField
142
+ id="recovery-confirm-password"
143
+ label="新しいパスワード(確認)"
144
+ error="パスワードが一致しません。"
145
+ required
146
+ >
147
+ <PasswordInput
148
+ autoComplete="new-password"
149
+ value={confirmation}
150
+ onChange={(event) => setConfirmation(event.target.value)}
151
+ />
152
+ </FormField>
153
+ <Button fullWidth>パスワードを更新</Button>
154
+ <Flex justify="between" gap="sm" wrap>
155
+ <Button variant="ghost" size="sm">
156
+ サインインに戻る
157
+ </Button>
158
+ </Flex>
159
+ </AuthStack>
160
+ </Form>
159
161
  </CardContent>
160
162
  </Card>
161
163
 
@@ -14,6 +14,7 @@ import {
14
14
  ListRow,
15
15
  } from "@godxjp/ui/data-display";
16
16
  import {
17
+ Form,
17
18
  Checkbox,
18
19
  Field,
19
20
  FormField,
@@ -137,91 +138,93 @@ export default function Demo() {
137
138
  </CardDescription>
138
139
  </CardHeader>
139
140
  <CardContent>
140
- <AuthStack>
141
- <FormField id="reg-name" label="お名前" required>
142
- <Input id="reg-name" autoComplete="name" placeholder="山田 太郎" />
143
- </FormField>
141
+ <Form>
142
+ <AuthStack>
143
+ <FormField id="reg-name" label="お名前" required>
144
+ <Input id="reg-name" autoComplete="name" placeholder="山田 太郎" />
145
+ </FormField>
144
146
 
145
- <FormField
146
- id="reg-email"
147
- label="メールアドレス"
148
- required
149
- helper="確認リンクの送信先になります。"
150
- >
151
- <Input
147
+ <FormField
152
148
  id="reg-email"
153
- type="email"
154
- autoComplete="email"
155
- placeholder="you@example.com"
156
- />
157
- </FormField>
149
+ label="メールアドレス"
150
+ required
151
+ helper="確認リンクの送信先になります。"
152
+ >
153
+ <Input
154
+ id="reg-email"
155
+ type="email"
156
+ autoComplete="email"
157
+ placeholder="you@example.com"
158
+ />
159
+ </FormField>
158
160
 
159
- <FormField id="reg-password" label="パスワード" required>
160
- <PasswordInput
161
- id="reg-password"
162
- autoComplete="new-password"
163
- value={password}
164
- onChange={(event) => setPassword(event.target.value)}
165
- />
166
- </FormField>
161
+ <FormField id="reg-password" label="パスワード" required>
162
+ <PasswordInput
163
+ id="reg-password"
164
+ autoComplete="new-password"
165
+ value={password}
166
+ onChange={(event) => setPassword(event.target.value)}
167
+ />
168
+ </FormField>
167
169
 
168
- {/* The strength meter is a real primitive — never a hand-rolled coloured bar. */}
169
- <PasswordStrength value={password} />
170
+ {/* The strength meter is a real primitive — never a hand-rolled coloured bar. */}
171
+ <PasswordStrength value={password} />
170
172
 
171
- <FormField
172
- id="reg-confirm"
173
- label="パスワード(確認)"
174
- required
175
- // The error is announced through the field's own aria-errormessage wiring, so
176
- // the mismatch is not communicated by colour alone.
177
- error={mismatch ? "パスワードが一致しません。" : undefined}
178
- >
179
- <PasswordInput
173
+ <FormField
180
174
  id="reg-confirm"
181
- autoComplete="new-password"
182
- value={confirm}
183
- onChange={(event) => setConfirm(event.target.value)}
184
- />
185
- </FormField>
175
+ label="パスワード(確認)"
176
+ required
177
+ // The error is announced through the field's own aria-errormessage wiring, so
178
+ // the mismatch is not communicated by colour alone.
179
+ error={mismatch ? "パスワードが一致しません。" : undefined}
180
+ >
181
+ <PasswordInput
182
+ id="reg-confirm"
183
+ autoComplete="new-password"
184
+ value={confirm}
185
+ onChange={(event) => setConfirm(event.target.value)}
186
+ />
187
+ </FormField>
186
188
 
187
- <Field id="reg-consent" label="利用規約とプライバシーポリシーに同意します">
188
- <Checkbox
189
- id="reg-consent"
190
- checked={consent}
191
- onCheckedChange={(next) => setConsent(next === true)}
192
- />
193
- </Field>
189
+ <Field id="reg-consent" label="利用規約とプライバシーポリシーに同意します">
190
+ <Checkbox
191
+ id="reg-consent"
192
+ checked={consent}
193
+ onCheckedChange={(next) => setConsent(next === true)}
194
+ />
195
+ </Field>
194
196
 
195
- <Button
196
- loading={submitting}
197
- disabled={!consent || mismatch}
198
- onClick={() => {
199
- setSubmitting(true);
200
- setSubmitting(false);
201
- setStage("pending-email");
202
- }}
203
- >
204
- アカウントを作成
205
- </Button>
197
+ <Button
198
+ loading={submitting}
199
+ disabled={!consent || mismatch}
200
+ onClick={() => {
201
+ setSubmitting(true);
202
+ setSubmitting(false);
203
+ setStage("pending-email");
204
+ }}
205
+ >
206
+ アカウントを作成
207
+ </Button>
206
208
 
207
- {/* ── SOCIAL / PROVIDER ACTIONS — the documented "SocialLinks" composition.
209
+ {/* ── SOCIAL / PROVIDER ACTIONS — the documented "SocialLinks" composition.
208
210
  A divider + real outline Buttons. No new component: the package must not
209
211
  invent which providers a product offers, in what order, or what consent
210
212
  they imply. `disabled` / `loading` are the Button's own props. ── */}
211
- <AuthDivider label="または" />
213
+ <AuthDivider label="または" />
212
214
 
213
- <Flex direction="col" gap="sm">
214
- {PROVIDERS.map((provider) => {
215
- const Icon = provider.icon;
216
- return (
217
- <Button key={provider.id} variant="outline" disabled={submitting}>
218
- <Icon aria-hidden="true" />
219
- {provider.label}
220
- </Button>
221
- );
222
- })}
223
- </Flex>
224
- </AuthStack>
215
+ <Flex direction="col" gap="sm">
216
+ {PROVIDERS.map((provider) => {
217
+ const Icon = provider.icon;
218
+ return (
219
+ <Button key={provider.id} variant="outline" disabled={submitting}>
220
+ <Icon aria-hidden="true" />
221
+ {provider.label}
222
+ </Button>
223
+ );
224
+ })}
225
+ </Flex>
226
+ </AuthStack>
227
+ </Form>
225
228
  </CardContent>
226
229
  </Card>
227
230
  ) : (