@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,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"docPath": "layout/affix.tsx",
|
|
3
|
+
"example": "import { Affix } from \"@godxjp/ui/layout\";\nimport { Button, Text } from \"@godxjp/ui/general\";\nimport { Flex } from \"@godxjp/ui/layout\";\n\nconst [pinned, setPinned] = useState(false);\n\n<Affix offsetBlockStart={64} onChange={setPinned}>\n <Flex align=\"center\" justify=\"between\" gap=\"sm\">\n <Text weight=\"medium\">{pinned ? \"Invoices\" : \"All invoices, 2026\"}</Text>\n <Button size={pinned ? \"sm\" : \"md\"}>New invoice</Button>\n </Flex>\n</Affix>",
|
|
4
|
+
"group": "layout",
|
|
5
|
+
"importPath": "@godxjp/ui/layout",
|
|
6
|
+
"name": "Affix",
|
|
7
|
+
"props": [
|
|
8
|
+
{
|
|
9
|
+
"description": "What gets pinned. Must not itself be `position: absolute` (antd's note applies here for the same reason: the pin works by making this content `position: fixed`).",
|
|
10
|
+
"name": "children",
|
|
11
|
+
"type": "ReactNode"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"description": "antd `offsetTop`, on its LOGICAL axis. Pixels from the scrollport's block-start edge at which it pins, and the distance it sits at once pinned. Overrides the `--affix-inset-block-start` token per instance; omit it to take the theme's. Default `0` (antd's) via that token.",
|
|
15
|
+
"name": "offsetBlockStart",
|
|
16
|
+
"type": "number"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"description": "antd `offsetBottom`, on its LOGICAL axis — the sticky FORM FOOTER rather than the sticky header. Passing it (and no `offsetBlockStart`) is what selects block-end pinning, exactly as in antd; block-start wins when both are given.",
|
|
20
|
+
"name": "offsetBlockEnd",
|
|
21
|
+
"type": "number"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"description": "antd `target`, default `() => window` — the scroll box to pin against, which need not be the nearest scrolling ancestor. Same lazy-getter shape `FloatButton.BackTop.target` uses here, and for the same reason: the element does not exist on the render that declares it.",
|
|
25
|
+
"name": "target",
|
|
26
|
+
"type": "() => Window | HTMLElement | null"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"description": "antd `onChange`. Fires on the pin TRANSITION and only on it — never on a scroll frame that did not change the state. Structural, not guarded: the value is React state, so the effect cannot run without it having flipped, and the mount is skipped.",
|
|
30
|
+
"name": "onChange",
|
|
31
|
+
"type": "(affixed: boolean) => void"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"description": "NOT props. antd's physical spellings, kept in the type so that arriving from antd's docs is a COMPILE ERROR naming `offsetBlockStart` / `offsetBlockEnd`, plus a development console.warn. See docs/DESIGN-AUTHORITY.md.",
|
|
35
|
+
"name": "offsetTop / offsetBottom",
|
|
36
|
+
"type": "never"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"description": "DOM id of the outer (in-flow) box.",
|
|
40
|
+
"name": "id",
|
|
41
|
+
"type": "string"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"description": "Structural class on the outer box.",
|
|
45
|
+
"name": "className",
|
|
46
|
+
"type": "string"
|
|
47
|
+
}
|
|
48
|
+
],
|
|
49
|
+
"related": [
|
|
50
|
+
"Anchor — takes an `AffixProp` subset as its own `affix` prop and pins its section nav with this.",
|
|
51
|
+
"PageContainer — `stickyFooter` / `footerReveal=\"onScroll\"` is the page-level answer; it shares the same `useIntersects` observer this uses.",
|
|
52
|
+
"FloatButton.BackTop — the other public scroll-position API, and where `target`'s lazy-getter shape comes from.",
|
|
53
|
+
"Topbar / AppShell — the app chrome an affixed bar usually sits UNDER; `--affix-inset-block-start` is how it clears it."
|
|
54
|
+
],
|
|
55
|
+
"rules": [
|
|
56
|
+
2,
|
|
57
|
+
40,
|
|
58
|
+
44,
|
|
59
|
+
45
|
|
60
|
+
],
|
|
61
|
+
"storyPath": "layout/Affix.stories.tsx",
|
|
62
|
+
"tagline": "Ant Design `Affix` (6.6.5): pin an element to its scrollport once the page scrolls past it, and REPORT that it is pinned. `position: sticky` pins and then says nothing, so a sticky header cannot shrink, swap its logo or raise its shadow; this one exposes the boolean as `data-affixed` for CSS and `onChange` for JavaScript, and holds the flow open with a MEASURED placeholder so the page does not jump by the bar's height at the moment it pins.",
|
|
63
|
+
"usage": [
|
|
64
|
+
"DO use it whenever the pinned state has to be VISIBLE — a header that condenses, a toolbar that gains a shadow over the rows sliding under it, a filter rail that swaps to a compact form. That state is the entire reason this exists; if you need none of it, plain `position: sticky` is lighter and correct.",
|
|
65
|
+
"DO style the pinned state off `[data-affixed]` on `data-slot=\"affix-content\"`, and transition on `--duration-fast` / `--ease-standard`. The condensed bar must still be there under `prefers-reduced-motion` — it snaps to the smaller size, it never fades out.",
|
|
66
|
+
"DO set the resting offset ONCE in your theme (`:root { --affix-inset-block-start: 4rem; }`) when every affixed bar in the product has to clear the same app header. `offsetBlockStart` is the per-instance override, not the place to repeat a global decision.",
|
|
67
|
+
"DO pass `target` when the page scrolls inside a pane rather than the document — an AppShell main region, a dialog body, a MasterDetail column.",
|
|
68
|
+
"DON'T put a `transform`, `filter` or `backdrop-filter` on an ancestor. That ancestor becomes the containing block for `position: fixed` and the bar pins to it instead of the scrollport. The same limitation applies to `position: sticky` and to antd's Affix; there is no fix inside the component.",
|
|
69
|
+
"DON'T give the children `position: absolute`, and DON'T expect a horizontally scrolling container to work — antd documents both, and both are true here.",
|
|
70
|
+
"DON'T hand-roll the placeholder. The height is MEASURED, so it is right at every width and after any re-wrap; a number written once in CSS is wrong at every other width, which is the page-jump bug this component exists to remove."
|
|
71
|
+
],
|
|
72
|
+
"useCases": [
|
|
73
|
+
"A site or app header that condenses on scroll: the wordmark shrinks to a monogram and the bar loses half its height, driven by `data-affixed` alone.",
|
|
74
|
+
"A table toolbar (search, filters, bulk actions) that stays reachable over a thousand-row list and gains a shadow the moment it starts covering rows.",
|
|
75
|
+
"A form's action footer pinned to the bottom of a long form with `offsetBlockEnd`, released once the real end of the form scrolls into view.",
|
|
76
|
+
"The substrate under `Anchor affix` — antd specifies Anchor's own `affix` prop as `AffixProps`, so the two are one stack."
|
|
77
|
+
]
|
|
78
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"example": "import { Alert, AlertTitle, AlertDescription } from \"@godxjp/ui/feedback\";\n\n<Alert tone=\"warning\">\n <AlertTitle>3 件の打刻漏れがあります</AlertTitle>\n <AlertDescription>本日中に確認してください。</AlertDescription>\n</Alert>",
|
|
3
|
+
"group": "feedback",
|
|
4
|
+
"importPath": "@godxjp/ui/feedback",
|
|
5
|
+
"name": "Alert",
|
|
6
|
+
"props": [
|
|
7
|
+
{
|
|
8
|
+
"defaultValue": "\"default\"",
|
|
9
|
+
"description": "STRUCTURAL axis, orthogonal to `tone` (which owns colour + icon): \"default\" is the inline rounded card; \"banner\" is the full-bleed attention strip — prefer the `Banner` export, which fixes this axis.",
|
|
10
|
+
"name": "variant",
|
|
11
|
+
"type": "\"default\" | \"banner\""
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"description": "Renders an × dismiss button when provided.",
|
|
15
|
+
"name": "onDismiss",
|
|
16
|
+
"type": "() => void"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"description": "Override or hide (false) the icon.",
|
|
20
|
+
"name": "icon",
|
|
21
|
+
"type": "LucideIcon | false"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"description": "Semantic tone driving the colour + leading icon.",
|
|
25
|
+
"name": "tone",
|
|
26
|
+
"type": "\"success\" | \"warning\" | \"destructive\" | \"info\" | \"neutral\""
|
|
27
|
+
}
|
|
28
|
+
],
|
|
29
|
+
"related": [
|
|
30
|
+
"Toaster — use for transient, auto-dismissing feedback ('Record saved', 'Deleted'). Alert is for persistent page-scoped banners; Toaster is for fire-and-forget notifications triggered by toast() from sonner.",
|
|
31
|
+
"AlertMutationFeedback — use when you want inline success/error feedback tightly coupled to a form mutation's state (renders inline below the submit button). Alert requires you to manage show/hide state yourself.",
|
|
32
|
+
"DataState — use for full query lifecycle (loading skeleton + empty state + error) inside a data-fetching section. Alert.QueryError is the error sub-component DataState uses internally; prefer DataState when you also need the loading/empty states.",
|
|
33
|
+
"EmptyState — use for the zero-data case inside a list or table section, not for errors or warnings. Alert is for status messages; EmptyState is for the absence of data."
|
|
34
|
+
],
|
|
35
|
+
"rules": [],
|
|
36
|
+
"storyPath": "feedback/Alert.stories.tsx",
|
|
37
|
+
"subParts": [
|
|
38
|
+
"AlertActions",
|
|
39
|
+
"AlertBase",
|
|
40
|
+
"AlertContent",
|
|
41
|
+
"AlertDescription",
|
|
42
|
+
"AlertMutationFeedback",
|
|
43
|
+
"AlertQueryError",
|
|
44
|
+
"AlertTitle"
|
|
45
|
+
],
|
|
46
|
+
"tagline": "Inline alert banner with variant-aware icon + optional dismiss. Parts: Alert/AlertTitle/AlertDescription/AlertActions/AlertQueryError.",
|
|
47
|
+
"usage": [
|
|
48
|
+
"ANATOMY (positions are fixed — never re-lay-them-out): Alert is ONE horizontal row — a SINGLE leading tone icon at the inline-start (top-aligned to the first text line, auto-selected by `tone`, never two icons), then the text body (Title/Description), then `<Alert.Actions>` in a trailing-RIGHT column (≥sm), and the dismiss × pinned to the TOP-RIGHT corner (rendered by `onDismiss`). DON'T stack these vertically, DON'T make the action a full-width bar under the text, DON'T center the × at the bottom, DON'T add a second icon — that hand-rolled vertical banner is the #1 Alert mistake.",
|
|
49
|
+
"DO compose text as `<Alert.Title>` + `<Alert.Description>` — they stack vertically inside the body (the Example below is canonical). When you add `<Alert.Actions>`, the body becomes a two-column grid (text | actions) at ≥sm; group multi-part text in `<Alert.Content>` so it occupies the text column as one block: `<Alert tone=\"destructive\"><Alert.Content><Alert.Title>Error</Alert.Title><Alert.Description>{msg}</Alert.Description></Alert.Content><Alert.Actions><Button …/></Alert.Actions></Alert>`.",
|
|
50
|
+
"DO use `Alert.QueryError` (alias `AlertQueryError`) for TanStack Query / API failure surfaces — it already renders humanError(error), an i18n title, and an optional Retry button. Never hand-roll that pattern.",
|
|
51
|
+
"`AlertMutationFeedback` props: `mutation`, `onRetry?`, `showRetry?` (default `true`), `pending?`, `ignoreValidationErrors?: boolean`, `className?`. `ignoreValidationErrors={true}` skips the alert for every error where `classifyQueryError(mutation.error).category === \"validation\"` (400/422). DEFAULT (omitted): skip a validation error only when rendered inside a `FormRoot`/`Form` whose `errors` bag holds at least one message (the fields show it, `<FormErrors />` shows unclaimed keys); outside such a form, or with an empty/absent bag, the alert renders. So DON'T hand-guard `{!isValidation && <AlertMutationFeedback …/>}` inside `FormRoot errors={…}` — a 422 is not drawn twice, and a 5xx still renders. Pass `ignoreValidationErrors={false}` to force the alert in such a form.",
|
|
52
|
+
"DON'T pass raw action elements directly as top-level children of `<Alert>` without wrapping them in `<Alert.Actions>` — the layout slot only activates correctly via the `data-slot=\"alert-actions\"` wrapper.",
|
|
53
|
+
"DON'T hand-roll a dismiss ✕ button — pass `onDismiss` to `<Alert>` and the component renders its own accessible dismiss button with `aria-label=\"Dismiss\"`. The `onDismiss` handler may return a Promise.",
|
|
54
|
+
"DON'T suppress the icon with `icon={false}` unless there is a deliberate design reason; the icon is the primary a11y cue for sighted users since the root already carries `role=\"alert\"` for screen readers.",
|
|
55
|
+
"DO NOT use `Alert` for transient ephemeral feedback (e.g. 'saved successfully'). Use `toast()` from sonner + `<Toaster>` for that. `Alert` is for persistent, page-scoped banners that stay visible until the user acts or dismisses."
|
|
56
|
+
],
|
|
57
|
+
"useCases": [
|
|
58
|
+
"Page-level error banner after a form submission fails server-side validation — `tone=\"destructive\"` with `Alert.Title` summarising the error and `Alert.Description` listing field issues, paired with `onDismiss` so the user can clear it.",
|
|
59
|
+
"Inline warning at the top of an accounting invoice list when the OAuth token for the MF sync is about to expire — `tone=\"warning\"` with an `Alert.Actions` containing a 'Reconnect' Button.",
|
|
60
|
+
"Success confirmation banner rendered after a bulk-import job completes and the user returns to the list page — `tone=\"success\"` with `Alert.Description` showing the record count imported.",
|
|
61
|
+
"TanStack Query data-fetch failure inside a Card body — use `<Alert.QueryError error={error} onRetry={refetch} />` instead of writing a custom error state.",
|
|
62
|
+
"Informational notice at the top of a settings page when a feature is in beta or requires a plan upgrade — `tone=\"info\"` with a short description and an `Alert.Actions` 'Learn more' link.",
|
|
63
|
+
"Dismissible billing-overdue notice at the top of the dashboard — `tone=\"destructive\"` with `onDismiss` that sets a session flag so it does not reappear until the next login."
|
|
64
|
+
]
|
|
65
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
{
|
|
2
|
+
"example": "import { AlertDialog } from \"@godxjp/ui/feedback\";\n\n<AlertDialog\n open={open}\n onOpenChange={setOpen}\n title=\"Delete project\"\n description=\"This action cannot be undone.\"\n confirmLabel=\"Delete\"\n cancelLabel=\"Cancel\"\n onConfirm={async () => {\n await deleteProject();\n setOpen(false);\n }}\n variant=\"destructive\"\n/>",
|
|
3
|
+
"group": "feedback",
|
|
4
|
+
"importPath": "@godxjp/ui/feedback",
|
|
5
|
+
"name": "AlertDialog",
|
|
6
|
+
"props": [
|
|
7
|
+
{
|
|
8
|
+
"description": "Controlled open state.",
|
|
9
|
+
"name": "open",
|
|
10
|
+
"type": "boolean"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"description": "Open-state change handler.",
|
|
14
|
+
"name": "onOpenChange",
|
|
15
|
+
"type": "(open: boolean) => void"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"description": "Accessible title/announcement for the alertdialog.",
|
|
19
|
+
"name": "title",
|
|
20
|
+
"required": true,
|
|
21
|
+
"type": "string"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"description": "Optional supporting explanatory text.",
|
|
25
|
+
"name": "description",
|
|
26
|
+
"type": "string"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"description": "Primary action label (defaults to translated continue).",
|
|
30
|
+
"name": "confirmLabel",
|
|
31
|
+
"type": "string"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"description": "Dismiss action label (defaults to translated cancel).",
|
|
35
|
+
"name": "cancelLabel",
|
|
36
|
+
"type": "string"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"defaultValue": "\"default\"",
|
|
40
|
+
"description": "Variant passed through to the confirm button.",
|
|
41
|
+
"name": "variant",
|
|
42
|
+
"type": "\"default\" | \"destructive\""
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"description": "Optional type-to-confirm phrase to prevent accidental confirm.",
|
|
46
|
+
"name": "confirmPhrase",
|
|
47
|
+
"type": "string"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"description": "Semantic alias of `confirmPhrase` — the exact token to type (e.g. an org slug) before confirm arms.",
|
|
51
|
+
"name": "challenge",
|
|
52
|
+
"type": "string"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"description": "Primary action handler.",
|
|
56
|
+
"name": "onConfirm",
|
|
57
|
+
"type": "() => Promise<void> | void"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"description": "Optional step-up re-auth (passkey/2FA) gate; must resolve truthy before `onConfirm` fires. Returning false keeps the dialog open and announces failure.",
|
|
61
|
+
"name": "stepUp",
|
|
62
|
+
"type": "() => Promise<boolean> | boolean"
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"description": "Keep modal open after confirm when true.",
|
|
66
|
+
"name": "keepOpenOnConfirm",
|
|
67
|
+
"type": "boolean"
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"description": "Disable actions while async work is running.",
|
|
71
|
+
"name": "pending",
|
|
72
|
+
"type": "boolean"
|
|
73
|
+
}
|
|
74
|
+
],
|
|
75
|
+
"related": [
|
|
76
|
+
"Dialog — use for form-style and non-destructive modal flows, no confirm preset behavior.",
|
|
77
|
+
"AlertDialogRoot — the compound counterpart, and the OLDER way in. It still works and is not going away in this major, but a confirmation body the preset does not cover (a summary table, a diff, a required reason field) is now better written as `Dialog` with `variant=\"destructive\"`: same `role=\"alertdialog\"`, same non-dismissable scrim, freeform body, and `DialogBody` / `DialogClose` available — the two parts the `AlertDialog*` family never had (gh#567)."
|
|
78
|
+
],
|
|
79
|
+
"rules": [
|
|
80
|
+
23,
|
|
81
|
+
3
|
|
82
|
+
],
|
|
83
|
+
"storyPath": "feedback/AlertDialog.stories.tsx",
|
|
84
|
+
"subParts": [
|
|
85
|
+
"AlertDialogAction",
|
|
86
|
+
"AlertDialogCancel",
|
|
87
|
+
"AlertDialogContent",
|
|
88
|
+
"AlertDialogDescription",
|
|
89
|
+
"AlertDialogFooter",
|
|
90
|
+
"AlertDialogHeader",
|
|
91
|
+
"AlertDialogOverlay",
|
|
92
|
+
"AlertDialogPortal",
|
|
93
|
+
"AlertDialogTitle",
|
|
94
|
+
"AlertDialogTrigger"
|
|
95
|
+
],
|
|
96
|
+
"tagline": "Canonical modal confirmation flow (destructive / high-stakes decisions). Preserves confirm semantics with `role=\"alertdialog\"` and built-in cancel/confirm handling.",
|
|
97
|
+
"usage": [
|
|
98
|
+
"Use `AlertDialog` for destructive/irreversible actions (delete, void, unpublish, archive, etc.).",
|
|
99
|
+
"Use `confirmPhrase`/`challenge` for high-friction operations (e.g. typing an org slug) to reduce accidental confirmation — both force the destructive shape: the destructive confirm button plus a leading status glyph beside the title, which is Ant Design `Modal.confirm` parity. The header surface stays UNTINTED — antd signals danger with the glyph and never tints a modal header (its soft `colorErrorBg` belongs to Alert/Tag/message). To tint the band anyway, set `DialogHeader tone` yourself; it is a separate seven-value axis the preset no longer imposes.",
|
|
100
|
+
"Pass `stepUp` for a passkey/2FA re-auth gate that must resolve truthy before `onConfirm` runs (refunds, org deletion).",
|
|
101
|
+
"Pass `keepOpenOnConfirm` when the confirm handler advances a multi-step flow and should not close immediately."
|
|
102
|
+
],
|
|
103
|
+
"useCases": [
|
|
104
|
+
"Dangerous delete or irreversible workflow confirmation that should block the background UI.",
|
|
105
|
+
"Organization/resource deletion gated behind typing the exact slug (`challenge`).",
|
|
106
|
+
"Refunds or privileged actions requiring step-up re-authentication before they run.",
|
|
107
|
+
"Destructive batch operations that should remain modal and explicit until action is intentionally confirmed."
|
|
108
|
+
]
|
|
109
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"example": "import {\n AlertDialogRoot, AlertDialogTrigger, AlertDialogPortal, AlertDialogOverlay,\n AlertDialogContent, AlertDialogHeader, AlertDialogTitle, AlertDialogDescription,\n AlertDialogFooter, AlertDialogAction, AlertDialogCancel,\n} from \"@godxjp/ui/feedback\";\nimport { Button } from \"@godxjp/ui/general\";\n\nfunction ConfirmSettlement() {\n return (\n <AlertDialogRoot>\n <AlertDialogTrigger asChild><Button variant=\"outline\" size=\"sm\">支払を確定</Button></AlertDialogTrigger>\n <AlertDialogPortal>\n <AlertDialogOverlay />\n <AlertDialogContent className=\"max-w-md\">\n <AlertDialogHeader>\n <AlertDialogTitle>2026年7月分の支払を確定しますか?</AlertDialogTitle>\n <AlertDialogDescription>確定すると振込データが生成されます。</AlertDialogDescription>\n </AlertDialogHeader>\n {/* freeform impact summary goes here */}\n <AlertDialogFooter>\n <AlertDialogCancel>戻る</AlertDialogCancel>\n <AlertDialogAction>確定する</AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialogPortal>\n </AlertDialogRoot>\n );\n}",
|
|
3
|
+
"group": "feedback",
|
|
4
|
+
"importPath": "@godxjp/ui/feedback",
|
|
5
|
+
"name": "AlertDialogRoot",
|
|
6
|
+
"props": [
|
|
7
|
+
{
|
|
8
|
+
"description": "Controlled open state.",
|
|
9
|
+
"name": "open",
|
|
10
|
+
"type": "boolean"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"description": "Uncontrolled initial open state (use with AlertDialogTrigger).",
|
|
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
|
+
"description": "The trigger and the portalled alertdialog parts.",
|
|
24
|
+
"name": "children",
|
|
25
|
+
"type": "React.ReactNode"
|
|
26
|
+
}
|
|
27
|
+
],
|
|
28
|
+
"related": [
|
|
29
|
+
"AlertDialog — the flat preset. Prefer it; it is the canonical destructive-confirm recipe and needs no compound markup.",
|
|
30
|
+
"Dialog — compound modal for form-style, non-destructive flows. `role=dialog`, dismissible on outside click.",
|
|
31
|
+
"AlertDialogHeader — the header band; `tone` tints only the background (default | success | warning | destructive | info | muted | neutral)."
|
|
32
|
+
],
|
|
33
|
+
"rules": [
|
|
34
|
+
23,
|
|
35
|
+
3
|
|
36
|
+
],
|
|
37
|
+
"storyPath": "feedback/AlertDialog.stories.tsx",
|
|
38
|
+
"tagline": "Compound alertdialog Root — the role=\"alertdialog\" mirror of Dialog's root. Wraps Radix AlertDialog.Root and supplies the context AlertDialogTitle/AlertDialogDescription/AlertDialogAction/AlertDialogCancel read. Parts: AlertDialogTrigger/AlertDialogPortal/AlertDialogOverlay/AlertDialogContent/AlertDialogHeader/AlertDialogFooter/AlertDialogTitle/AlertDialogDescription/AlertDialogAction/AlertDialogCancel.",
|
|
39
|
+
"usage": [
|
|
40
|
+
"Reach for the flat `AlertDialog` preset FIRST — it already covers title/description/confirm/cancel, the typed `challenge`, `stepUp` re-auth and `pending`. Use `AlertDialogRoot` only when the confirm body needs content the preset does not model (an impact summary, a diff, a nested list).",
|
|
41
|
+
"DO name it `AlertDialogRoot`, not `AlertDialog` — the `AlertDialog` export is the flat preset and takes a completely different (non-compound) prop API.",
|
|
42
|
+
"DO include `AlertDialogHeader` with `AlertDialogTitle` inside every `AlertDialogContent` — Radix requires an accessible title for `role=alertdialog`; omitting it warns in the console and breaks screen-reader announcement. `AlertDialogHeader` also takes the prop-driven `title`/`subtitle`/`extra`/`tone` form, where `subtitle` renders the `AlertDialogDescription`.",
|
|
43
|
+
"DO portal explicitly: `AlertDialogContent` does NOT self-portal (unlike `DialogContent`). Wrap it in `AlertDialogPortal` with a sibling `AlertDialogOverlay`, or the scrim and stacking context are wrong.",
|
|
44
|
+
"DO use `AlertDialogAction` / `AlertDialogCancel` for the footer buttons — they carry the button styling AND the Radix close semantics. Do not wrap them in `asChild` `<Button variant=…>`: the Root's button classes and the child's would both land on the element and the variant would not win.",
|
|
45
|
+
"DO NOT import `@radix-ui/react-alert-dialog` directly in a consumer app. Everything the compound needs is exported from `@godxjp/ui/feedback`."
|
|
46
|
+
],
|
|
47
|
+
"useCases": [
|
|
48
|
+
"Confirm-with-impact-summary — a destructive confirmation that must show what will be affected (a list of 3 downstream jobs, a table of invoices) before the user commits. The flat preset only takes a string `description`.",
|
|
49
|
+
"Batch-close confirmation whose body renders a rich breakdown (counts, totals, per-tenant rows) alongside the standard confirm/cancel pair.",
|
|
50
|
+
"Any confirmation that must keep `role=alertdialog` semantics (focus trap, no dismiss-on-outside-click) but needs freeform children — use Dialog only when the flow is non-destructive."
|
|
51
|
+
]
|
|
52
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
{
|
|
2
|
+
"docPath": "navigation/anchor.tsx",
|
|
3
|
+
"example": "import { Anchor } from \"@godxjp/ui/navigation\";\n\n<Anchor\n offsetBlockStart={64}\n targetOffsetBlockStart={88}\n label=\"On this page\"\n items={[\n { key: \"overview\", href: \"#overview\", title: \"Overview\" },\n {\n key: \"pricing\",\n href: \"#pricing\",\n title: \"Pricing\",\n children: [{ key: \"tiers\", href: \"#tiers\", title: \"Tiers\" }],\n },\n { key: \"faq\", href: \"#faq\", title: \"FAQ\" },\n ]}\n onValueChange={(href) => console.log(\"reading\", href)}\n/>",
|
|
4
|
+
"group": "navigation",
|
|
5
|
+
"importPath": "@godxjp/ui/navigation",
|
|
6
|
+
"name": "Anchor",
|
|
7
|
+
"props": [
|
|
8
|
+
{
|
|
9
|
+
"description": "antd `items`, in document order. Each is `{ key, href, title, target?, children?, replace?, targetOffsetBlockStart? }` — antd's AnchorItem, field for field, with its per-link `targetOffset` on the logical axis. `children` is ONE level of nesting and is dropped (with a warning) when `direction=\"horizontal\"`, as in antd.",
|
|
10
|
+
"name": "items",
|
|
11
|
+
"type": "AnchorItemProp[]"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"description": "antd `direction`, default `\"vertical\"`. Horizontal is one scrolling row: the ink rail moves along the block-end edge and nesting is not available.",
|
|
15
|
+
"name": "direction",
|
|
16
|
+
"type": "\"vertical\" | \"horizontal\""
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"description": "antd `affix`, default `true` — literally antd's own `AffixProps` minus the fields Anchor supplies, which is why Affix is the dependency. `false` leaves the nav in the flow.",
|
|
20
|
+
"name": "affix",
|
|
21
|
+
"type": "boolean | Omit<AffixProp, 'offsetBlockStart' | 'offsetTop' | 'target' | 'children'>"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"description": "antd `bounds`, default `5`. Pixel tolerance added to the decision line.",
|
|
25
|
+
"name": "bounds",
|
|
26
|
+
"type": "number"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"description": "The active `href`, as the controlled triad. `onValueChange` is antd's `onChange` under this library's name, and it reports the link the SCROLL POSITION resolved (antd's own note) rather than what `getCurrentAnchor` substituted. antd's `onChange` is typed `never` and warns.",
|
|
30
|
+
"name": "value / defaultValue / onValueChange",
|
|
31
|
+
"type": "string / string / (href: string) => void"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"description": "antd `getCurrentAnchor` — the last word on the highlight, running INSIDE the resolution with no render round-trip. A controlled `value` outranks it.",
|
|
35
|
+
"name": "getCurrentAnchor",
|
|
36
|
+
"type": "(activeLink: string) => string"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"description": "antd `offsetTop`, on its LOGICAL axis: where the decision line sits, and the distance `Affix` pins the nav at. Default `0`.",
|
|
40
|
+
"name": "offsetBlockStart",
|
|
41
|
+
"type": "number"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"description": "antd `targetOffset`, on its LOGICAL axis: where a CLICKED section lands — the room a pinned header needs. Defaults to `offsetBlockStart`, and (as in antd) it moves the decision line too, so a click can never leave the entry it just selected unselected.",
|
|
45
|
+
"name": "targetOffsetBlockStart",
|
|
46
|
+
"type": "number"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"description": "antd `getContainer`, default `() => window` — the scroll box holding the sections.",
|
|
50
|
+
"name": "getContainer",
|
|
51
|
+
"type": "() => HTMLElement | Window"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"description": "antd `showInkInFixed`, default `false`. Draw the travelling ink even with `affix={false}`; the static rule is always drawn.",
|
|
55
|
+
"name": "showInkInFixed",
|
|
56
|
+
"type": "boolean"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"description": "antd `replace`, default `false`. Replace the hash in history rather than pushing it; `items[].replace` overrides it per entry.",
|
|
60
|
+
"name": "replace",
|
|
61
|
+
"type": "boolean"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"description": "antd `onClick`, fired before the scroll. A modified click is never hijacked.",
|
|
65
|
+
"name": "onClick",
|
|
66
|
+
"type": "(event: React.MouseEvent<HTMLAnchorElement>, item: AnchorItemProp) => void"
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"description": "Accessible name of the <nav> landmark. antd ships an unnamed <div>; a page routinely carries a breadcrumb, a rail and this, so a localized default (\"On this page\") applies when omitted.",
|
|
70
|
+
"name": "label",
|
|
71
|
+
"type": "string"
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"description": "NOT props. antd's spellings, kept in the type so arriving from antd's docs is a COMPILE ERROR naming `onValueChange` / `offsetBlockStart` / `targetOffsetBlockStart`, plus a development console.warn.",
|
|
75
|
+
"name": "onChange / offsetTop / targetOffset",
|
|
76
|
+
"type": "never"
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"description": "DOM id of the <nav>.",
|
|
80
|
+
"name": "id",
|
|
81
|
+
"type": "string"
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"description": "Structural class on the <nav>.",
|
|
85
|
+
"name": "className",
|
|
86
|
+
"type": "string"
|
|
87
|
+
}
|
|
88
|
+
],
|
|
89
|
+
"related": [
|
|
90
|
+
"Affix — the pin underneath it. antd specifies `Anchor.affix` as `AffixProps`, so the two ship as one stack.",
|
|
91
|
+
"NavList — ROUTE navigation inside a page; its `activeId` is the answer Anchor computes, for a different kind of destination.",
|
|
92
|
+
"LegalDocumentShell — carries its own built-in contents rail for the legal-document case; Anchor is the general one, for content the shell does not own.",
|
|
93
|
+
"Breadcrumb — where you ARE in the hierarchy (`aria-current=\"page\"`), not where you are on the page."
|
|
94
|
+
],
|
|
95
|
+
"rules": [
|
|
96
|
+
2,
|
|
97
|
+
6,
|
|
98
|
+
40,
|
|
99
|
+
44,
|
|
100
|
+
45
|
|
101
|
+
],
|
|
102
|
+
"storyPath": "navigation/Anchor.stories.tsx",
|
|
103
|
+
"tagline": "Ant Design `Anchor` (6.6.5): the in-page section navigation, and the only thing in this library that COMPUTES which section is current. `NavList activeId` takes that answer as a prop. Resolves by antd's rule — the last section whose edge has crossed a single decision line — which is a pure function of scroll position and therefore cannot flicker, suppresses itself during the programmatic scroll a click starts, reads the landing hash before any scroll event, and jumps rather than tweens under `prefers-reduced-motion`.",
|
|
104
|
+
"usage": [
|
|
105
|
+
"DO give every target a real `id` and point `href` at it as `#id`. The links are real anchors: middle-clickable, deep-linkable, and correct before JavaScript boots.",
|
|
106
|
+
"DO set `targetOffsetBlockStart` to the height of whatever is pinned above the content, or a clicked section lands under the header. Leave `offsetBlockStart` alone unless the DECISION LINE also needs moving; it follows `targetOffsetBlockStart` by default.",
|
|
107
|
+
"DO use `value` / `onValueChange` when the router owns the current section; use `getCurrentAnchor` only for antd's narrower job of rewriting the resolved link in place.",
|
|
108
|
+
"DON'T reach for NavList for this. NavList is ROUTE navigation — each row is a page the router renders, and its `activeId` is an answer you already have. Anchor's entries are fragments of the page being read, which is also why the current one is `aria-current=\"location\"` and not `\"page\"`.",
|
|
109
|
+
"DON'T use Tabs either: the sections are all on the page at once and scroll past each other, where a tab panel shows one at a time.",
|
|
110
|
+
"DON'T nest more than one level, and don't nest at all when horizontal — antd allows neither, and this warns rather than rendering a second row the ink rail cannot follow.",
|
|
111
|
+
"DON'T hand-roll the scrollspy with an IntersectionObserver band. A section taller than the band reports nothing, and with several short sections in the band at once the answer depends on scroll direction; both are why this measures against a line instead."
|
|
112
|
+
],
|
|
113
|
+
"useCases": [
|
|
114
|
+
"A long settings or policy page whose sections a reader jumps between, with the current one always visible in the rail.",
|
|
115
|
+
"API or product documentation: the on-this-page rail beside the article, tracking the reader down the page.",
|
|
116
|
+
"A marketing landing page's section nav, pinned under the site header via `affix` and `offsetBlockStart`.",
|
|
117
|
+
"A horizontal section bar on a narrow viewport, where a vertical rail has no column to live in."
|
|
118
|
+
]
|
|
119
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
{
|
|
2
|
+
"example": "import { AppLauncher, Topbar } from \"@godxjp/ui/layout\";\n\n<Topbar\n start={brand}\n end={\n <AppLauncher\n apps={[\n { id: \"console\", name: t(\"app.console\"), href: \"/console\", icon: <BarChart3 />, current: true },\n { id: \"billing\", name: t(\"app.billing\"), href: \"/billing\", icon: <Receipt /> },\n ]}\n groups={[{ label: t(\"app.more\"), apps: [{ id: \"support\", name: t(\"app.support\"), href: supportUrl, external: true }] }]}\n linkComponent={inertiaSidebarLink(Link)}\n labels={labels}\n />\n }\n/>",
|
|
3
|
+
"group": "layout",
|
|
4
|
+
"importPath": "@godxjp/ui/layout",
|
|
5
|
+
"name": "AppLauncher",
|
|
6
|
+
"props": [
|
|
7
|
+
{
|
|
8
|
+
"description": "Ungrouped app tiles, rendered first with no heading. Each is { id, name, href, icon?, current?, external? } and each tile is a REAL <a href> — pass only apps the viewer may open; there is no disabled tile.",
|
|
9
|
+
"name": "apps",
|
|
10
|
+
"required": true,
|
|
11
|
+
"type": "readonly AppLauncherApp[]"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"description": "Labelled sections rendered after `apps` — the \"more from …\" band. Each group is a named role=\"group\", not a heading, so the same markup is correct inside the popover and inside the Sheet.",
|
|
15
|
+
"name": "groups",
|
|
16
|
+
"type": "readonly AppLauncherGroup[]"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"description": "Localized trigger name, panel title, empty/loading/retry copy, and the optional `externalHint` announced on an external tile (WCAG 3.2.5). `trigger` is a plain string, not a function of the current app: the nine-dot button shows no current value.",
|
|
20
|
+
"name": "labels",
|
|
21
|
+
"required": true,
|
|
22
|
+
"type": "AppLauncherLabels"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"defaultValue": "3",
|
|
26
|
+
"description": "Grid column count, written inline to the `--app-launcher-columns` custom property. Omit it and `.ui-app-launcher-panel` keeps its own declaration of 3 — the default sits in the stylesheet, where a theme can reach it, rather than in a component token (the token NAME vocabulary has no word for a count).",
|
|
27
|
+
"name": "columns",
|
|
28
|
+
"type": "number"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"description": "Framework router link — the SAME contract `Sidebar` and `NavList` take, so `inertiaSidebarLink(Link)` / `createSidebarLink(Link, \"to\")` is reused verbatim. The launcher still composes the tile; `external` apps bypass it and render a plain anchor.",
|
|
32
|
+
"name": "linkComponent",
|
|
33
|
+
"type": "SidebarLinkComponentProp"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"defaultValue": "false",
|
|
37
|
+
"description": "Shows the loading state; the trigger stays openable.",
|
|
38
|
+
"name": "loading",
|
|
39
|
+
"type": "boolean"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"description": "Consumer-supplied error state.",
|
|
43
|
+
"name": "error",
|
|
44
|
+
"type": "ReactNode"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"description": "Consumer-owned retry callback.",
|
|
48
|
+
"name": "onRetry",
|
|
49
|
+
"type": "() => void"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"defaultValue": "\"auto\"",
|
|
53
|
+
"description": "Presentation contract. \"auto\" resolves through the SHARED Sheet hook useSheetResponsiveMode(): desktop popover above --sheet-responsive-breakpoint-width (48rem/768px), focus-trapped bottom Sheet at/below it. \"fullscreen\" is the LAUNCHPAD — one full-viewport modal at every width, the page behind it blurred and the grid floating on that ground (macOS Launchpad / Windows Start), with the column count stepping 3·4·5·6 on the house container ladder. Reach for it in a PLATFORM start bar, where the launcher is the primary way to move between products; keep \"auto\" for a launcher inside one app's topbar, where the grid is a shortcut and the work behind it should stay visible.",
|
|
54
|
+
"name": "responsive",
|
|
55
|
+
"type": "\"auto\" | \"popover\" | \"sheet\" | \"fullscreen\""
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"defaultValue": "\"bar\"",
|
|
59
|
+
"description": "The BOX the trigger takes — the same split AppSettingToggle draws, for the same reason. `bar` (default) is a TopbarItem: a cell as tall as the bar, whose hover IS the bar's surface. `icon` is a square ghost Button, for chrome that is NOT a bar — a nav rail (GoDX Dock puts it there), a card header, a toolbar. A TopbarItem outside a bar has nothing to bleed to: it stretches to a container that never set a band height, and its squared corners and full-bleed hover read as a broken cell rather than a control. The panel, the grid, the labels and the responsive contract are identical in both.",
|
|
60
|
+
"name": "appearance",
|
|
61
|
+
"type": "\"bar\" | \"icon\""
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"description": "Controlled open state.",
|
|
65
|
+
"name": "open",
|
|
66
|
+
"type": "boolean"
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"description": "Open-state callback.",
|
|
70
|
+
"name": "onOpenChange",
|
|
71
|
+
"type": "(open: boolean) => void"
|
|
72
|
+
}
|
|
73
|
+
],
|
|
74
|
+
"related": [
|
|
75
|
+
"ServiceLauncherCard — the PAGE-SIZED launcher tile (status, hostname, plan, action, locked reason) for a service-catalogue page, where choosing is a considered act. AppLauncher's tile is bar-sized: mark + name, the whole tile one link, because changing app is a reflex. Neither is built out of the other; a grid of ServiceLauncherCards inside a popover is the wrong component.",
|
|
76
|
+
"AppShell (navRail) — the SAME platform scope expressed as a docked column instead of a bar control. Pick ONE: the launcher for a platform with MANY apps where switching is occasional (Google Workspace), the rail for a single product where switching workspace is constant enough to deserve permanent screen width (Slack).",
|
|
77
|
+
"OrgSwitcher — the other platform-scope control: which ORGANIZATION you are in, not which app. They compose; they do not replace each other.",
|
|
78
|
+
"TopbarItem — the bar cell the trigger is built from; use it directly for a one-off bar control."
|
|
79
|
+
],
|
|
80
|
+
"rules": [],
|
|
81
|
+
"storyPath": "layout/AppLauncher.stories.tsx",
|
|
82
|
+
"tagline": "Nine-dot topbar app grid — the platform-standard way to switch app (the Google Workspace shape).",
|
|
83
|
+
"usage": [
|
|
84
|
+
"DO drop it straight into a Topbar slot. It renders NO wrapper element, so the trigger is the slot's own flex child and `.ui-topbar-item { align-self: stretch }` reaches the bar's height.",
|
|
85
|
+
"DON'T hand-roll the trigger as `Button variant=\"ghost\"` IN A BAR. A Button in a bar is a --control-height pill floating in a taller strip, with its own hover fill and its own focus ring — the exact regression corrected in 20.0.0. In a bar the trigger is a `TopbarItem`, and that is what `appearance=\"bar\"` (the default) renders. OUTSIDE a bar the square ghost Button is correct, and `appearance=\"icon\"` renders it for you — still hand-roll nothing.",
|
|
86
|
+
"DO mark the app the viewer is inside with `current` — it becomes `aria-current=\"page\"`, which is what both the tint and the announcement key off.",
|
|
87
|
+
"DO set `external` on a destination outside this SPA. It renders a plain anchor with target/rel and skips `linkComponent`, because a client-side router link to another origin routes nowhere.",
|
|
88
|
+
"DON'T wrap it in your own media query to pick popover vs sheet — `responsive=\"auto\"` already reads the shared --sheet-responsive-breakpoint-width token.",
|
|
89
|
+
"`className`, `id` and every `data-*` land on the TRIGGER, so an end-to-end selector can hold it without depending on the localized accessible name."
|
|
90
|
+
],
|
|
91
|
+
"useCases": [
|
|
92
|
+
"A platform with several apps (console, billing, chat, people) where the bar must offer all of them from every page.",
|
|
93
|
+
"Replacing a hand-rolled dropdown of product links in the topbar end slot."
|
|
94
|
+
]
|
|
95
|
+
}
|