@godxjp/ui 28.9.0 → 28.12.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.
- package/agent/START-HERE.md +193 -0
- package/agent/anti-ai-tells.json +158 -0
- package/agent/components/Accordion.json +60 -0
- package/agent/components/AccountChip.json +59 -0
- package/agent/components/Actions.json +78 -0
- package/agent/components/Activity.json +80 -0
- package/agent/components/Affix.json +78 -0
- package/agent/components/Alert.json +65 -0
- package/agent/components/AlertDialog.json +109 -0
- package/agent/components/AlertDialogRoot.json +52 -0
- package/agent/components/Anchor.json +119 -0
- package/agent/components/AppLauncher.json +95 -0
- package/agent/components/AppProvider.json +105 -0
- package/agent/components/AppSettingPicker.json +88 -0
- package/agent/components/AppSettingToggle.json +70 -0
- package/agent/components/AppShell.json +159 -0
- package/agent/components/AreaChart.json +105 -0
- package/agent/components/AspectRatio.json +38 -0
- package/agent/components/Attachments.json +77 -0
- package/agent/components/AuthAccountSummary.json +64 -0
- package/agent/components/AuthDivider.json +35 -0
- package/agent/components/AuthFooter.json +48 -0
- package/agent/components/AuthIdentity.json +42 -0
- package/agent/components/AuthShell.json +108 -0
- package/agent/components/AuthStack.json +21 -0
- package/agent/components/Avatar.json +89 -0
- package/agent/components/Badge.json +96 -0
- package/agent/components/Banner.json +48 -0
- package/agent/components/BarChart.json +108 -0
- package/agent/components/BranchScopePicker.json +89 -0
- package/agent/components/Breadcrumb.json +54 -0
- package/agent/components/Button.json +133 -0
- package/agent/components/Calendar.json +259 -0
- package/agent/components/Callout.json +46 -0
- package/agent/components/Card.json +112 -0
- package/agent/components/CardBar.json +49 -0
- package/agent/components/CardContent.json +51 -0
- package/agent/components/Carousel.json +50 -0
- package/agent/components/Cascader.json +209 -0
- package/agent/components/CenteredShell.json +66 -0
- package/agent/components/ChatBubble.json +100 -0
- package/agent/components/ChatBubbleList.json +64 -0
- package/agent/components/ChatComposer.json +160 -0
- package/agent/components/ChatSuggestion.json +86 -0
- package/agent/components/Checkbox.json +68 -0
- package/agent/components/CheckboxGroup.json +96 -0
- package/agent/components/CodeBlock.json +64 -0
- package/agent/components/Collapsible.json +74 -0
- package/agent/components/ColorPicker.json +87 -0
- package/agent/components/Command.json +168 -0
- package/agent/components/CommandPalette.json +84 -0
- package/agent/components/CompactBarTrend.json +101 -0
- package/agent/components/Conversations.json +82 -0
- package/agent/components/CredentialReveal.json +93 -0
- package/agent/components/DataState.json +79 -0
- package/agent/components/DataTable.json +268 -0
- package/agent/components/DatePicker.json +275 -0
- package/agent/components/Descriptions.json +67 -0
- package/agent/components/Dialog.json +78 -0
- package/agent/components/DraggablePanel.json +106 -0
- package/agent/components/DropdownMenu.json +102 -0
- package/agent/components/EmptyState.json +83 -0
- package/agent/components/ErrorSurface.json +128 -0
- package/agent/components/FeatureList.json +43 -0
- package/agent/components/Field.json +64 -0
- package/agent/components/FilterBar.json +99 -0
- package/agent/components/Flex.json +153 -0
- package/agent/components/FloatButton.json +91 -0
- package/agent/components/Form.json +87 -0
- package/agent/components/FormErrors.json +51 -0
- package/agent/components/FormField.json +137 -0
- package/agent/components/FormFieldArray.json +39 -0
- package/agent/components/FormFieldControl.json +129 -0
- package/agent/components/FormRoot.json +122 -0
- package/agent/components/Heading.json +61 -0
- package/agent/components/HoverCard.json +55 -0
- package/agent/components/Icon.json +60 -0
- package/agent/components/InfiniteQueryState.json +58 -0
- package/agent/components/Input.json +122 -0
- package/agent/components/InputOTP.json +106 -0
- package/agent/components/Label.json +43 -0
- package/agent/components/LegalDocumentShell.json +102 -0
- package/agent/components/Legend.json +42 -0
- package/agent/components/LineChart.json +103 -0
- package/agent/components/Link.json +41 -0
- package/agent/components/ListRow.json +92 -0
- package/agent/components/Logo.json +85 -0
- package/agent/components/Marquee.json +91 -0
- package/agent/components/Masonry.json +82 -0
- package/agent/components/MasterDetail.json +95 -0
- package/agent/components/MegaMenu.json +120 -0
- package/agent/components/MobileShell.json +73 -0
- package/agent/components/NavList.json +63 -0
- package/agent/components/NumberInput.json +158 -0
- package/agent/components/OrgSwitcher.json +89 -0
- package/agent/components/OverlayPortalProvider.json +42 -0
- package/agent/components/PageContainer.json +181 -0
- package/agent/components/Pagination.json +132 -0
- package/agent/components/Paragraph.json +40 -0
- package/agent/components/PasswordInput.json +79 -0
- package/agent/components/PasswordStrength.json +51 -0
- package/agent/components/PermissionMatrix.json +81 -0
- package/agent/components/PieChart.json +99 -0
- package/agent/components/Popover.json +110 -0
- package/agent/components/PrefetchLink.json +65 -0
- package/agent/components/Progress.json +79 -0
- package/agent/components/Prose.json +57 -0
- package/agent/components/QrCode.json +62 -0
- package/agent/components/Radio.json +98 -0
- package/agent/components/RadioGroup.json +91 -0
- package/agent/components/RangeTimeline.json +80 -0
- package/agent/components/Rating.json +92 -0
- package/agent/components/ResizablePanel.json +69 -0
- package/agent/components/ResponsiveGrid.json +77 -0
- package/agent/components/Reveal.json +70 -0
- package/agent/components/ScrollArea.json +104 -0
- package/agent/components/SearchInput.json +98 -0
- package/agent/components/Segmented.json +96 -0
- package/agent/components/Select.json +397 -0
- package/agent/components/Separator.json +86 -0
- package/agent/components/ServiceCatalogCta.json +46 -0
- package/agent/components/ServiceLauncherCard.json +93 -0
- package/agent/components/ServiceRolePanel.json +83 -0
- package/agent/components/Sheet.json +85 -0
- package/agent/components/Sidebar.json +118 -0
- package/agent/components/Skeleton.json +57 -0
- package/agent/components/SkeletonArticle.json +71 -0
- package/agent/components/SkeletonAvatar.json +50 -0
- package/agent/components/SkeletonButton.json +57 -0
- package/agent/components/SkeletonForm.json +52 -0
- package/agent/components/SkeletonImage.json +37 -0
- package/agent/components/SkeletonInput.json +51 -0
- package/agent/components/SkeletonNode.json +42 -0
- package/agent/components/SkeletonRows.json +49 -0
- package/agent/components/SkeletonTable.json +45 -0
- package/agent/components/Slider.json +160 -0
- package/agent/components/SplitPane.json +66 -0
- package/agent/components/StatCard.json +83 -0
- package/agent/components/Steps.json +95 -0
- package/agent/components/Swatch.json +41 -0
- package/agent/components/Switch.json +81 -0
- package/agent/components/Table.json +112 -0
- package/agent/components/Tabs.json +158 -0
- package/agent/components/TagInput.json +105 -0
- package/agent/components/Text.json +201 -0
- package/agent/components/Textarea.json +126 -0
- package/agent/components/ThoughtChain.json +76 -0
- package/agent/components/Thumbnail.json +70 -0
- package/agent/components/TimePicker.json +200 -0
- package/agent/components/TimeRangePicker.json +90 -0
- package/agent/components/Timeline.json +47 -0
- package/agent/components/TimelineGrid.json +92 -0
- package/agent/components/Title.json +67 -0
- package/agent/components/Toaster.json +42 -0
- package/agent/components/Toggle.json +90 -0
- package/agent/components/ToggleGroup.json +102 -0
- package/agent/components/Toolbar.json +120 -0
- package/agent/components/Tooltip.json +110 -0
- package/agent/components/Topbar.json +83 -0
- package/agent/components/TopbarItem.json +79 -0
- package/agent/components/Transfer.json +141 -0
- package/agent/components/Tree.json +185 -0
- package/agent/components/TreeSelect.json +232 -0
- package/agent/components/TwoFactorSetup.json +79 -0
- package/agent/components/Typography.json +42 -0
- package/agent/components/Upload.json +221 -0
- package/agent/components/UploadCropDialog.json +60 -0
- package/agent/components/VisuallyHidden.json +20 -0
- package/agent/components/Welcome.json +65 -0
- package/agent/components/formatDate.json +46 -0
- package/agent/components/inertiaUpload.json +32 -0
- package/agent/components/useZodForm.json +39 -0
- package/agent/components-index.json +884 -0
- package/agent/components.json +15515 -0
- package/agent/index.json +56 -0
- package/agent/llms.txt +32 -0
- package/agent/patterns/account-recovery-settings.json +19 -0
- package/agent/patterns/async-data-state.json +20 -0
- package/agent/patterns/auth-recovery-panels.json +29 -0
- package/agent/patterns/badge-coloring.json +14 -0
- package/agent/patterns/common-fixes.json +16 -0
- package/agent/patterns/confirm-destructive.json +11 -0
- package/agent/patterns/data-table-page.json +18 -0
- package/agent/patterns/deferred-loading.json +12 -0
- package/agent/patterns/error-pages.json +28 -0
- package/agent/patterns/inertia-detail-page.json +13 -0
- package/agent/patterns/inertia-list-page.json +15 -0
- package/agent/patterns/inertia-persistent-layout.json +14 -0
- package/agent/patterns/organization-memberships.json +19 -0
- package/agent/patterns/page-sections.json +18 -0
- package/agent/patterns/settings-page-responsive.json +18 -0
- package/agent/patterns/settings-section-rows.json +23 -0
- package/agent/patterns/signup-form.json +13 -0
- package/agent/patterns/topbar-account-chip.json +18 -0
- package/agent/patterns/transactional-email.json +22 -0
- package/agent/patterns-index.json +323 -0
- package/agent/patterns.json +342 -0
- package/agent/rules.json +237 -0
- package/agent/tokens.json +8427 -0
- package/agent/vocabulary.json +198 -0
- package/dist/components/data-display/service-launcher-card.d.ts +19 -0
- package/dist/components/data-display/service-launcher-card.js +14 -1
- package/dist/components/data-entry/attachments.js +77 -33
- package/dist/components/data-entry/input.js +8 -1
- package/dist/components/layout/flex.d.ts +2 -2
- package/dist/components/layout/flex.js +2 -0
- package/dist/components/ui/avatar.d.ts +1 -18
- package/dist/components/ui/avatar.js +1 -36
- package/dist/components/ui/tag-input.d.ts +10 -0
- package/dist/components/ui/tag-input.js +35 -2
- package/dist/contracts/measurement.json +1 -1
- package/dist/i18n/messages/en.json +190 -1
- package/dist/i18n/messages/ja.json +188 -1
- package/dist/i18n/messages/vi.json +188 -1
- package/dist/lib/image-loading-status.d.ts +25 -0
- package/dist/lib/image-loading-status.js +41 -0
- package/dist/props/components/data-entry.prop.d.ts +21 -2
- package/dist/props/components/layout.prop.d.ts +42 -0
- package/dist/props/registry.d.ts +14 -1
- package/dist/props/registry.js +18 -1
- package/dist/styles/card-layout.css +10 -4
- package/dist/styles/control.css +33 -4
- package/dist/styles/data-display-layout.css +1 -1
- package/dist/styles/data-entry-layout.css +245 -2
- package/dist/styles/layout.css +17 -0
- package/dist/styles/navigation-layout.css +3 -1
- package/dist/styles/shell-layout.css +2 -0
- package/dist/styles/table-layout.css +50 -9
- package/dist/tokens/components/attachments.css +18 -9
- package/dist/tokens/components/segmented.css +7 -3
- package/dist/tokens/components/table.css +2 -1
- package/docs/COMPOSITION-VS-COMPONENT.md +41 -0
- package/docs/DESIGN-AUTHORITY.md +52 -0
- package/docs/DEVELOPMENT.md +81 -6
- package/docs/assets/service-mark-rose.svg +6 -0
- package/docs/assets/service-mark-teal.svg +5 -0
- package/docs/data-display/service-launcher-card.tsx +232 -92
- package/docs/data-entry/tag-input.tsx +37 -0
- package/docs/layout/flex.tsx +40 -0
- package/docs/roadmap/website-components.md +34 -0
- package/docs/showcase/marketing-page.tsx +54 -45
- package/docs/showcase/table-pagination.tsx +99 -18
- package/docs/showcase/theme-customization.tsx +25 -2
- package/package.json +8 -5
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
{
|
|
2
|
+
"example": "import { FormField, Input } from \"@godxjp/ui/data-entry\";\n\n<FormField id=\"coupon-name\" label=\"クーポン名\" required error={errors.name} helper=\"最大50文字\">\n <Input id=\"coupon-name\" placeholder=\"春の花粉症対策15%OFF\" value={name} onValueChange={(e) => setName(e.target.value)} />\n</FormField>",
|
|
3
|
+
"group": "data-entry",
|
|
4
|
+
"importPath": "@godxjp/ui/data-entry",
|
|
5
|
+
"name": "FormField",
|
|
6
|
+
"props": [
|
|
7
|
+
{
|
|
8
|
+
"description": "Tên trường của form — dùng khi `id` không đủ để nối control với error/aria.",
|
|
9
|
+
"name": "field",
|
|
10
|
+
"type": "string"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"description": "Nội dung phụ cạnh nhãn: gợi ý, badge bắt buộc, nút trợ giúp, action chữ ngắn. Nằm TRONG hàng nhãn nên không phá nhịp trường. Ở layout horizontal/inline hàng nhãn xuống dòng: addon không vừa cạnh nhãn thì rơi xuống dòng riêng dưới nhãn, trong cột nhãn, không tràn sang cột control.",
|
|
14
|
+
"name": "labelAddon",
|
|
15
|
+
"type": "ReactNode"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"description": "Forwarded to Label htmlFor + builds helper/error ids.",
|
|
19
|
+
"name": "id",
|
|
20
|
+
"required": true,
|
|
21
|
+
"type": "string"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"description": "Error-bag key of this field. When the surrounding Form carries `errors`, the field resolves its message from `errors[name]` automatically (an explicit `error` prop wins) and CLAIMS the key so <FormErrors /> does not repeat it. NOT injected into the child — pass `name` on the control itself for native form submission.",
|
|
25
|
+
"name": "name",
|
|
26
|
+
"type": "string"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"description": "Field label above the control.",
|
|
30
|
+
"name": "label",
|
|
31
|
+
"required": true,
|
|
32
|
+
"type": "ReactNode"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"defaultValue": "false",
|
|
36
|
+
"description": "Red asterisk + aria-required on the child.",
|
|
37
|
+
"name": "required",
|
|
38
|
+
"type": "boolean"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"description": "Muted hint shown when there is no error.",
|
|
42
|
+
"name": "helper",
|
|
43
|
+
"type": "string"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"defaultValue": "\"after\"",
|
|
47
|
+
"description": "Which side of the control the helper sits on. `before` puts it between the label and the input, for a hint the reader needs before answering (a bilingual form's second line, a unit or format note). Paint only — the helper keeps its id and stays on aria-describedby. Prefer it over stuffing a second line into `labelAddon` (a label-row slot for a chip, help button or short action) or into a ReactNode `label` (which loses the string-label aria fallbacks).",
|
|
48
|
+
"name": "helperPlacement",
|
|
49
|
+
"type": "\"before\" | \"after\""
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"description": "Destructive error message (role=alert); overrides helper.",
|
|
53
|
+
"name": "error",
|
|
54
|
+
"type": "string"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"description": "Override the parent Form's layout for this field only.",
|
|
58
|
+
"name": "layout",
|
|
59
|
+
"type": "\"vertical\" | \"horizontal\" | \"inline\""
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"description": "Override the Form's label width for this field.",
|
|
63
|
+
"name": "labelWidth",
|
|
64
|
+
"type": "number | string"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"description": "Override the Form's control width for this field.",
|
|
68
|
+
"name": "controlWidth",
|
|
69
|
+
"type": "number | string"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"description": "Span N columns when inside a `columns` Form grid.",
|
|
73
|
+
"name": "colSpan",
|
|
74
|
+
"type": "number"
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"description": "The single interactive control to render. Mutually exclusive with `staticText` — pass exactly one of the two.",
|
|
78
|
+
"name": "children",
|
|
79
|
+
"type": "ReactNode"
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"description": "Read-only VALUE instead of an interactive control — renders as plain text styled to match `Descriptions.Item`'s value typography byte-for-byte (`text-sm break-all`), skipping all of FormField's id/aria-* control wiring (there is nothing to label). Mutually exclusive with `children`. Use this to put a read-only field (name, email — anything immutable) on the SAME `<Form>` as editable fields, so it inherits the exact same layout/labelAlign/row-gap automatically instead of reaching for a separate `Descriptions` block that needs its own props reconciled to match.",
|
|
83
|
+
"name": "staticText",
|
|
84
|
+
"type": "ReactNode"
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"description": "Validation state; an actual error takes priority. Warnings do not mark values invalid.",
|
|
88
|
+
"name": "validateStatus",
|
|
89
|
+
"type": "\"success\" | \"warning\" | \"error\" | \"validating\""
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"description": "Show a localized accessible validation status beside its feedback icon.",
|
|
93
|
+
"name": "hasFeedback",
|
|
94
|
+
"type": "boolean"
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"description": "Custom feedback content; the localized status remains available.",
|
|
98
|
+
"name": "feedback",
|
|
99
|
+
"type": "React.ReactNode"
|
|
100
|
+
}
|
|
101
|
+
],
|
|
102
|
+
"related": [
|
|
103
|
+
"Label — the bare Radix label component. Use directly only when you are building a fully custom layout that cannot accept FormField's stack wrapper, and you will manage aria-describedby/aria-invalid yourself. FormField is always preferred for standard form controls.",
|
|
104
|
+
"Field — the inline row for a boolean control: label and control side by side, with `labelAddon`, `description` and `error` (gh#812 added the last two slots; `error` wires aria-invalid / aria-errormessage / aria-describedby onto the control, so a validated boolean no longer has to be hand-wired). It still renders NO hidden `<input name>` — `Switch name=\"…\"` submits itself — and it has no `required`, `helper`, error-bag `name` binding or layout/width knobs. Still: never wrap a bare `Switch` in FormField.",
|
|
105
|
+
"Field — pairs a single checkbox or radio with a label and optional description in a horizontal layout (control beside text). Use Field instead of FormField when the control and its label sit side-by-side rather than stacked.",
|
|
106
|
+
"CheckboxGroup / RadioGroup — for groups of options where FormField is not needed per-item; the group component handles its own legend/label and option layout."
|
|
107
|
+
],
|
|
108
|
+
"rules": [
|
|
109
|
+
23
|
|
110
|
+
],
|
|
111
|
+
"storyPath": "data-entry/FormField.stories.tsx",
|
|
112
|
+
"tagline": "Wraps a control with label, helper, and error; injects the accessible name (aria-labelledby), description (aria-describedby) and validation (aria-errormessage/aria-invalid/aria-required) contract onto the child, which forwards it to its real semantic focus target. Reads the parent Form's layout (vertical/horizontal) — overridable per field.",
|
|
113
|
+
"usage": [
|
|
114
|
+
"DO pass the same string to both `id` on `<FormField>` and `id` on the child control — the component wires `<Label htmlFor={id}>`, and builds `{id}-helper` / `{id}-error` ids for `aria-describedby`. If the ids diverge the label click and screen-reader announcements break.",
|
|
115
|
+
"DO pass a SINGLE React element as `children`. FormField calls `React.cloneElement` on it to inject `aria-describedby`, `aria-required`, and `aria-invalid` — if you pass a fragment or multiple nodes, cloneElement silently skips the injection and a11y attributes are lost.",
|
|
116
|
+
"COMPOSITE CHILD: when the single child is a layout wrapper — a `Flex` holding a range from/to pair or a 年/月 input+select combo — the label still reaches every control inside. FormField publishes its label through FieldNameContext and each control's semantic focus target (Input's `<input>`, Select/SearchSelect's `role=combobox` trigger, and everything composed on them) adopts it as a LAST-RESORT accessible name; a control's own `aria-label`/`aria-labelledby` always wins, so set a per-control `aria-label` (e.g. 開始日/終了日) when the two halves should announce distinct names. The wrapper itself renders as a named `role='group'` (see Flex).",
|
|
117
|
+
"DO reach for `staticText` (not `children` with a bare string/span) for a read-only field mixed into an otherwise-editable Form — e.g. an immutable name/email row above an editable role Select on the same Members-edit card. It renders with the exact typography `Descriptions.Item`'s value uses, and — because it IS a FormField reading the same Form context — it lines up with every other field's label column, `labelAlign`, and row-to-row gap automatically. A bare string as `children` instead triggers the dev-mode 'expected a single React element child' warning and has no typography contract at all.",
|
|
118
|
+
"WIDTH: a FormField FILLS its container in vertical/horizontal layout — like the conventional Form.Item (vertical → width:100%). It works full-width inside `<Form>`, a `ResponsiveGrid` cell, a bare `<Flex direction='col'>`, or a plain block; you do NOT need to wrap it in a grid to get full width. `layout='inline'` is the only content-width exception (compact, side-by-side). To narrow just the control (keeping the label row full-width), set `controlWidth` — never constrain the FormField itself.",
|
|
119
|
+
"DO use the `error` prop (not a hand-rolled `<p>`) for validation messages — it renders with `role='alert'` and `text-destructive` styling and overrides `helper` automatically. Never render an error paragraph alongside FormField.",
|
|
120
|
+
"DO use `labelAddon` (a ReactNode rendered after the label text, in the label row) for supplementary controls such as a tooltip trigger, a 'copy' icon button or a short text action ('Assign to myself'); in a horizontal/inline layout the label row wraps, so an addon that does not fit beside the label drops under it inside the label column rather than overlapping the control — never insert such controls as siblings outside FormField, which breaks layout.",
|
|
121
|
+
"DON'T wrap `Switch` in FormField — use `Field` instead. Field owns the inline row: `label`, `labelAddon`, `description`, and `error` (which wires aria-invalid / aria-errormessage / aria-describedby onto the control, gh#812). It renders no hidden input of its own — `Switch name=\"…\"` is what submits natively — and it has no `required`, `helper` or layout knobs.",
|
|
122
|
+
"DON'T use FormField for checkbox-beside-label or radio-beside-label patterns — use `Field` (single checkbox/radio with description) or `CheckboxGroup` / `RadioGroup` (multiple options), which have their own integrated labelling.",
|
|
123
|
+
"CONTRACT (which element owns each ARIA relationship): every data-entry control accepts and FORWARDS the injected props to its real semantic focus target, not a wrapper div — Input/Textarea/NumberInput → the `<input>/<textarea>`; Select/SearchSelect/Cascader/TreeSelect → the `role=combobox` trigger (with aria-expanded + aria-haspopup + aria-controls per the WAI-ARIA APG combobox pattern); DatePicker/TimePicker → the typeable `role=combobox` input (aria-haspopup=dialog); ColorPicker → the `<input type=color>` swatch; SearchInput → the `role=searchbox` input. GROUP controls own the relationship on their container: RadioGroup → `role=radiogroup` (full validation incl. aria-invalid/-errormessage/-required); CheckboxGroup, `DatePicker range` (two inputs), and Transfer → `role=group` — per ARIA 1.2 a group is not a widget, so the error id is folded into aria-describedby instead of aria-invalid/-errormessage. Upload forwards the label/description onto its native `<input type=file>`; its visible dropzone/button keeps its own action label. This forwarding is implemented once in `src/lib/field-a11y.ts` (`pickFieldA11y` / `pickGroupFieldA11y` / `resolveFieldA11y`) — do not reinvent it per control.",
|
|
124
|
+
"FIELD IDENTITY / AUTOMATION: FormField also injects a `data-field` — the field's stable MACHINE key, resolved as `field` → `name` → `id` — onto the same semantic focus target the ARIA relationships land on, and onto every option of a RadioGroup/CheckboxGroup. Use it (not a generated id, and never the visible Japanese label) as the selector in e2e tests and screen automation. It reaches NESTED controls too: when the direct child is a layout wrapper (a Flex holding a from/to pair, a 年/月 combo, a value beside a 「不明」 checkbox) cloneElement stops on that wrapper, so FormField also publishes the field through context and each control inside resolves its own key from its OWN `id` — which is what keeps `search_billing_date_from` and `..._to` distinct instead of collapsing onto one shared key. A nested control with NO id of its own deliberately gets nothing: a fabricated key is worse than a missing one, because automation binds to it and breaks silently. Two companion pieces: a `Select`'s trigger also carries `data-value` = the selected CODE (the trigger shows the option LABEL, and Radix keeps the value in an aria-hidden 1x1px native `<select>`), and each RadioGroup/CheckboxGroup option gets a deterministic `{groupId}-{optionValue}` id instead of a per-mount `React.useId()` token. Nothing here is opt-in and no DOM structure changed. A `data-field` written on the control itself always wins.",
|
|
125
|
+
"NATIVE `name` IS OPT-IN: FormField emits the same key as a real `name` attribute ONLY when the app set `<AppProvider emitFieldNames>`. It is off by default because `name` decides what a native `<form>` submit sends — turning it on globally in a shared package would make every consumer start posting new keys to its backend on an upgrade. Turn it on in apps that need native form posts or a screen-automation contract; a `name` written on the control itself always wins.",
|
|
126
|
+
"NATIVE FORM PARTICIPATION: pass `name` to a control for HTML form submission — Input/Textarea/NumberInput/Select submit natively; SearchSelect submits via a hidden input; DatePicker/TimePicker emit ISO strings (`yyyy-MM-dd` / 24h `HH:mm`); the range pickers emit `${name}_from` / `${name}_to`. `required`/`readOnly`/`disabled` map to the underlying control. Cascader/TreeSelect/Transfer submit named values via hidden inputs; Upload appends staged local files to FormData when named. Disabled controls are excluded.",
|
|
127
|
+
"ERROR TIMING & RECOVERY: pass `error` only after a field is dirty or the form is submitted (don't show errors on pristine mount). The error node renders with `role='alert'` so it is announced live the moment it appears; clearing `error` (e.g. after the user corrects the value or a server round-trip succeeds) removes aria-invalid and restores the helper. On submit, focus the first invalid control and/or render an error summary that links to each field by `id`."
|
|
128
|
+
],
|
|
129
|
+
"useCases": [
|
|
130
|
+
"Labelling a text `Input` or `Textarea` in an invoice-entry form, showing a red asterisk for required fields and surfacing server validation errors returned from a Laravel FormRequest.",
|
|
131
|
+
"Wrapping a `Select` or `DatePicker` inside a multi-field filter panel where each control needs a visible label, helper hint (e.g. 'YYYY/MM/DD'), and inline error state.",
|
|
132
|
+
"Adding a `labelAddon` tooltip button next to a 'Tax rate' label in an accounting form to explain when different rates apply, without breaking the label–control association.",
|
|
133
|
+
"Enclosing a `DatePicker range` or `TimePicker` in an admin settings page where the field needs a label, a muted hint ('Inclusive of start and end date'), and conditional error display.",
|
|
134
|
+
"Wrapping a `SearchSelect` or `Select` (with `showSearch`) control for vendor/account lookup in a journal-entry form where the `id` must be kept consistent for programmatic focus management.",
|
|
135
|
+
"Providing structured error feedback for a `Cascader` or `TreeSelect` in a multi-level category assignment screen, replacing ad-hoc error rendering with the standardised `role='alert'` pattern."
|
|
136
|
+
]
|
|
137
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"docPath": "docs/FORMS.md",
|
|
3
|
+
"example": "import { FormFieldArray, FormFieldControl } from \"@godxjp/ui/form\";\nimport { Input } from \"@godxjp/ui/data-entry\";\n<FormFieldArray name=\"contacts\">{({fields}) => fields.map(row => <FormFieldControl key={row.key} name={`${row.name}.email`} label=\"Email\">{field => <Input {...field} value={String(field.value ?? \"\")} />}</FormFieldControl>)}</FormFieldArray>;",
|
|
4
|
+
"group": "data-entry",
|
|
5
|
+
"importPath": "@godxjp/ui/form",
|
|
6
|
+
"name": "FormFieldArray",
|
|
7
|
+
"props": [
|
|
8
|
+
{
|
|
9
|
+
"description": "Array field path in the surrounding RHF FormRoot.",
|
|
10
|
+
"name": "name",
|
|
11
|
+
"type": "FieldArrayPath<T>"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"description": "Receives fields with stable key/name/index, append/prepend/insert/remove/move/swap/replace, array error and disabled state.",
|
|
15
|
+
"name": "children",
|
|
16
|
+
"type": "(collection) => React.ReactNode"
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"related": [
|
|
20
|
+
"Form",
|
|
21
|
+
"FormField",
|
|
22
|
+
"FormRoot",
|
|
23
|
+
"FormFieldControl"
|
|
24
|
+
],
|
|
25
|
+
"rules": [
|
|
26
|
+
23,
|
|
27
|
+
31
|
|
28
|
+
],
|
|
29
|
+
"storyPath": "data-entry/Form.stories.tsx",
|
|
30
|
+
"tagline": "Dynamic typed field collections with stable keys, nested validation and append/remove/reorder operations.",
|
|
31
|
+
"usage": [
|
|
32
|
+
"Use inside the documented form composition; do not nest native form elements.",
|
|
33
|
+
"Use godx-ui controls and preserve field names, errors and disabled state."
|
|
34
|
+
],
|
|
35
|
+
"useCases": [
|
|
36
|
+
"Validated settings forms",
|
|
37
|
+
"Nested repeating data entry"
|
|
38
|
+
]
|
|
39
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
{
|
|
2
|
+
"docPath": "docs/data-entry/form-root.tsx",
|
|
3
|
+
"example": "import { FormFieldControl } from \"@godxjp/ui/form\";\nimport { Input } from \"@godxjp/ui/data-entry\";\n<FormFieldControl name=\"email\" label=\"Email\" required>{field => <Input {...field} value={String(field.value ?? \"\")} />}</FormFieldControl>;",
|
|
4
|
+
"group": "data-entry",
|
|
5
|
+
"importPath": "@godxjp/ui/form",
|
|
6
|
+
"name": "FormFieldControl",
|
|
7
|
+
"props": [
|
|
8
|
+
{
|
|
9
|
+
"description": "Typed field path, including nested list rows.",
|
|
10
|
+
"name": "name",
|
|
11
|
+
"type": "FieldPath<T>"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"description": "Visible field label. Optional (antd): omitted, the field renders NO label row and reserves no space for one — the boolean-field shape, where the control carries its own inline label.",
|
|
15
|
+
"name": "label",
|
|
16
|
+
"type": "React.ReactNode"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"defaultValue": "\"value\"",
|
|
20
|
+
"description": "antd `valuePropName`. `\"checked\"` hands the render prop `checked` / `onCheckedChange` instead of `value` / `onChange` (the render-prop type narrows) and stores a boolean, so `<Checkbox {...field}>label</Checkbox>` / `<Switch {...field} />` need no wiring.",
|
|
21
|
+
"name": "valuePropName",
|
|
22
|
+
"type": "\"value\" | \"checked\""
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"description": "Dependent fields that trigger revalidation.",
|
|
26
|
+
"name": "dependencies",
|
|
27
|
+
"type": "FieldPath<T>[]"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"description": "Extract the control value.",
|
|
31
|
+
"name": "getValueFromEvent",
|
|
32
|
+
"type": "(...args: unknown[]) => unknown"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"description": "Normalize before storage.",
|
|
36
|
+
"name": "normalize",
|
|
37
|
+
"type": "(value: unknown, previous: unknown) => unknown"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"description": "Keep value after unmount, default true.",
|
|
41
|
+
"name": "preserve",
|
|
42
|
+
"type": "boolean"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"description": "Override validation error text.",
|
|
46
|
+
"name": "help",
|
|
47
|
+
"type": "React.ReactNode"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"description": "Field-level disabled state.",
|
|
51
|
+
"name": "disabled",
|
|
52
|
+
"type": "boolean"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"description": "Validation feedback state.",
|
|
56
|
+
"name": "validateStatus",
|
|
57
|
+
"type": "\"success\" | \"warning\" | \"error\" | \"validating\""
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"description": "Show accessible validation feedback.",
|
|
61
|
+
"name": "hasFeedback",
|
|
62
|
+
"type": "boolean"
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"description": "Custom feedback icon/content.",
|
|
66
|
+
"name": "feedback",
|
|
67
|
+
"type": "React.ReactNode"
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"description": "Render a godx-ui control bound to the field.",
|
|
71
|
+
"name": "children",
|
|
72
|
+
"type": "(field) => React.ReactNode"
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"description": "Optional DOM identity; defaults to a unique id even across sibling forms.",
|
|
76
|
+
"name": "id",
|
|
77
|
+
"type": "string"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"description": "Per-field layout override.",
|
|
81
|
+
"name": "layout",
|
|
82
|
+
"type": "FormLayoutProp"
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"description": "Per-field label width.",
|
|
86
|
+
"name": "labelWidth",
|
|
87
|
+
"type": "WidthProp"
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"description": "Per-field control width.",
|
|
91
|
+
"name": "controlWidth",
|
|
92
|
+
"type": "WidthProp"
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"description": "Label help or action. In a horizontal/inline layout it wraps under the label inside the label column when it does not fit beside it.",
|
|
96
|
+
"name": "labelAddon",
|
|
97
|
+
"type": "React.ReactNode"
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"description": "Grid column span.",
|
|
101
|
+
"name": "colSpan",
|
|
102
|
+
"type": "number"
|
|
103
|
+
}
|
|
104
|
+
],
|
|
105
|
+
"related": [
|
|
106
|
+
"Form",
|
|
107
|
+
"FormField",
|
|
108
|
+
"FormRoot",
|
|
109
|
+
"FormFieldControl"
|
|
110
|
+
],
|
|
111
|
+
"rules": [
|
|
112
|
+
23,
|
|
113
|
+
31
|
|
114
|
+
],
|
|
115
|
+
"storyPath": "data-entry/Form.stories.tsx",
|
|
116
|
+
"tagline": "Bind a typed field to RHF or a server adapter, with shared FormField presentation and validation.",
|
|
117
|
+
"usage": [
|
|
118
|
+
"Use inside the documented form composition; do not nest native form elements.",
|
|
119
|
+
"Use godx-ui controls and preserve field names, errors and disabled state.",
|
|
120
|
+
"RENDER-PROP BAG: `{ id, name, value, onChange, onValueChange, onBlur, ref, disabled? }`. `ref` is `RefCallback<HTMLElement>` (gh#698), so `{...field}` spreads onto Input, Textarea, Select, NumberInput and DatePicker with NO cast — never cast or drop the ref (react-hook-form focuses the first invalid field through it). `value` is `unknown`: narrow it per control (`String(field.value ?? \"\")`, `typeof field.value === \"number\" ? field.value : null`).",
|
|
121
|
+
"SERVER VALIDATION: a `FormField name` under `FormRoot errors={…}` claims its bag key, so the 422 message renders once under the field — see FormRoot's canonical server-validation composition (FormRoot + FormErrors + AlertMutationFeedback renders a 422 exactly once).",
|
|
122
|
+
"CANONICAL BOOLEAN FIELD (gh#709, antd `Form.Item valuePropName=\"checked\"`): `<FormFieldControl name=\"is_shared\" valuePropName=\"checked\">{(field) => <Checkbox {...field}>プロジェクトに共有する</Checkbox>}</FormFieldControl>` — box and label on ONE line, the label text toggles the box and names it, no label row above it, and a validation / 422 error still lands on the checkbox (`aria-invalid` + `aria-describedby`). The bag is `{ id, name, checked, onCheckedChange, onBlur, ref, disabled? }`; the stored value is always a boolean.",
|
|
123
|
+
"DON'T put a boolean field's label ABOVE its checkbox — `<FormFieldControl name=\"is_shared\" label=\"プロジェクトに共有する\">{(field) => <Checkbox checked={field.value} onCheckedChange={(c) => field.onChange(c === true)} />}</FormFieldControl>` renders the label on one row and the box far below it. Use `valuePropName=\"checked\"` with the label as the Checkbox's children."
|
|
124
|
+
],
|
|
125
|
+
"useCases": [
|
|
126
|
+
"Validated settings forms",
|
|
127
|
+
"Nested repeating data entry"
|
|
128
|
+
]
|
|
129
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
{
|
|
2
|
+
"docPath": "docs/data-entry/form-root.tsx",
|
|
3
|
+
"example": "import { FormRoot, FormFieldControl, useZodForm } from \"@godxjp/ui/form\";\nimport { Input } from \"@godxjp/ui/data-entry\";\nimport { z } from \"zod\";\nconst schema = z.object({ email: z.string().email() });\n// Inside your component:\nconst form = useZodForm(schema, { defaultValues: { email: \"\" } });\n<FormRoot form={form} onSubmit={save} layout=\"horizontal\"><FormFieldControl name=\"email\" label=\"Email\">{field => <Input {...field} value={String(field.value ?? \"\")} />}</FormFieldControl></FormRoot>;",
|
|
4
|
+
"group": "data-entry",
|
|
5
|
+
"importPath": "@godxjp/ui/form",
|
|
6
|
+
"name": "FormRoot",
|
|
7
|
+
"props": [
|
|
8
|
+
{
|
|
9
|
+
"description": "RHF form returned by useZodForm; use either form or adapter.",
|
|
10
|
+
"name": "form",
|
|
11
|
+
"type": "UseFormReturn<T>"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"description": "External store with getValue/setValue/getError/getValues/isSubmitting and optional reset.",
|
|
15
|
+
"name": "adapter",
|
|
16
|
+
"type": "FormStateAdapter"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"description": "Validated submit callback.",
|
|
20
|
+
"name": "onSubmit",
|
|
21
|
+
"type": "(values: T) => void | Promise<void>"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"description": "Validation failure callback.",
|
|
25
|
+
"name": "onSubmitFailed",
|
|
26
|
+
"type": "(errors: FieldErrors<T>) => void"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"description": "Handle rejected submission.",
|
|
30
|
+
"name": "onSubmitError",
|
|
31
|
+
"type": "(error: unknown) => void"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"description": "Banner shown when onSubmit rejects. Default: localized dataEntry.form.submitFailed text; a node replaces it; false never shows it. Skipped by default for a validation rejection (400/422) once the `errors` bag holds a message.",
|
|
35
|
+
"name": "submitFailedMessage",
|
|
36
|
+
"type": "ReactNode | false"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"description": "Called after values reset.",
|
|
40
|
+
"name": "onReset",
|
|
41
|
+
"type": "() => void"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"description": "Scroll the first invalid field into view.",
|
|
45
|
+
"name": "scrollToFirstError",
|
|
46
|
+
"type": "boolean"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"description": "Disable field mutations.",
|
|
50
|
+
"name": "disabled",
|
|
51
|
+
"type": "boolean"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"description": "Shared Form layout.",
|
|
55
|
+
"name": "layout",
|
|
56
|
+
"type": "FormLayoutProp"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"description": "Shared label width.",
|
|
60
|
+
"name": "labelWidth",
|
|
61
|
+
"type": "WidthProp"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"description": "Shared control width.",
|
|
65
|
+
"name": "controlWidth",
|
|
66
|
+
"type": "WidthProp"
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"description": "Shared label alignment.",
|
|
70
|
+
"name": "labelAlign",
|
|
71
|
+
"type": "\"start\" | \"end\""
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"description": "Responsive stacking breakpoint.",
|
|
75
|
+
"name": "collapseBelow",
|
|
76
|
+
"type": "BreakpointProp | false"
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"description": "Control density.",
|
|
80
|
+
"name": "density",
|
|
81
|
+
"type": "DensityProp"
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"description": "Server error bag.",
|
|
85
|
+
"name": "errors",
|
|
86
|
+
"type": "ErrorBagProp"
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"description": "Required/optional marking policy.",
|
|
90
|
+
"name": "requiredMark",
|
|
91
|
+
"type": "boolean | \"optional\""
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"description": "Responsive field grid rendered inside the form element.",
|
|
95
|
+
"name": "columns",
|
|
96
|
+
"type": "ResponsiveGridColumnsProp"
|
|
97
|
+
}
|
|
98
|
+
],
|
|
99
|
+
"related": [
|
|
100
|
+
"Form",
|
|
101
|
+
"FormField",
|
|
102
|
+
"FormRoot",
|
|
103
|
+
"FormFieldControl"
|
|
104
|
+
],
|
|
105
|
+
"rules": [
|
|
106
|
+
23,
|
|
107
|
+
31
|
|
108
|
+
],
|
|
109
|
+
"storyPath": "data-entry/Form.stories.tsx",
|
|
110
|
+
"tagline": "Client or server-adapted forms with shared layout, validation, submission, reset and error recovery.",
|
|
111
|
+
"usage": [
|
|
112
|
+
"Use inside the documented form composition; do not nest native form elements.",
|
|
113
|
+
"Use godx-ui controls and preserve field names, errors and disabled state.",
|
|
114
|
+
"SUBMIT-FAILED BANNER: when `onSubmit` rejects, FormRoot shows a destructive banner (`submitFailedMessage`, default the localized `dataEntry.form.submitFailed`). It is SKIPPED for a validation rejection (`classifyQueryError(error).category === \"validation\"`: 400/422) once the `errors` bag holds at least one message — the fields / `<FormErrors />` show it, as antd shows field errors instead of a form banner — and stays skipped for that failure if the app later clears the bag. A validation rejection with an empty or message-less bag, and every 5xx / network / unknown rejection, still shows it, so a failure is never silently swallowed. Errors mapped with react-hook-form `setError` instead of `errors` are not detected: pass `submitFailedMessage={false}` there. `submitFailedMessage={false}` never shows the banner; a node replaces its text.",
|
|
115
|
+
"CANONICAL SERVER-VALIDATION FORM (gh#698) — a 422 renders EXACTLY ONCE: `const m = useMutation({ mutationFn: save }); <FormRoot form={form} onSubmit={(v) => m.mutateAsync(v)} errors={serverErrors(m.error)}><FormErrors /><AlertMutationFeedback mutation={m} /><FormFieldControl name=\"code\" label=\"Code\">{(field) => <Input {...field} value={String(field.value ?? \"\")} />}</FormFieldControl></FormRoot>` (`serverErrors` = your API client's mapper from its error to the Laravel-style `{ key: string[] }` bag). Each message appears once: under its field (claimed key) or in `<FormErrors />` (unclaimed key); `AlertMutationFeedback` skips the validation error (gh#690) and FormRoot shows no `submitFailed` banner. A 5xx / network rejection still shows both the FormRoot banner and the AlertMutationFeedback alert — pass `submitFailedMessage={false}` to keep only the latter.",
|
|
116
|
+
"DON'T hand-guard the banner or wrap `mutateAsync` in try/catch just to hide a 422 — pass the bag to `errors` and the form renders it once."
|
|
117
|
+
],
|
|
118
|
+
"useCases": [
|
|
119
|
+
"Validated settings forms",
|
|
120
|
+
"Nested repeating data entry"
|
|
121
|
+
]
|
|
122
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"example": "import { Heading } from \"@godxjp/ui/general\";\n\n<Heading level={2}>請求書一覧</Heading>\n<Heading level={3} tone=\"muted\">補足セクション</Heading>\n<Heading level={1} size=\"5xl\" align=\"center\">Ship it everywhere</Heading>",
|
|
3
|
+
"group": "general",
|
|
4
|
+
"importPath": "@godxjp/ui/general",
|
|
5
|
+
"name": "Heading",
|
|
6
|
+
"props": [
|
|
7
|
+
{
|
|
8
|
+
"defaultValue": "medium",
|
|
9
|
+
"description": "Độ đậm theo canon 3 bậc (400 · 500 · 700). Đặt `bold` cho tiêu đề cần nhấn.",
|
|
10
|
+
"name": "weight",
|
|
11
|
+
"type": "\"regular\" | \"medium\" | \"bold\""
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"defaultValue": "2",
|
|
15
|
+
"description": "Heading level — renders the matching <h*> and, unless `size` overrides it, sizes from --heading-h{1..4}.",
|
|
16
|
+
"name": "level",
|
|
17
|
+
"type": "1 | 2 | 3 | 4"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"description": "Visual size, overriding the step `level` would have taken — the SAME ladder `Text size` reads. `level` still owns the document outline, so a marketing hero is `<Heading level={1} size=\"5xl\">`: a real <h1> at 54px, with no admin screen's <h1> moving. Omit it and `level` decides, exactly as before. Use this INSTEAD of a bespoke `.display` class with a raw font-size.",
|
|
21
|
+
"name": "size",
|
|
22
|
+
"type": "\"2xs\" | \"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\" | \"2xl\" | \"3xl\" | \"4xl\" | \"5xl\""
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"description": "Override the rendered element (e.g. a visual h2 that is a real <h1>).",
|
|
26
|
+
"name": "as",
|
|
27
|
+
"type": "\"h1\" | \"h2\" | \"h3\" | \"h4\" | \"div\""
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"defaultValue": "\"default\"",
|
|
31
|
+
"description": "Semantic foreground colour.",
|
|
32
|
+
"name": "tone",
|
|
33
|
+
"type": "\"default\" | \"muted\" | \"primary\" | \"success\" | \"warning\" | \"destructive\" | \"info\" | \"inherit\""
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"description": "Logical text alignment.",
|
|
37
|
+
"name": "align",
|
|
38
|
+
"type": "\"start\" | \"center\" | \"end\""
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"description": "Single-line ellipsis.",
|
|
42
|
+
"name": "truncate",
|
|
43
|
+
"type": "boolean"
|
|
44
|
+
}
|
|
45
|
+
],
|
|
46
|
+
"rules": [
|
|
47
|
+
6,
|
|
48
|
+
23
|
|
49
|
+
],
|
|
50
|
+
"storyPath": "general/typography.tsx",
|
|
51
|
+
"tagline": "Section heading sized from the --heading-h* tokens. `level` sets the size AND the semantic <h1..h4>; `size` overrides the size alone, and its top three steps are the display ramp a marketing hero needs.",
|
|
52
|
+
"usage": [
|
|
53
|
+
"DO use `<Heading level>` for section titles instead of a raw `<h2 className=\"text-lg font-semibold\">`. The level drives both the token size and the semantic element.",
|
|
54
|
+
"Inside a Card use `<CardTitle>`; use `<Heading>` for free-standing page/section headings not covered by a component slot."
|
|
55
|
+
],
|
|
56
|
+
"useCases": [
|
|
57
|
+
"A section heading on a dashboard: `<Heading level={3}>今月のKPI</Heading>`.",
|
|
58
|
+
"A visually-smaller heading that must stay an <h1> for a11y: `<Heading level={1} as=\"h1\">…</Heading>`.",
|
|
59
|
+
"A marketing hero headline: `<Heading level={1} size=\"5xl\">` — a real <h1> on the display ramp, instead of a page-local `.display` class."
|
|
60
|
+
]
|
|
61
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"example": "import { HoverCard, HoverCardTrigger, HoverCardContent } from \"@godxjp/ui/data-display\";\n\n<HoverCard>\n <HoverCardTrigger>@yamada</HoverCardTrigger>\n <HoverCardContent>山田太郎 — 経理部</HoverCardContent>\n</HoverCard>",
|
|
3
|
+
"group": "data-display",
|
|
4
|
+
"importPath": "@godxjp/ui/data-display",
|
|
5
|
+
"name": "HoverCard",
|
|
6
|
+
"props": [
|
|
7
|
+
{
|
|
8
|
+
"defaultValue": "700",
|
|
9
|
+
"description": "ms before opening on hover.",
|
|
10
|
+
"name": "openDelay",
|
|
11
|
+
"type": "number"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"defaultValue": "300",
|
|
15
|
+
"description": "ms before closing.",
|
|
16
|
+
"name": "closeDelay",
|
|
17
|
+
"type": "number"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"description": "Controlled open state.",
|
|
21
|
+
"name": "open",
|
|
22
|
+
"type": "boolean"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"description": "Open-state callback.",
|
|
26
|
+
"name": "onOpenChange",
|
|
27
|
+
"type": "(open: boolean) => void"
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"related": [
|
|
31
|
+
"Tooltip (short text label, not rich content)",
|
|
32
|
+
"Popover (click-triggered, interactive content)"
|
|
33
|
+
],
|
|
34
|
+
"rules": [
|
|
35
|
+
3,
|
|
36
|
+
6
|
|
37
|
+
],
|
|
38
|
+
"storyPath": "data-display/HoverCard.stories.tsx",
|
|
39
|
+
"subParts": [
|
|
40
|
+
"HoverCardContent",
|
|
41
|
+
"HoverCardTrigger"
|
|
42
|
+
],
|
|
43
|
+
"tagline": "Radix hover card — a rich popover shown on hover/focus of a trigger (for sighted-pointer affordances; not a replacement for Tooltip's short text).",
|
|
44
|
+
"usage": [
|
|
45
|
+
"DO compose HoverCard > HoverCardTrigger > HoverCardContent.",
|
|
46
|
+
"DO use for RICH preview content (a card, avatar + bio); for short plain-text hints use Tooltip.",
|
|
47
|
+
"DON'T rely on it for essential info — hover isn't available on touch; provide the same content on click/tap elsewhere."
|
|
48
|
+
],
|
|
49
|
+
"useCases": [
|
|
50
|
+
"User/profile preview on @mention hover",
|
|
51
|
+
"Entity preview (customer/account) on a table cell",
|
|
52
|
+
"Glossary term definitions",
|
|
53
|
+
"Commit/PR preview links"
|
|
54
|
+
]
|
|
55
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"example": "import { Lock, ShieldCheck } from \"lucide-react\";\nimport { Icon, Text } from \"@godxjp/ui/general\";\n\n// decorative — aria-hidden, on the same step as the text beside it\n<Text size=\"sm\">\n <Icon as={Lock} size=\"sm\" tone=\"muted\" /> {t(\"invoice.encrypted\")}\n</Text>\n\n// the glyph IS the value — give it a name\n<Icon as={ShieldCheck} size=\"md\" tone=\"success\" label={t(\"session.secure\")} />",
|
|
3
|
+
"group": "general",
|
|
4
|
+
"importPath": "@godxjp/ui/general",
|
|
5
|
+
"name": "Icon",
|
|
6
|
+
"props": [
|
|
7
|
+
{
|
|
8
|
+
"description": "The glyph COMPONENT — `as={Lock}`, not `<Lock />`. Every lucide-react icon qualifies. Icon renders ONTO it (the sized element is the <svg> itself), so no wrapper box enters the row and Button's own `svg` rule still sees a direct svg child.",
|
|
9
|
+
"name": "as",
|
|
10
|
+
"required": true,
|
|
11
|
+
"type": "ComponentType<SVGProps<SVGSVGElement> & RefAttributes<SVGSVGElement>>"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"defaultValue": "\"md\"",
|
|
15
|
+
"description": "Step of the nine-step icon scale (10/12/14/16/20/24/36/40/48px at a 16px root — docs/TOKENS.md). NOT the four-step control SizeProp: a glyph beside 2xs caption text and a 48px empty-state mark are the same primitive. An explicit step OUT-RANKS the context rules, so `size=\"lg\"` inside a Button renders at 20px, not the button's 16px.",
|
|
16
|
+
"name": "size",
|
|
17
|
+
"type": "\"2xs\" | \"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\" | \"2xl\" | \"3xl\" | \"4xl\""
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"description": "Semantic colour intent, the SAME vocabulary Text uses (a status glyph reads the AA-safe --text-warning, never the --warning fill role). Omitted by default, and that default is load-bearing: a glyph inherits currentColor, so an Icon inside a Button, a Badge or a toned Text paints in that surface's own ink without being told.",
|
|
21
|
+
"name": "tone",
|
|
22
|
+
"type": "\"default\" | \"muted\" | \"primary\" | \"success\" | \"warning\" | \"destructive\" | \"info\" | \"inherit\""
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"description": "Accessible NAME, for a glyph that is the only thing saying what it says (a lock in a status cell with no text beside it). Supplying it renders role=\"img\" + aria-label; leaving it off (the default) renders aria-hidden, which is correct for a glyph beside a visible label. Consumer-owned copy — route it through your t().",
|
|
26
|
+
"name": "label",
|
|
27
|
+
"type": "string"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"description": "Merged onto the glyph. NOT the place to size it — the sizing rule is (0,3,1) and deliberately out-ranks a utility, so pass `size` instead.",
|
|
31
|
+
"name": "className",
|
|
32
|
+
"type": "string"
|
|
33
|
+
}
|
|
34
|
+
],
|
|
35
|
+
"related": [
|
|
36
|
+
"Button / DropdownMenuItem / TopbarItem / ListRow — the four contexts that size a glyph for you. Inside them a bare glyph is already correct; reach for Icon there only when you want a DIFFERENT step.",
|
|
37
|
+
"Text — shares the `tone` vocabulary, and is the usual thing an Icon sits beside. Match the icon step to the text step (`size=\"sm\"` beside `Text size=\"sm\"`).",
|
|
38
|
+
"Logo — a brand mark, not a glyph: it has its own sizes and its own tone axis.",
|
|
39
|
+
"Avatar — a person or an entity, not an icon; it brings its own box and must not be squeezed into an icon measure."
|
|
40
|
+
],
|
|
41
|
+
"rules": [],
|
|
42
|
+
"storyPath": "general/Icon.stories.tsx",
|
|
43
|
+
"tagline": "A glyph on the --icon-size-* scale, and the ONLY supported way to render a standalone one. A lucide component ships width=\"24\" height=\"24\"; only four rules in this library override that (.ui-button svg, the menu row, the topbar cell, the ListRow leading slot), so everywhere else a glyph draws at 24px beside 14px text.",
|
|
44
|
+
"usage": [
|
|
45
|
+
"DO use <Icon> for ANY glyph that is not already inside Button / DropdownMenuItem / TopbarItem / a ListRow leading slot — in a Text, a table cell, an <a>, a Flex. Those four are the only contexts in the library that size a glyph for you; everywhere else a bare lucide icon renders at 24px.",
|
|
46
|
+
"DO NOT reach for `className=\"size-4\"` or `w-[16px]` — docs/CONSUMER-RULES.md §3/§8 forbid them, and they re-derive a metric the theme owns. DO NOT pass lucide's own `size={16}` either: that is a literal where the design system has a scale.",
|
|
47
|
+
"DO leave `label` off for a decorative glyph beside a visible label — Icon is aria-hidden by default, which is what WCAG 2.2 SC 1.1.1 asks for. Announcing the same thing twice is the usual defect.",
|
|
48
|
+
"DO pass a localized `label` when the glyph carries meaning nothing else on screen says (a lock alone in a status column). It becomes role=\"img\" + that name.",
|
|
49
|
+
"DO leave `tone` unset inside a Button, a Badge or a coloured surface, so the glyph inherits that surface's ink. Set it only when the glyph carries its own status meaning.",
|
|
50
|
+
"DO retune the SCALE from the theme (`--icon-size-sm`, `--icon-size-lg`, …) rather than per call site; a single instance that genuinely sits off the scale sets `--icon-glyph-size` inline (tier 2, docs/TOKENS.md).",
|
|
51
|
+
"DO NOT wrap the glyph in a <span> to size it — a wrapper adds a box to every flex row that holds an icon, and it puts an element between Button and the svg its own rule targets."
|
|
52
|
+
],
|
|
53
|
+
"useCases": [
|
|
54
|
+
"A status glyph inline with 14px body text: `<Text size=\"sm\"><Icon as={Lock} size=\"sm\" tone=\"muted\" /> 暗号化済み</Text>`.",
|
|
55
|
+
"A lock/unlock column in a table, where the glyph IS the value: `<Icon as={Lock} size=\"sm\" label={t('row.locked')} />`.",
|
|
56
|
+
"A glyph that must be bigger than the control around it: `<Button><Icon as={Download} size=\"lg\" />…</Button>`.",
|
|
57
|
+
"An icon inside a breakpoint wrapper (`<Flex hideBelow=\"sm\"><Icon as={Building2} size=\"lg\" /></Flex>`), where a direct-child rule can no longer reach it.",
|
|
58
|
+
"An external-link marker at the end of a link: `<Icon as={ArrowUpRight} size=\"sm\" />`."
|
|
59
|
+
]
|
|
60
|
+
}
|