@godxjp/ui 28.9.0 → 28.10.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 +76 -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 +86 -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 +15507 -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 +8422 -0
- package/agent/vocabulary.json +198 -0
- 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/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 +23 -1
- package/dist/i18n/messages/ja.json +21 -1
- package/dist/i18n/messages/vi.json +21 -1
- 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 +9 -0
- package/dist/props/registry.js +6 -0
- package/dist/styles/card-layout.css +4 -4
- package/dist/styles/control.css +31 -2
- package/dist/styles/data-display-layout.css +1 -1
- 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/segmented.css +5 -1
- package/dist/tokens/components/table.css +2 -1
- package/docs/COMPOSITION-VS-COMPONENT.md +41 -0
- package/docs/DESIGN-AUTHORITY.md +14 -0
- package/docs/DEVELOPMENT.md +81 -6
- 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 +88 -18
- package/docs/showcase/theme-customization.tsx +25 -2
- package/package.json +6 -4
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"example": "import { Descriptions } from \"@godxjp/ui/data-display\";\n\n<Descriptions columns={2}>\n <Descriptions.Item label=\"会員ID\" mono>{member.id}</Descriptions.Item>\n <Descriptions.Item label=\"プラン\">{member.plan}</Descriptions.Item>\n <Descriptions.Item label=\"メモ\" span={2}>{member.note}</Descriptions.Item>\n</Descriptions>",
|
|
3
|
+
"group": "data-display",
|
|
4
|
+
"importPath": "@godxjp/ui/data-display",
|
|
5
|
+
"name": "Descriptions",
|
|
6
|
+
"props": [
|
|
7
|
+
{
|
|
8
|
+
"defaultValue": "2",
|
|
9
|
+
"description": "Column count (antd `column`, in this library's plural spelling). A plain 1 | 2 | 3 keeps the mobile-first ladder it has always painted (1 → sm:2 → lg:3). Any other number, or the responsive object, publishes --descriptions-column-count per breakpoint instead, so a 4- or 6-column reference grid — and a per-breakpoint one — is expressible without a class-name fork.",
|
|
10
|
+
"name": "columns",
|
|
11
|
+
"type": "number | { sm?: number; md?: number; lg?: number; xl?: number }"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"defaultValue": "false",
|
|
15
|
+
"description": "Draw the grid as a bordered table with a rule between every cell and a shaded label cell (antd `bordered`) — the read-only counterpart of a dense data grid. Every cell draws its own block-start/inline-start rule and the container closes the other two edges, so the frame is complete at any column count, including a responsive one. Colour, radius and cell inset come from the --descriptions-* tokens.",
|
|
16
|
+
"name": "bordered",
|
|
17
|
+
"type": "boolean"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"defaultValue": "\"vertical\"",
|
|
21
|
+
"description": "Label placement within each item — `vertical` stacks the label over the value (default); `horizontal` puts the label BESIDE the value in a token-aligned column (mirrors `<Form layout>`). Tune the horizontal label-column width via `--descriptions-label-width`.",
|
|
22
|
+
"name": "layout",
|
|
23
|
+
"type": "\"vertical\" | \"horizontal\""
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"defaultValue": "\"start\"",
|
|
27
|
+
"description": "Applies only in layout=\"horizontal\" — a vertical label sits above its value and end-aligning it there would read as a mistake, the same contract `Form` keeps.",
|
|
28
|
+
"name": "labelAlign",
|
|
29
|
+
"type": "\"start\" | \"end\""
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"description": "Descriptions.Item elements.",
|
|
33
|
+
"name": "children",
|
|
34
|
+
"type": "ReactNode"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"description": "Declarative items (antd `items`) — the alternative to composing `Descriptions.Item` children. `children` is antd's name for the value and `value` is this library's older one; either works. Both APIs can be mixed: items render first, then any children. On an item, span accepts antd's 'filled' (take the whole remaining row) and the responsive object as well as the 2 | 3 the compound form has always taken.",
|
|
38
|
+
"name": "items",
|
|
39
|
+
"type": "{ key?: React.Key; label: ReactNode; children?: ReactNode; value?: ReactNode; mono?: boolean; span?: number | 'filled' | { sm?, md?, lg?, xl? }; className?: string }[]"
|
|
40
|
+
}
|
|
41
|
+
],
|
|
42
|
+
"related": [
|
|
43
|
+
"Card / CardContent — Descriptions provides the internal grid layout; Card/CardContent provides the outer container, padding, and border. Always wrap Descriptions in CardContent (never add p-4 directly on Descriptions). Use Card when you need the visual surface; use Descriptions inside it for the label/value structure.",
|
|
44
|
+
"DataTable — use DataTable when you have multiple rows of the same entity type that need sorting, filtering, or pagination. Use Descriptions when you have a single entity's fields laid out as labelled metadata (one row per field, not one row per record).",
|
|
45
|
+
"Table — use Table (the lower-level primitive) for tabular data with explicit column headers and multiple data rows. Use Descriptions when the data is inherently label→value (no column headers needed, each field is its own row/cell).",
|
|
46
|
+
"Flex — use Flex for arbitrary vertical/horizontal layout of heterogeneous UI elements. Use Descriptions when every item follows the label-on-top / value-below pattern and you want responsive multi-column alignment for free."
|
|
47
|
+
],
|
|
48
|
+
"rules": [],
|
|
49
|
+
"storyPath": "data-display/Descriptions.stories.tsx",
|
|
50
|
+
"tagline": "Responsive definition grid for detail-page metadata. COMPOUND — value goes in Descriptions.Item children.",
|
|
51
|
+
"usage": [
|
|
52
|
+
"DO use Descriptions.Item as the ONLY direct child — never raw <div>, <dt>/<dd>, or plain text nodes. Every label/value pair must be wrapped in <Descriptions.Item label=\"…\">value</Descriptions.Item>.",
|
|
53
|
+
"DO pass span={2} or span={3} on an Item when its value is long (e.g. a full address, a memo field, a JSON blob) — span={2} applies sm:col-span-2 and span={3} applies lg:col-span-3, keeping the grid aligned across breakpoints.",
|
|
54
|
+
"DO pass mono on Item for machine-readable values: IDs, UUIDs, file paths, currency codes, JSON snippets. This sets font-mono + break-all so long strings wrap rather than overflow.",
|
|
55
|
+
"DO embed any ReactNode as the Item child — Badge, Badge, formatDate output, a Tooltip-wrapped value, or a plain string all work. The value slot is not text-only.",
|
|
56
|
+
"DON'T use Descriptions as a hand-rolled <dl>/<dt>/<dd> replacement for prose or running text — it is for structured metadata on detail/show pages only. For flowing key→value prose, use a plain <dl>.",
|
|
57
|
+
"DON'T add className padding or margin to the root Descriptions to simulate a Card — wrap it in CardContent instead. Descriptions provides only grid layout (gap-x-6 gap-y-3); outer spacing is the Card/CardContent concern."
|
|
58
|
+
],
|
|
59
|
+
"useCases": [
|
|
60
|
+
"Detail/show page header block — displaying entity metadata such as invoice number, status, due date, vendor name, and payment method in a 2- or 3-column grid before the line-item DataTable.",
|
|
61
|
+
"Account or member profile panel — showing user ID (mono), plan, registered date, email, and a status Badge in one scannable block instead of a vertical stack of FormField-looking rows.",
|
|
62
|
+
"Accounting journal entry detail — date, reference code (mono), debit account, credit account, amount, and memo (span={2}) grouped in a compact grid alongside a Timeline of audit events.",
|
|
63
|
+
"Read-only summary step in a multi-step form or wizard — displaying the values the user entered before final submission (Steps + Descriptions), without any input controls.",
|
|
64
|
+
"Sidebar or Sheet detail pane — a narrow 1-column Descriptions inside a Sheet presenting the selected row's metadata while the main DataTable stays visible.",
|
|
65
|
+
"API / webhook event inspector — showing event ID (mono, span={2}), event type, timestamp, HTTP status, and payload size in a grid, with a Badge for the status code."
|
|
66
|
+
]
|
|
67
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"example": "import { useState } from \"react\";\nimport { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogBody, DialogFooter } from \"@godxjp/ui/feedback\";\nimport { Button } from \"@godxjp/ui/general\";\n\nfunction CreateDialog() {\n const [open, setOpen] = useState(false);\n return (\n <Dialog open={open} onOpenChange={setOpen}>\n <DialogTrigger asChild><Button size=\"sm\">新規作成</Button></DialogTrigger>\n <DialogContent className=\"max-w-lg\">\n <DialogHeader>\n <DialogTitle>新規クーポン作成</DialogTitle>\n <DialogDescription>クーポン情報を入力してください。</DialogDescription>\n </DialogHeader>\n {/* The middle MUST be a DialogBody — that is the only scrolling box. Without it, content\n taller than the viewport is clipped at both ends and the footer goes with it. */}\n <DialogBody>{/* fields */}</DialogBody>\n <DialogFooter>\n <Button variant=\"outline\" onClick={() => setOpen(false)}>キャンセル</Button>\n <Button onClick={() => setOpen(false)}>保存</Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n );\n}",
|
|
3
|
+
"group": "feedback",
|
|
4
|
+
"importPath": "@godxjp/ui/feedback",
|
|
5
|
+
"name": "Dialog",
|
|
6
|
+
"props": [
|
|
7
|
+
{
|
|
8
|
+
"description": "Controlled open state.",
|
|
9
|
+
"name": "open",
|
|
10
|
+
"type": "boolean"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"description": "Initial open state when uncontrolled.",
|
|
14
|
+
"name": "defaultOpen",
|
|
15
|
+
"type": "boolean"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"description": "Open-state change handler.",
|
|
19
|
+
"name": "onOpenChange",
|
|
20
|
+
"type": "(open: boolean) => void"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"defaultValue": "\"default\"",
|
|
24
|
+
"description": "How dangerous this dialog is. ONE prop, THREE results, because they always travel together: `destructive` renders `role=\"alertdialog\"` instead of `role=\"dialog\"`, stops an outside click from dismissing, and gives `DialogAction` the destructive emphasis (antd `okType=\"danger\"`). It also defaults the corner ✕ off, because a ✕ is an accidental-dismiss affordance too. Escape still closes either way. Settable on the root (covers the tree) or on `DialogContent` (the nearer one wins). This is what replaces reaching for the separate `AlertDialog*` family — gh#567.",
|
|
25
|
+
"name": "variant",
|
|
26
|
+
"type": "\"default\" | \"destructive\""
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"defaultValue": "true",
|
|
30
|
+
"description": "`false` renders a NON-MODAL dialog (WAI-ARIA APG allows non-modal dialogs): the page behind stays interactive and in the accessibility tree (no inert/aria-hidden), no scroll lock, no scrim, an outside press does NOT close it, and there is no `aria-modal`. The dialog keeps role=dialog + its title as name, centred fixed placement, sizes and tokens; focus moves into it on open, Tab can leave it, Escape closes it while focus is inside, and focus returns to the trigger on close (only if focus was still inside). Ignored, with a dev warning, under `variant=\"destructive\"` — an alertdialog is always modal. gh#696.",
|
|
31
|
+
"name": "modal",
|
|
32
|
+
"type": "boolean"
|
|
33
|
+
}
|
|
34
|
+
],
|
|
35
|
+
"related": [
|
|
36
|
+
"AlertDialog — the flat confirm PRESET (title/description/challenge/step-up/pending, no markup of your own). Reach for the preset when it covers the case. When it does not, do NOT reach for the `AlertDialog*` compound parts: use `Dialog` with `variant=\"destructive\"`, which is the same role and the same scrim with a freeform body.",
|
|
37
|
+
"Sheet — use Sheet instead of Dialog when the content is a slide-in panel (filters, detail sidebar, settings drawer). Sheet uses `side` prop and is better suited for wide filter forms or contextual detail panels that don't demand full focus interruption.",
|
|
38
|
+
"Alert — use Alert for inline, non-modal status messages (validation errors, success banners on the page). Dialog is modal and focus-trapping; Alert is inline and never blocks interaction.",
|
|
39
|
+
"Popover — use Popover for lightweight non-modal overlays anchored to a trigger (quick-edit a single field, tooltip-style confirmation for low-stakes actions). Dialog is full-modal; Popover stays near its trigger and doesn't dim the page.",
|
|
40
|
+
"AlertMutationFeedback — use AlertMutationFeedback for toast/inline feedback after the Dialog closes, not inside it. Putting a success toast inside a Dialog that is about to unmount causes it to disappear immediately; emit the feedback after `onOpenChange(false)` resolves."
|
|
41
|
+
],
|
|
42
|
+
"rules": [
|
|
43
|
+
23,
|
|
44
|
+
3
|
|
45
|
+
],
|
|
46
|
+
"storyPath": "feedback/Dialog.stories.tsx",
|
|
47
|
+
"subParts": [
|
|
48
|
+
"DialogAction",
|
|
49
|
+
"DialogBody",
|
|
50
|
+
"DialogCancel",
|
|
51
|
+
"DialogClose",
|
|
52
|
+
"DialogContent",
|
|
53
|
+
"DialogDescription",
|
|
54
|
+
"DialogFooter",
|
|
55
|
+
"DialogHeader",
|
|
56
|
+
"DialogOverlay",
|
|
57
|
+
"DialogPortal",
|
|
58
|
+
"DialogRoot",
|
|
59
|
+
"DialogTitle",
|
|
60
|
+
"DialogTrigger"
|
|
61
|
+
],
|
|
62
|
+
"tagline": "Compound modal. Controlled via open + onOpenChange. Parts available flat (DialogTrigger/DialogContent/…) or as Dialog.Trigger/Dialog.Content. Rendered with role=dialog.",
|
|
63
|
+
"usage": [
|
|
64
|
+
"Use `Dialog` for form-style or wizard-style modal flows that need freeform content and a close action.",
|
|
65
|
+
"DO reach for `variant=\"destructive\"` for a dangerous confirmation, INCLUDING one that needs a form inside it (a required reason, a typed challenge). It gives the alertdialog role and the non-dismissable scrim without giving up the freeform body. The separate `AlertDialog*` parts remain for existing code and still work, but they are the older way in.",
|
|
66
|
+
"DO leave `variant` alone for everything that is not destructive. The prop is a danger level, not a colour knob: to tint only the header band use `DialogHeader tone`, which is a separate axis with seven values.",
|
|
67
|
+
"DO always control open state via `open` + `onOpenChange`. Dialog has no uncontrolled shortcut — omitting `open` means the trigger alone drives state, which is fine for simple trigger-only cases, but any async submission flow must use controlled state so you can hold the dialog open while `pending=true` and close it only on success.",
|
|
68
|
+
"DO include `DialogHeader` with `DialogTitle` (and optionally `DialogDescription`) inside every `DialogContent`. Radix requires an accessible title for screen readers; omitting it triggers a console warning and breaks a11y.",
|
|
69
|
+
"DO wrap tall/scrolling content in `DialogBody` (the ring-safe scroll slot, max-height ~60vh). It insets the content to match the dialog padding so a full-width control's focus ring never clips against the scroll container — mirror of SheetBody.",
|
|
70
|
+
"DO set `modal={false}` when the user must keep working on the page behind an open dialog (edit a list while a payment or detail dialog stays open). Control `open` yourself: an outside press no longer closes it, so give it a visible close action. Escape closes it only while focus is inside the dialog."
|
|
71
|
+
],
|
|
72
|
+
"useCases": [
|
|
73
|
+
"Inline form dialog — create or edit a record (invoice line, supplier, coupon) without navigating away. Place `FormField`/`Input`/`Select` inside `DialogContent`, wire the submit button to your mutation, and hold `open` while `pending` to prevent double-submit.",
|
|
74
|
+
"Read-only detail popup — show a full transaction audit trail, attachment preview, or approval history in a modal without leaving the list page. Use `Dialog` with no `DialogFooter` action buttons, just a close trigger.",
|
|
75
|
+
"Non-modal side task — `modal={false}` keeps a list or cart behind the dialog editable while the dialog stays open (take payment while the order lines can still change).",
|
|
76
|
+
"Wizard / multi-step flow — step through entity setup (legal entity → fiscal year → opening balances) using a single Dialog whose `DialogContent` conditionally renders different step panels. Control which step is shown in local state."
|
|
77
|
+
]
|
|
78
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
{
|
|
2
|
+
"docPath": "layout/draggable-panel.tsx",
|
|
3
|
+
"example": "import { useState } from \"react\";\nimport { DraggablePanel, Flex } from \"@godxjp/ui/layout\";\nimport { Text } from \"@godxjp/ui/general\";\n\nfunction Assistant() {\n const [position, setPosition] = useState({ x: 0, y: 0 });\n return (\n <DraggablePanel\n title=\"アシスタント\"\n placement=\"bottom-end\"\n position={position}\n onPositionChange={setPosition}\n >\n <Flex direction=\"col\" gap=\"sm\">\n <Text size=\"sm\">この請求書の消費税区分について質問できます。</Text>\n </Flex>\n </DraggablePanel>\n );\n}",
|
|
4
|
+
"group": "layout",
|
|
5
|
+
"importPath": "@godxjp/ui/layout",
|
|
6
|
+
"name": "DraggablePanel",
|
|
7
|
+
"props": [
|
|
8
|
+
{
|
|
9
|
+
"description": "Panel name, shown in the title bar. A string also becomes the region's accessible name.",
|
|
10
|
+
"name": "title",
|
|
11
|
+
"required": true,
|
|
12
|
+
"type": "ReactNode"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"description": "Panel body. Text stays selectable: only the title bar starts a drag.",
|
|
16
|
+
"name": "children",
|
|
17
|
+
"type": "ReactNode"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"description": "Trailing slot in the title bar, before the close control.",
|
|
21
|
+
"name": "extra",
|
|
22
|
+
"type": "ReactNode"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"defaultValue": "\"bottom-end\"",
|
|
26
|
+
"description": "Resting corner before any movement. Logical directions, so it mirrors for an RTL locale with no extra work.",
|
|
27
|
+
"name": "placement",
|
|
28
|
+
"type": "\"top-start\" | \"top-end\" | \"bottom-start\" | \"bottom-end\""
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"defaultValue": "\"md\"",
|
|
32
|
+
"description": "Panel width from the token ladder (18rem / 22rem / 28rem / xl → --centered-shell-width-md).",
|
|
33
|
+
"name": "width",
|
|
34
|
+
"type": "\"sm\" | \"md\" | \"lg\" | \"xl\""
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"defaultValue": "\"both\"",
|
|
38
|
+
"description": "react-draggable's axis, ported verbatim. `none` keeps the panel mounted but pinned where it started.",
|
|
39
|
+
"name": "axis",
|
|
40
|
+
"type": "\"both\" | \"x\" | \"y\" | \"none\""
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"defaultValue": "\"viewport\"",
|
|
44
|
+
"description": "react-draggable's bounds. `viewport` keeps the whole panel on screen so it can never be thrown off-screen and stranded. Its selector and {left, top, right, bottom} forms are deliberately absent: a selector reaches into internal DOM, and the object is spelled in physical directions that cannot mirror under RTL.",
|
|
45
|
+
"name": "bounds",
|
|
46
|
+
"type": "\"viewport\" | \"none\""
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"description": "react-draggable's controlled and uncontrolled offset from the resting corner, in CSS pixels on the physical axes. Pass `position` together with `onPositionChange` to own the value.",
|
|
50
|
+
"name": "position / defaultPosition",
|
|
51
|
+
"type": "{ x: number; y: number }"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"description": "Fires with the CLAMPED offset after every pointer frame and every keyboard nudge. The panel reports where it is and never remembers it — where that is stored, and per what scope, is your decision.",
|
|
55
|
+
"name": "onPositionChange",
|
|
56
|
+
"type": "(position: { x: number; y: number }) => void"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"description": "Presence renders the close control in the title bar (antd Modal's onCancel). Omit it for a panel the page controls entirely.",
|
|
60
|
+
"name": "onClose",
|
|
61
|
+
"type": "() => void"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"description": "Optional localized strings for the close and move controls. Each key wins over t() when set. For script-injected embeds that cannot mount AppProvider without restyling the host page (gh#606).",
|
|
65
|
+
"name": "labels",
|
|
66
|
+
"type": "{ close?: string; move?: string }"
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"defaultValue": "false",
|
|
70
|
+
"description": "react-draggable's disabled. The panel stays exactly where it is and the handle stops moving it, by pointer and by keyboard alike.",
|
|
71
|
+
"name": "disabled",
|
|
72
|
+
"type": "boolean"
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"description": "Root class. Geometry lives in the --draggable-panel-* tokens.",
|
|
76
|
+
"name": "className",
|
|
77
|
+
"type": "string"
|
|
78
|
+
}
|
|
79
|
+
],
|
|
80
|
+
"related": [
|
|
81
|
+
"ResizablePanel — resizes panes WITHIN a layout (react-resizable-panels). It changes how much room a pane gets; it cannot move a floating element around the viewport.",
|
|
82
|
+
"Sheet — a panel pinned to an edge of the screen, with a scrim and focus handling. Reach for it when the panel is a step in a flow rather than a companion to the page.",
|
|
83
|
+
"Popover — anchored to its trigger and closed on outside press. Reach for it for something short-lived beside a control, not for a surface that stays.",
|
|
84
|
+
"Dialog — modal and focus-trapping. Reach for it when the page behind must be unusable until the person answers."
|
|
85
|
+
],
|
|
86
|
+
"rules": [
|
|
87
|
+
23,
|
|
88
|
+
44
|
|
89
|
+
],
|
|
90
|
+
"storyPath": "layout/DraggablePanel.stories.tsx",
|
|
91
|
+
"tagline": "A floating surface the person using it can MOVE — drag it by the title-bar handle, or focus the handle and nudge it with the arrow keys. Bounded to the viewport, position reported through onPositionChange and never stored by the library.",
|
|
92
|
+
"usage": [
|
|
93
|
+
"DO import from `@godxjp/ui/layout`: `import { DraggablePanel } from \"@godxjp/ui/layout\";`",
|
|
94
|
+
"DO treat the position as yours. The panel reports it and stores nothing, so persisting it per user, per workspace or not at all is a decision you make.",
|
|
95
|
+
"DO keep the body text short enough to read at 22rem, or let it scroll — the body is its own scroll region and the panel is capped at 70vh.",
|
|
96
|
+
"DON'T hand-roll this with a fixed-position div and pointer maths: that is page-local CSS ui-audit blocks, and every consumer that floats anything would write it again.",
|
|
97
|
+
"DON'T reach for it when the surface should interrupt. This is a labelled region, not a dialog: it traps no focus, dims no page and announces nothing. Use Dialog when the decision must be made before anything else can happen.",
|
|
98
|
+
"DON'T drag from the whole surface by wrapping the body in your own pointer handler — dragging from anywhere makes the text unselectable in a panel whose whole purpose is text."
|
|
99
|
+
],
|
|
100
|
+
"useCases": [
|
|
101
|
+
"A floating assistant or chat box over a data-dense screen, moved aside when it covers the column being asked about.",
|
|
102
|
+
"A persistent calculator, unit converter or note pad kept on screen while the person works in the page behind it.",
|
|
103
|
+
"A live preview or inspector panel positioned wherever the reviewer wants it for the screen they are on.",
|
|
104
|
+
"A media or call tile that has to stay visible but must not sit on top of the form being filled in."
|
|
105
|
+
]
|
|
106
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
{
|
|
2
|
+
"example": "import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator } from \"@godxjp/ui/navigation\";\nimport { Button } from \"@godxjp/ui/general\";\n\n<DropdownMenu>\n <DropdownMenuTrigger asChild><Button variant=\"outline\" size=\"sm\">操作</Button></DropdownMenuTrigger>\n <DropdownMenuContent>\n <DropdownMenuItem>編集</DropdownMenuItem>\n <DropdownMenuSeparator />\n <DropdownMenuItem variant=\"destructive\">削除</DropdownMenuItem>\n </DropdownMenuContent>\n</DropdownMenu>\n\n// Right-click menu — what replaced the ContextMenu component (v23). Opens at the pointer,\n// suppresses the browser menu, and opens from Shift+F10 for keyboard users.\n<DropdownMenu trigger={[\"contextMenu\"]}>\n <DropdownMenuTrigger>行 JE-0042</DropdownMenuTrigger>\n <DropdownMenuContent>\n <DropdownMenuItem>編集</DropdownMenuItem>\n <DropdownMenuItem variant=\"destructive\">削除</DropdownMenuItem>\n </DropdownMenuContent>\n</DropdownMenu>",
|
|
3
|
+
"group": "navigation",
|
|
4
|
+
"importPath": "@godxjp/ui/navigation",
|
|
5
|
+
"name": "DropdownMenu",
|
|
6
|
+
"props": [
|
|
7
|
+
{
|
|
8
|
+
"description": "On DropdownMenuContent: match the trigger, fit content, or use a token width. Omit to keep the default minimum.",
|
|
9
|
+
"name": "width",
|
|
10
|
+
"type": "\"trigger\" | \"auto\" | \"sm\" | \"md\" | \"lg\""
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"description": "Controlled open state (Ant Design `open`).",
|
|
14
|
+
"name": "open",
|
|
15
|
+
"type": "boolean"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"defaultValue": "[\"click\"]",
|
|
19
|
+
"description": "On the ROOT. Ant Design `trigger` — the gestures that open the menu, and more than one may be live at once. `contextMenu` opens AT the pointer and suppresses the browser's own menu; it is the replacement for the ContextMenu component deleted in v23, and antd expresses right-click menus the same way. The default is `[\"click\"]`, not antd's `[\"hover\"]`. The keyboard opener survives every mode: Enter/Space/ArrowDown for click and hover, Shift+F10 or the ContextMenu key for contextMenu — so a gesture list can never produce a pointer-only menu (WCAG 2.1.1).",
|
|
20
|
+
"name": "trigger",
|
|
21
|
+
"type": "(\"click\" | \"hover\" | \"contextMenu\")[]"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"description": "On the ROOT. Ant Design `disabled` — no gesture opens the menu (antd implements it by emptying the trigger list). The trigger's own `disabled` still works for the button case.",
|
|
25
|
+
"name": "disabled",
|
|
26
|
+
"type": "boolean"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"defaultValue": "0.15",
|
|
30
|
+
"description": "On the ROOT, in SECONDS (antd's unit). Hover dwell before a `trigger={[\"hover\"]}` menu opens.",
|
|
31
|
+
"name": "mouseEnterDelay",
|
|
32
|
+
"type": "number"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"defaultValue": "0.1",
|
|
36
|
+
"description": "On the ROOT, in SECONDS. Grace period after the pointer leaves before a hover menu closes — it is what lets the reader cross the gap from the trigger onto the menu, so do not set it to 0.",
|
|
37
|
+
"name": "mouseLeaveDelay",
|
|
38
|
+
"type": "number"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"description": "Open-state change handler (Ant Design `onOpenChange`).",
|
|
42
|
+
"name": "onOpenChange",
|
|
43
|
+
"type": "(open: boolean) => void"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"description": "On DropdownMenuContent. Ant Design `placement`, spelled on the LOGICAL inline axis (antd's `bottomLeft` is `bottomStart` here), so an RTL app anchors on the correct edge with no second value. It is sugar over Radix's `side` + `align`, and an explicitly passed `side`/`align` still wins. antd's inline-side placements (`left*`/`right*`) are deliberately absent — Radix's `side` is physical and this library ships no DirectionProvider, so they would open on the wrong edge in RTL; pass Radix's own `side` if you knowingly want a physical one.",
|
|
47
|
+
"name": "placement",
|
|
48
|
+
"type": "\"top\" | \"topStart\" | \"topEnd\" | \"bottom\" | \"bottomStart\" | \"bottomEnd\""
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"description": "On DropdownMenuContent. Ant Design `arrow` — paints the pointer at the anchored edge (default off). Sized from --dropdown-arrow-{width,height,background} rather than Radix's built-in 10×5.",
|
|
52
|
+
"name": "arrow",
|
|
53
|
+
"type": "boolean"
|
|
54
|
+
}
|
|
55
|
+
],
|
|
56
|
+
"related": [
|
|
57
|
+
"Popover — use Popover when the floating panel needs arbitrary layout (filter forms, date pickers, rich content grids). Use DropdownMenu only for a list of discrete clickable actions or toggle items; DropdownMenu has no layout flexibility beyond label/separator/group.",
|
|
58
|
+
"Command — use Command (cmdk) when the list is large, needs fuzzy-search filtering, or acts as a keyboard-driven command palette. DropdownMenu has no built-in search input; once the list exceeds ~8 items or needs filtering, switch to Command (often inside a Popover).",
|
|
59
|
+
"Select — use Select when the purpose is choosing a value to submit in a form field (has a name prop for native form submission, renders a hidden select for a11y). Use DropdownMenu when the purpose is triggering actions, not picking a form value.",
|
|
60
|
+
"Sidebar — use Sidebar for persistent left-rail navigation. DropdownMenu is transient (opens on click, dismisses on select); Sidebar is always-visible structural navigation."
|
|
61
|
+
],
|
|
62
|
+
"rules": [],
|
|
63
|
+
"storyPath": "navigation/DropdownMenu.stories.tsx",
|
|
64
|
+
"subParts": [
|
|
65
|
+
"DropdownMenuCheckboxItem",
|
|
66
|
+
"DropdownMenuContent",
|
|
67
|
+
"DropdownMenuGroup",
|
|
68
|
+
"DropdownMenuItem",
|
|
69
|
+
"DropdownMenuLabel",
|
|
70
|
+
"DropdownMenuPortal",
|
|
71
|
+
"DropdownMenuRadioGroup",
|
|
72
|
+
"DropdownMenuRadioItem",
|
|
73
|
+
"DropdownMenuSeparator",
|
|
74
|
+
"DropdownMenuShortcut",
|
|
75
|
+
"DropdownMenuSub",
|
|
76
|
+
"DropdownMenuSubContent",
|
|
77
|
+
"DropdownMenuSubTrigger",
|
|
78
|
+
"DropdownMenuTrigger"
|
|
79
|
+
],
|
|
80
|
+
"tagline": "Dropdown menu (react-aria). Compose DropdownMenu/DropdownMenuTrigger/DropdownMenuContent/DropdownMenuItem/DropdownMenuSeparator. `trigger` picks the gestures — click (default), hover, or contextMenu, which is what replaced the deleted ContextMenu component.",
|
|
81
|
+
"usage": [
|
|
82
|
+
"DO compose the full sub-part tree: DropdownMenu (root) → DropdownMenuTrigger (with asChild to delegate to your Button/icon) → DropdownMenuContent → DropdownMenuItem / DropdownMenuSeparator / DropdownMenuLabel / DropdownMenuGroup. Omitting any level (e.g. rendering DropdownMenuContent without DropdownMenu as ancestor) breaks Radix context and the menu will not open.",
|
|
83
|
+
"DO use DropdownMenuTrigger with asChild and pass a godx-ui Button or icon Button as the child — never render a raw <button> or <div> as the trigger, and never omit asChild when the child is already a button-like element (double-button nesting breaks a11y).",
|
|
84
|
+
"DO use variant='destructive' on DropdownMenuItem for irreversible actions (delete, revoke, void) — this applies the semantic destructive colour token automatically without any className override.",
|
|
85
|
+
"DO use DropdownMenuSub + DropdownMenuSubTrigger + DropdownMenuSubContent for nested sub-menus (e.g. 'Export' → 'CSV', 'PDF'). The ChevronRight icon is rendered automatically by DropdownMenuSubTrigger — do not add your own.",
|
|
86
|
+
"DO use DropdownMenuCheckboxItem (with checked + onCheckedChange) or DropdownMenuRadioGroup + DropdownMenuRadioItem for toggle/selection menus such as column visibility or active view. These items manage their own checked indicator — do not layer a Checkbox or RadioGroup inside a plain DropdownMenuItem.",
|
|
87
|
+
"DON'T put anything but collection nodes directly inside DropdownMenuContent / DropdownMenuGroup / DropdownMenuRadioGroup — only DropdownMenuItem, DropdownMenuCheckboxItem, DropdownMenuRadioItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuGroup, DropdownMenuSub, or a component that returns one. A leading icon or a bare text label goes INSIDE an item. react-aria builds the menu through a stand-in document that cannot create SVG or text nodes, so an icon placed beside the items throws during render (`createElementNS is not a function`); the library contains that throw so the page survives, but the menu comes up EMPTY and the console says why (gh#637).",
|
|
88
|
+
"DON'T use DropdownMenu for form submission — items fire onSelect callbacks, not form field values. There is no name prop for native form submission. If a menu selection must feed a form field, lift state into a controlled value and wire a hidden Input or use Select instead.",
|
|
89
|
+
"DO use `trigger={[\"contextMenu\"]}` for a right-click menu on a row, card or tile — there is no ContextMenu component any more (deleted in v23). Give that target something focusable and an interactive role: the menu must be reachable with Shift+F10, and react-aria warns in dev when an `asChild` trigger has neither.",
|
|
90
|
+
"DON'T put actions ONLY behind `contextMenu`. Right-click is undiscoverable for new users and absent on touch (it falls back to long-press), so mirror anything critical in a visible click trigger.",
|
|
91
|
+
"DO keep `mouseLeaveDelay` non-zero on `trigger={[\"hover\"]}`: closing the instant the pointer leaves the trigger makes the menu impossible to reach, because the pointer has to cross the gap to get there."
|
|
92
|
+
],
|
|
93
|
+
"useCases": [
|
|
94
|
+
"Row action menu in a DataTable: a '...' icon Button opens a DropdownMenu with Edit, Duplicate, DropdownMenuSeparator, then Delete (variant='destructive') — keeps the row compact and avoids inline button clutter.",
|
|
95
|
+
"Topbar / avatar chip: a user-avatar Button triggers a DropdownMenu with Profile, Settings, DropdownMenuSeparator, Sign out — standard app-shell pattern for account actions.",
|
|
96
|
+
"Bulk-action toolbar: after selecting rows, an 'Actions' Button opens a DropdownMenu with Approve, Reject, Export — prevents the toolbar from overflowing with individual buttons.",
|
|
97
|
+
"Column visibility toggle in a report table: a 'Columns' Button opens a DropdownMenu whose items are DropdownMenuCheckboxItem entries, letting users show/hide columns without a Dialog.",
|
|
98
|
+
"Quick status change on an accounting entry: a Badge-like trigger opens a DropdownMenu with DropdownMenuRadioGroup items (Draft, Posted, Voided) so the user can transition status without navigating away.",
|
|
99
|
+
"Right-click actions on a DataTable row or a file tile: `trigger={[\"click\", \"contextMenu\"]}` puts the same menu behind the row's kebab AND a right-click anywhere on the row, so the accelerator and the discoverable affordance stay one menu.",
|
|
100
|
+
"Hover-opened menu on a toolbar entry (`trigger={[\"hover\"]}`) where the pointer is already travelling — the keyboard still opens it with Enter."
|
|
101
|
+
]
|
|
102
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
{
|
|
2
|
+
"example": "import { EmptyState } from \"@godxjp/ui/data-display\";\n\n<EmptyState title=\"該当データがありません\" description=\"検索条件を変更してください。\" />",
|
|
3
|
+
"group": "data-display",
|
|
4
|
+
"importPath": "@godxjp/ui/data-display",
|
|
5
|
+
"name": "EmptyState",
|
|
6
|
+
"props": [
|
|
7
|
+
{
|
|
8
|
+
"description": "Primary empty message.",
|
|
9
|
+
"name": "title",
|
|
10
|
+
"required": true,
|
|
11
|
+
"type": "string"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"description": "Secondary helper text.",
|
|
15
|
+
"name": "description",
|
|
16
|
+
"type": "string"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"description": "Icon above the title.",
|
|
20
|
+
"name": "icon",
|
|
21
|
+
"type": "LucideIcon"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"description": "CTA element (e.g. a Button).",
|
|
25
|
+
"name": "action",
|
|
26
|
+
"type": "ReactNode"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"defaultValue": "\"page\"",
|
|
30
|
+
"description": "Contextual visual weight. Compact omits the icon medallion.",
|
|
31
|
+
"name": "variant",
|
|
32
|
+
"type": "\"page\" | \"section\" | \"compact\""
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"defaultValue": "\"muted\"",
|
|
36
|
+
"description": "Medallion colour intent (a subset of the shared tone vocabulary; `destructive` is the DS name for a danger state). Recolours BOTH the icon GLYPH and the medallion fill from the matching role token — set `success` for a confirmation zero-state (e.g. device approved) instead of hand-rolling a `.ui-success-state` class. The glyph inherits `--empty-state-icon-foreground`; never pass a `text-*` colour utility on your icon, it would out-specify the token and pin every tone to muted.",
|
|
37
|
+
"name": "tone",
|
|
38
|
+
"type": "\"muted\" | \"success\" | \"warning\" | \"destructive\" | \"info\""
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"defaultValue": "3",
|
|
42
|
+
"description": "Semantic heading level of the title. A page/onboarding empty state directly under the page h1 uses titleLevel={2}; one nested in an already-h2 section keeps the default 3.",
|
|
43
|
+
"name": "titleLevel",
|
|
44
|
+
"type": "1 | 2 | 3 | 4"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"description": "Render the title as a non-heading element (p/div) instead of a heading — for a compact/section empty state inside a section that already owns its heading, so the message is not announced as a heading and cannot skip an outline level. Overrides titleLevel.",
|
|
48
|
+
"name": "titleAs",
|
|
49
|
+
"type": "\"h1\" | \"h2\" | \"h3\" | \"h4\" | \"p\" | \"div\""
|
|
50
|
+
}
|
|
51
|
+
],
|
|
52
|
+
"related": [
|
|
53
|
+
"DataTable — already embeds an EmptyState automatically when `data` is empty; customise via the `empty=` prop. Do NOT wrap DataTable in a `data.length === 0` guard that renders EmptyState separately.",
|
|
54
|
+
"DataState — TanStack Query lifecycle widget (`@godxjp/ui/query`). Pass `<EmptyState />` to its `empty=` prop for zero-items; DataState itself covers loading/error — do not use EmptyState for those states.",
|
|
55
|
+
"InfiniteQueryState — same pattern as DataState but for `useInfiniteQuery`; pass EmptyState to `empty=` when the flattened list is empty.",
|
|
56
|
+
"SkeletonTable — use for the loading skeleton before data arrives (pass to DataState's `skeleton=` or DataTable's `loading=`). EmptyState is for after data arrives and is empty, not while loading."
|
|
57
|
+
],
|
|
58
|
+
"rules": [],
|
|
59
|
+
"storyPath": "data-display/EmptyState.stories.tsx",
|
|
60
|
+
"tagline": "Centred empty placeholder with icon, title, description, and optional CTA.",
|
|
61
|
+
"usage": [
|
|
62
|
+
"DO always pass `title` — it is the only required prop and renders a heading (`<h3>` by default); omitting it causes a blank silent render with no visible error.",
|
|
63
|
+
"DO set `titleLevel` to match the page outline (page h1 → section h2 → nested h3) so the empty state does not trigger a heading-order violation. Choose the level for OUTLINE position, never for visual size — the size never changes with the level. When the empty state sits in a section that already has its own heading, use `titleAs=\"p\"` so the message is not a heading at all.",
|
|
64
|
+
"DO use `tone=\"success\"` (or warning/destructive/info) for a semantic confirmation/alert zero-state — it recolours the icon medallion from the role token; do NOT hand-roll a `.ui-success-state` class that scopes `--empty-state-icon-*`.",
|
|
65
|
+
"DO use the `icon` prop (a Lucide icon component, not a JSX element) to give visual context — e.g. `icon={InboxIcon}` for empty inboxes, `icon={SearchIcon}` after a failed search. Pass the component reference, not `<InboxIcon />`.",
|
|
66
|
+
"DO use `action` (a `ReactNode`, typically a `<Button>`) for actionable zero-states — e.g. 'Create first invoice' — so users have a clear next step instead of a dead end.",
|
|
67
|
+
"DO NOT hand-roll a `data.length === 0 ? <EmptyState /> : <DataTable />` conditional — `DataTable` already embeds an `EmptyState` in its body when `data` is empty. Use the `empty=` prop on `DataTable` to customise it, not a wrapper conditional.",
|
|
68
|
+
"DO NOT use EmptyState inside a `DataState` or `InfiniteQueryState` for the loading or error states — those widgets handle skeleton/error themselves; pass `EmptyState` only to their `empty=` prop for the zero-items case.",
|
|
69
|
+
"DO NOT add padding directly on `EmptyState` via `className` when placing it inside a `Card` — wrap it in `<CardContent>` first; EmptyState is a self-contained block with its own internal spacing via `ui-empty-state` styles.",
|
|
70
|
+
"DO omit optional secondary sections when absence has no user value. Otherwise use variant='compact' or 'section'; reserve page for the primary page job.",
|
|
71
|
+
"DO match empty-state visual weight to the section's importance and expected content density — a low-priority 'no received invitations' block uses variant='compact' (no medallion, minimal padding), not the full page treatment that would outweigh real content.",
|
|
72
|
+
"DO NOT wrap every empty condition in its own bordered Card. A compact/section empty state sits directly in the existing CardContent / section it belongs to; a dedicated bordered Card is only for a page-level or standalone zero-state.",
|
|
73
|
+
"DO pair `variant='page'` (the default) with `<PageContainer fill>` when the zero-state IS the whole page body — `fill` hands the body the shell's remaining height and the page zero-state then takes that height and centres in it. That is ONE fact (this page's body is its empty state), not a second prop to keep in lockstep: no `fill` on the empty state, no `h-full`/`min-h-screen`/`grid place-items-center` wrapper, no spacer div. Measured on a consumer dashboard at 1440x805: without it the block top-packed at y=240 with 348px of white below; with `fill` alone the body grew to 541px and the void merely moved; with both the block resolves to 525px and centres. Under an ordinary auto-height body nothing changes, so leave `fill` off on a page that has real content beneath the header."
|
|
74
|
+
],
|
|
75
|
+
"useCases": [
|
|
76
|
+
"Zero-row admin list pages (invoices, accounts, transactions) that are NOT backed by a `DataTable` — e.g. a card-grid or custom list layout where DataTable's built-in empty state doesn't apply.",
|
|
77
|
+
"Post-filter / post-search zero results — show `icon={SearchIcon}` + a `description` explaining what was searched and an `action` to clear filters.",
|
|
78
|
+
"First-run onboarding screens where no data has been created yet — e.g. 'No entities added yet' with an action button to create the first legal entity.",
|
|
79
|
+
"Passed as the `empty=` prop inside `DataState` or `InfiniteQueryState` to satisfy the TanStack Query lifecycle widget's zero-items slot without hand-rolling markup.",
|
|
80
|
+
"Standalone section within a `CardContent` to indicate a sub-section (e.g. attachments, comments, related records) has no entries yet, separate from the page-level list.",
|
|
81
|
+
"Error-adjacent zero states where the page loaded successfully but the filtered result set is empty — distinct from an error state handled by `DataState`/`AlertMutationFeedback`."
|
|
82
|
+
]
|
|
83
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
{
|
|
2
|
+
"example": "import { Button, Logo, Text } from \"@godxjp/ui/general\";\nimport { AppShell, ErrorSurface, PageContainer, Sidebar } from \"@godxjp/ui/layout\";\n\n// 403 — APPLICATION mode: the body inside the shell the route ALREADY renders.\n// The sidebar/topbar/breadcrumb are preserved; the surface builds no chrome.\nexport function ForbiddenPage() {\n return (\n <AppShell sidebar={<Sidebar activeId=\"reports\" sections={sections} />}>\n <PageContainer title=\"レポート\" breadcrumb={[{ label: \"ホーム\", to: \"/\" }, { label: \"レポート\" }]}>\n <ErrorSurface\n mode=\"application\"\n status={403}\n title={t(\"errors.403.title\")}\n description={t(\"errors.403.description\")}\n permission=\"reports.view\"\n organization=\"株式会社ゴッドエックス\"\n action={<Button onClick={goHome}>{t(\"errors.backHome\")}</Button>}\n />\n </PageContainer>\n </AppShell>\n );\n}\n\n// 503 — SYSTEM mode: the surface owns the page. No className, no min-h-dvh, no media query.\nexport function MaintenancePage() {\n return (\n <ErrorSurface\n mode=\"system\"\n status={503}\n brand={<Logo glyph=\"G\" />}\n title={t(\"errors.503.title\")}\n description={t(\"errors.503.description\")}\n maintenance={{\n start: \"2026-08-02T18:00:00Z\", // ISO-8601 instant, server-sent\n end: \"2026-08-02T20:00:00Z\",\n timeZone: \"Asia/Tokyo\", // IANA — explicit, so SSR and client agree\n progress: 40, // 0-100, server-sent (never from the client clock)\n }}\n footer={<Text size=\"xs\" tone=\"muted\">2026 GodX</Text>}\n action={<Button onClick={reload}>{t(\"errors.reload\")}</Button>}\n />\n );\n}",
|
|
3
|
+
"group": "layout",
|
|
4
|
+
"importPath": "@godxjp/ui/layout",
|
|
5
|
+
"name": "ErrorSurface",
|
|
6
|
+
"props": [
|
|
7
|
+
{
|
|
8
|
+
"description": "The shell contract, not a skin. \"application\" (400/403/404) renders ONLY the surface block — put it in AppShell's children (usually inside a PageContainer) so the sidebar/topbar/breadcrumb stay mounted; it cannot build chrome, because nav data and the user menu are consumer-owned. \"system\" (500/503) renders the whole page: CenteredShell align=\"center\", so no consumer min-h-dvh / flex class / media query.",
|
|
9
|
+
"name": "mode",
|
|
10
|
+
"required": true,
|
|
11
|
+
"type": "\"application\" | \"system\""
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"description": "The HTTP status (a NUMBER, not a string). It is the input that drives the default icon (TriangleAlert 400 · ShieldAlert 403 · SearchX 404 · ServerCrash 500 · Wrench 503) and tone (warning 400/403/503 · muted 404 · destructive 500). Also rendered as the compact tabular status code, announced as 'HTTP status 403' rather than the cardinal number. 400 is the malformed-request page — a route reached with parameters the server refuses to interpret (a bad id, a missing launch parameter) — and belongs in mode=\"application\" like 403/404.",
|
|
15
|
+
"name": "status",
|
|
16
|
+
"required": true,
|
|
17
|
+
"type": "400 | 403 | 404 | 500 | 503"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"description": "Headline. Consumer-owned copy from the APP's own t() — @godxjp/ui ships no product text.",
|
|
21
|
+
"name": "title",
|
|
22
|
+
"required": true,
|
|
23
|
+
"type": "ReactNode"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"description": "The ONE recovery action (a Button, or Button asChild wrapping a router Link). A single slot IS the enforcement: pass a fragment with two buttons and only the first renders, with a development-time console error. Support contact goes in `description`, never in a second CTA.",
|
|
27
|
+
"name": "action",
|
|
28
|
+
"required": true,
|
|
29
|
+
"type": "ReactNode"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"description": "Supporting sentence under the title. Put support-contact guidance here. Its measure is owned by --empty-state-description-max-width.",
|
|
33
|
+
"name": "description",
|
|
34
|
+
"type": "ReactNode"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"description": "Support correlation id, rendered as a <dt>/<dd> metadata row with a localized 'Request ID' label and a mono/tabular value so it can be read out or copied accurately. Pass the bare id — never write it into `description` as prose.",
|
|
38
|
+
"name": "requestId",
|
|
39
|
+
"type": "string"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"description": "The permission/role the viewer is missing (403). Pass the bare permission name ('reports.view'); the localized 'Required permission' label is the surface's.",
|
|
43
|
+
"name": "permission",
|
|
44
|
+
"type": "ReactNode"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"description": "The organization/tenant the failed request was scoped to. Together with `permission` this is what distinguishes a wrong-workspace 403 from a missing-role 403.",
|
|
48
|
+
"name": "organization",
|
|
49
|
+
"type": "ReactNode"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"description": "Planned-outage timing (503). `start`/`end` are ISO-8601 INSTANTS and `timeZone` an IANA id: the surface formats them with Intl.DateTimeFormat(locale).formatRange() and keeps the ISO value in <time dateTime>. NEVER pass a pre-formatted '18:00 - 20:00 JST' string. `progress` (0-100) adds a labelled Progress meter named via Intl.NumberFormat percent style; it is SERVER-SENT on purpose — deriving it from the client clock breaks SSR hydration.",
|
|
53
|
+
"name": "maintenance",
|
|
54
|
+
"type": "{ start: string; end?: string; timeZone?: string; progress?: number }"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"description": "Override the status-derived icon. Use only when the product has a truer glyph for the failure, never to change perceived severity.",
|
|
58
|
+
"name": "icon",
|
|
59
|
+
"type": "ComponentType<{ className?: string }>"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"description": "Override the status-derived tone (same union as EmptyState.tone). Defaults: 400/403/503 warning · 404 muted · 500 destructive.",
|
|
63
|
+
"name": "tone",
|
|
64
|
+
"type": "\"muted\" | \"info\" | \"success\" | \"warning\" | \"destructive\""
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"description": "Semantic heading level of `title`. Defaults to 2 in application mode (a PageContainer h1 sits above) and 1 in system mode (the surface IS the page). Choose it to keep the outline valid, never for size.",
|
|
68
|
+
"name": "titleLevel",
|
|
69
|
+
"type": "1 | 2 | 3 | 4"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"description": "system mode ONLY — brand slot above the status code (a Logo). Ignored in application mode, where the shell already shows the brand.",
|
|
73
|
+
"name": "brand",
|
|
74
|
+
"type": "ReactNode"
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"description": "system mode ONLY — the page footer (contentinfo): copyright, status page link, locale switch.",
|
|
78
|
+
"name": "footer",
|
|
79
|
+
"type": "ReactNode"
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"defaultValue": "\"sm\"",
|
|
83
|
+
"description": "system mode ONLY — measure of the centred column (the CenteredShell width tier).",
|
|
84
|
+
"name": "width",
|
|
85
|
+
"type": "\"sm\" | \"md\" | \"lg\""
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"description": "Root element id.",
|
|
89
|
+
"name": "id",
|
|
90
|
+
"type": "string"
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"description": "Root class override (rarely needed).",
|
|
94
|
+
"name": "className",
|
|
95
|
+
"type": "string"
|
|
96
|
+
}
|
|
97
|
+
],
|
|
98
|
+
"related": [
|
|
99
|
+
"CenteredShell — the viewport-centred page shell that ErrorSurface renders internally for mode=\"system\". Use it directly only for a standalone surface that is NOT one of the four HTTP statuses.",
|
|
100
|
+
"AppShell + PageContainer — what you wrap around a mode=\"application\" surface. ErrorSurface never builds them: nav sections and the user menu are consumer-owned data.",
|
|
101
|
+
"EmptyState — the zero-state primitive ErrorSurface composes for its icon/title/description/action body. Use it directly for an empty LIST or an empty section, not for an HTTP exception page.",
|
|
102
|
+
"AlertQueryError / humanError — inline, in-page query failure feedback. ErrorSurface is the whole PAGE; an inline alert is the right pick when the surrounding page still works.",
|
|
103
|
+
"Progress — the meter ErrorSurface renders for maintenance.progress."
|
|
104
|
+
],
|
|
105
|
+
"rules": [
|
|
106
|
+
23,
|
|
107
|
+
24
|
|
108
|
+
],
|
|
109
|
+
"storyPath": "layout/ErrorSurface.stories.tsx",
|
|
110
|
+
"tagline": "Package-owned semantic exception surface for 400 / 403 / 404 / 500 / 503. `mode` is the SHELL CONTRACT: \"application\" renders the body you put inside the AppShell the route already provides (chrome PRESERVED, never reconstructed); \"system\" owns the whole page via CenteredShell align=\"center\" (package-owned 1440/1024/390 geometry). Exactly one recovery action, plus semantic request-id / permission / organization / maintenance metadata slots.",
|
|
111
|
+
"usage": [
|
|
112
|
+
"DO use ErrorSurface for ANY 400 / 403 / 404 / 500 / 503 page. It is a real import from @godxjp/ui/layout — never hand-compose an error page from AuthShell + a generic Card, and never add a consumer-local `.canonical-auth-card`-style class (that workaround IS the regression).",
|
|
113
|
+
"DO put mode=\"application\" INSIDE the shell the route already renders: <AppShell …><PageContainer …><ErrorSurface mode=\"application\" …/></PageContainer></AppShell>. The surface returns only its own block on purpose, so the sidebar/topbar/breadcrumb survive and the user can navigate away.",
|
|
114
|
+
"DO use mode=\"system\" for 500/503 and pass NOTHING for geometry — the surface renders CenteredShell align=\"center\" itself. A className=\"min-h-dvh flex items-center\" is always wrong.",
|
|
115
|
+
"DO pass ONE action. A second CTA is dropped with a development error; put 'contact support' in `description`.",
|
|
116
|
+
"DO pass `maintenance` as ISO-8601 instants + an IANA timeZone and let Intl format it. A hand-built window string cannot localize, and a client-derived `progress` breaks SSR hydration.",
|
|
117
|
+
"DO let `status` pick the icon and tone. Override them only for a truer product glyph — never to recolour a status into a different severity.",
|
|
118
|
+
"DO NOT retune the geometry with a className: --error-surface-max-width, --error-surface-gap, --error-surface-padding-block(-compact), --error-surface-meta-* and --error-surface-progress-max-width are the knobs. The metadata divider defaults to `none` (rule #44); opt in with --error-surface-meta-border.",
|
|
119
|
+
"DO NOT write the request id, the missing permission or the maintenance window into `description` as prose — each is a semantic <dt>/<dd> slot, and prose loses the label↔value relationship for a screen reader.",
|
|
120
|
+
"DO NOT use AuthShell for an error page: it is the UNAUTHENTICATED root and imposes auth-card geometry."
|
|
121
|
+
],
|
|
122
|
+
"useCases": [
|
|
123
|
+
"Inertia/Laravel exception page (SCR-006): one Error.tsx receives `status` from Handler::render() and renders <ErrorSurface mode={status >= 500 ? \"system\" : \"application\"} status={status} … />, with Error.layout keeping the authenticated shell for 403/404 only.",
|
|
124
|
+
"Forbidden report inside the console: <ErrorSurface mode=\"application\" status={403} permission=\"reports.view\" organization=\"Acme KK\" action={<Button asChild><Link href=\"/\">Back</Link></Button>} /> inside the existing AppShell + PageContainer.",
|
|
125
|
+
"Planned maintenance page: <ErrorSurface mode=\"system\" status={503} maintenance={{ start, end, timeZone: \"Asia/Tokyo\", progress: 40 }} brand={<Logo glyph=\"G\" />} footer={…} />.",
|
|
126
|
+
"Unexpected failure with a support correlation id: <ErrorSurface mode=\"system\" status={500} requestId=\"01J9Z0…\" action={<Button onClick={reload}>Reload</Button>} />."
|
|
127
|
+
]
|
|
128
|
+
}
|