@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,86 @@
|
|
|
1
|
+
{
|
|
2
|
+
"example": "import { formatDate } from \"@godxjp/ui/datetime\";\nimport { Separator } from \"@godxjp/ui/layout\";\n\n// Plain section rule — decorative, nothing announced.\n<Separator />\n\n// Day divider: the label is the calendar boundary, formatted on the active locale.\n<Separator label={formatDate(\"2026-08-22\", { kind: \"long\" })} labelAlign=\"start\" />\n\n// Unread watermark: content, not decoration — announced once as the separator's name.\n<Separator label={t(\"chat.newMessages\")} tone=\"primary\" />",
|
|
3
|
+
"group": "layout",
|
|
4
|
+
"importPath": "@godxjp/ui/layout",
|
|
5
|
+
"name": "Separator",
|
|
6
|
+
"props": [
|
|
7
|
+
{
|
|
8
|
+
"description": "Hide from viewport breakpoint.",
|
|
9
|
+
"name": "hideFrom",
|
|
10
|
+
"type": "BreakpointProp"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"description": "Hide below viewport breakpoint.",
|
|
14
|
+
"name": "hideBelow",
|
|
15
|
+
"type": "BreakpointProp"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"description": "Block spacing on the token scale.",
|
|
19
|
+
"name": "space",
|
|
20
|
+
"type": "GapProp"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"description": "Label typography step.",
|
|
24
|
+
"name": "labelSize",
|
|
25
|
+
"type": "\"2xs\" | \"xs\" | \"sm\" | \"md\""
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"defaultValue": "\"horizontal\"",
|
|
29
|
+
"description": "Divider direction.",
|
|
30
|
+
"name": "orientation",
|
|
31
|
+
"type": "\"horizontal\" | \"vertical\""
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"description": "Localized text that interrupts the rule — the rule splits into two halves around it. Horizontal only (ignored with a dev warning when vertical). A string, not a node, because it becomes the separator's ACCESSIBLE NAME. Omit for a plain rule.",
|
|
35
|
+
"name": "label",
|
|
36
|
+
"type": "string"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"defaultValue": "\"center\"",
|
|
40
|
+
"description": "Where the label sits on the rule. `center` is the classic conjunction; `start` is the Slack/Mattermost stream convention. Logical — it flips under dir=\"rtl\".",
|
|
41
|
+
"name": "labelAlign",
|
|
42
|
+
"type": "\"start\" | \"center\" | \"end\""
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"defaultValue": "\"default\"",
|
|
46
|
+
"description": "Semantic emphasis of the label AND the rule together (never colour-only). `default` is the quiet chrome; use a role for an attention rule such as an unread watermark.",
|
|
47
|
+
"name": "tone",
|
|
48
|
+
"type": "\"default\" | \"muted\" | \"primary\" | \"success\" | \"warning\" | \"destructive\" | \"info\" | \"inherit\""
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"defaultValue": "true (false when `label` is set)",
|
|
52
|
+
"description": "Whether the separator is decorative for assistive tech. A `label` flips the default to false so the rule becomes a real role=\"separator\" named by the label.",
|
|
53
|
+
"name": "decorative",
|
|
54
|
+
"type": "boolean"
|
|
55
|
+
}
|
|
56
|
+
],
|
|
57
|
+
"related": [
|
|
58
|
+
"Flex direction='col' — use for vertical spacing without a visible rule.",
|
|
59
|
+
"AuthDivider — the auth-scoped preset over `Separator label` (it only re-points the --separator-* knobs at the --auth-shell-divider-* layer)."
|
|
60
|
+
],
|
|
61
|
+
"rules": [
|
|
62
|
+
2,
|
|
63
|
+
3,
|
|
64
|
+
44,
|
|
65
|
+
45
|
|
66
|
+
],
|
|
67
|
+
"storyPath": "layout/Separator.stories.tsx",
|
|
68
|
+
"tagline": "Tokenized horizontal or vertical rule, optionally INTERRUPTED by a localized label (day divider, unread watermark, auth conjunction).",
|
|
69
|
+
"usage": [
|
|
70
|
+
"DO use Separator for section dividers instead of raw border divs.",
|
|
71
|
+
"DO set orientation='vertical' only when the parent gives it a stable height.",
|
|
72
|
+
"DO use `label` for a message stream's day divider or a \"new messages\" unread watermark — it is the generic primitive AuthDivider was being misused for. Never hand-roll a <div> grid of two <span> rules.",
|
|
73
|
+
"DO format a day divider's date with the package's Intl/CLDR date subsystem (`formatDate(iso, { kind: \"long\" })` from @godxjp/ui/datetime) on the active locale — never hand-build the string.",
|
|
74
|
+
"DO pass `label` as a t() string. It becomes the separator's accessible name, and the visible node is aria-hidden so it is announced EXACTLY once.",
|
|
75
|
+
"DON'T set `decorative` on a labelled rule unless you mean it: role=\"none\" cannot carry a name, so the rule stops being announced as a milestone.",
|
|
76
|
+
"DON'T reach for AuthDivider outside an auth form — it is the auth-scoped preset over this and drags the --auth-shell-divider-* micro-scale with it.",
|
|
77
|
+
"DON'T bake the rule weight, the label gap/inset or the label type ramp into page CSS — they are --separator-* component tokens (rules #44/#45); retune them from theme.css."
|
|
78
|
+
],
|
|
79
|
+
"useCases": [
|
|
80
|
+
"Separating toolbar groups",
|
|
81
|
+
"Dividing stacked page sections",
|
|
82
|
+
"Vertical split between metadata groups",
|
|
83
|
+
"A message stream's calendar-boundary day divider",
|
|
84
|
+
"A \"new messages\" unread watermark above the first unread post"
|
|
85
|
+
]
|
|
86
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"example": "import { ServiceCatalogCta } from \"@godxjp/ui/data-display\";\nimport { Button } from \"@godxjp/ui/general\";\n\n<ServiceCatalogCta\n title={t(\"addFromCatalog\")}\n action={\n <Button asChild variant=\"outline\">\n <a href={catalogUrl}>{t(\"viewCatalog\")}</a>\n </Button>\n }\n/>",
|
|
3
|
+
"group": "data-display",
|
|
4
|
+
"importPath": "@godxjp/ui/data-display",
|
|
5
|
+
"name": "ServiceCatalogCta",
|
|
6
|
+
"props": [
|
|
7
|
+
{
|
|
8
|
+
"defaultValue": "Plus",
|
|
9
|
+
"description": "Decorative glyph above the title, rendered aria-hidden. Override only when the route is not an add/browse action.",
|
|
10
|
+
"name": "icon",
|
|
11
|
+
"type": "LucideIcon"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"description": "Localized invitation, e.g. the label of the catalog route this tile opens.",
|
|
15
|
+
"name": "title",
|
|
16
|
+
"required": true,
|
|
17
|
+
"type": "ReactNode"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"description": "The real navigation control — normally a Button, or Button asChild wrapping the catalog link. The tile itself is not clickable.",
|
|
21
|
+
"name": "action",
|
|
22
|
+
"required": true,
|
|
23
|
+
"type": "ReactNode"
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
"related": [
|
|
27
|
+
"ServiceLauncherCard — the real service tile this one accompanies; it carries status, metadata and the launch action.",
|
|
28
|
+
"ServiceLauncherCardSkeleton — the loading placeholder for the launcher tiles, documented under ServiceLauncherCard.",
|
|
29
|
+
"EmptyState — the zero-state primitive to reach for when the grid has NO services at all; this CTA is a companion to existing tiles, not an empty state."
|
|
30
|
+
],
|
|
31
|
+
"rules": [
|
|
32
|
+
40
|
|
33
|
+
],
|
|
34
|
+
"storyPath": "data-display/ServiceLauncherCard.stories.tsx",
|
|
35
|
+
"tagline": "Dashed companion tile that closes a launcher grid with the real catalog/add route — same Card geometry as ServiceLauncherCard, no status or metadata.",
|
|
36
|
+
"usage": [
|
|
37
|
+
"DO render it as the LAST child of the same ResponsiveGrid that holds the ServiceLauncherCards, so it inherits the 3→2→1 ladder and lines up with the tiles it follows.",
|
|
38
|
+
"DO give it a route that already exists. It is an invitation to a real catalog/add screen; a tile whose action goes nowhere reads as a broken service.",
|
|
39
|
+
"DON'T use it as an empty state for a grid that has no services — an empty launcher grid needs Empty, which explains the absence, not a CTA that implies there is something to add.",
|
|
40
|
+
"DON'T rebuild it as a Card with a dashed border and utility padding: the dashed surface, the medallion and the internal rhythm are token-owned (--card-service-launcher-*), same as the launcher tile."
|
|
41
|
+
],
|
|
42
|
+
"useCases": [
|
|
43
|
+
"Closing tile of an organization console launcher grid, linking to the service catalog.",
|
|
44
|
+
"Add-a-service affordance beside the subscribed applications an admin already has."
|
|
45
|
+
]
|
|
46
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
{
|
|
2
|
+
"example": "import { Clock } from \"lucide-react\";\nimport {\n ServiceCatalogCta,\n ServiceLauncherCard,\n ServiceLauncherCardSkeleton,\n} from \"@godxjp/ui/data-display\";\nimport { Button } from \"@godxjp/ui/general\";\nimport { ResponsiveGrid } from \"@godxjp/ui/layout\";\n\n// ResponsiveGrid owns the canonical 3 → 2 → 1 ladder; the page writes no tracks.\n<ResponsiveGrid columns={{ sm: 1, md: 2, lg: 3 }}>\n {loading\n ? services.map((s) => <ServiceLauncherCardSkeleton key={s.id} label={t(\"loadingService\")} />)\n : services.map((s) => (\n <ServiceLauncherCard\n key={s.id}\n icon={Clock}\n title={s.name}\n statusLabel={s.accessLabel}\n statusTone={s.accessTone}\n description={s.description}\n metadata={s.hostnameAndPlan}\n disabledReason={s.blockedReason}\n action={\n <Button asChild={s.canLaunch} disabled={!s.canLaunch}>\n {s.canLaunch ? <a href={s.launchUrl}>{t(\"launch\")}</a> : t(\"launch\")}\n </Button>\n }\n />\n ))}\n <ServiceCatalogCta\n title={t(\"addFromCatalog\")}\n action={<Button variant=\"outline\">{t(\"viewCatalog\")}</Button>}\n />\n</ResponsiveGrid>",
|
|
3
|
+
"group": "data-display",
|
|
4
|
+
"importPath": "@godxjp/ui/data-display",
|
|
5
|
+
"name": "ServiceLauncherCard",
|
|
6
|
+
"props": [
|
|
7
|
+
{
|
|
8
|
+
"description": "Decorative service glyph rendered in the canonical semantic icon surface.",
|
|
9
|
+
"name": "icon",
|
|
10
|
+
"required": true,
|
|
11
|
+
"type": "LucideIcon"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"description": "Real downstream service display name.",
|
|
15
|
+
"name": "title",
|
|
16
|
+
"required": true,
|
|
17
|
+
"type": "ReactNode"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"defaultValue": "2",
|
|
21
|
+
"description": "Semantic heading level; visual styling remains token-owned.",
|
|
22
|
+
"name": "titleLevel",
|
|
23
|
+
"type": "1 | 2 | 3 | 4"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"description": "Consumer-provided access/readiness label. The component never infers status.",
|
|
27
|
+
"name": "statusLabel",
|
|
28
|
+
"type": "ReactNode"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"defaultValue": "\"neutral\"",
|
|
32
|
+
"description": "Semantic tone corresponding to the real statusLabel.",
|
|
33
|
+
"name": "statusTone",
|
|
34
|
+
"type": "\"success\" | \"warning\" | \"destructive\" | \"info\" | \"neutral\" | \"muted\""
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"description": "Localized service summary.",
|
|
38
|
+
"name": "description",
|
|
39
|
+
"type": "ReactNode"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"description": "Compact mono metadata such as real hostname and subscribed plan.",
|
|
43
|
+
"name": "metadata",
|
|
44
|
+
"type": "ReactNode"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"description": "Real launch/detail action, normally a Button or Button asChild link.",
|
|
48
|
+
"name": "action",
|
|
49
|
+
"required": true,
|
|
50
|
+
"type": "ReactNode"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"description": "Localized prose reason accompanying a disabled/unavailable action. Rendered ABOVE the action (so assistive tech meets the explanation before the disabled control) and marks the tile data-unavailable, which mutes the medallion via --card-service-launcher-unavailable-icon-*. It never disables the action itself — that stays the consumer's Button prop.",
|
|
54
|
+
"name": "disabledReason",
|
|
55
|
+
"type": "ReactNode"
|
|
56
|
+
}
|
|
57
|
+
],
|
|
58
|
+
"related": [
|
|
59
|
+
"ServiceLauncherCardSkeleton — shape-matched initial loading placeholder (required `label`, aria-busy, no live region).",
|
|
60
|
+
"ServiceCatalogCta — dashed companion tile for an existing catalog/add route.",
|
|
61
|
+
"ResponsiveGrid — OWNS the 3→2→1 layout around launcher tiles; the launcher never ships grid tracks of its own.",
|
|
62
|
+
"Card — general-purpose surface; use ServiceLauncherCard instead for this established composite."
|
|
63
|
+
],
|
|
64
|
+
"rules": [
|
|
65
|
+
40
|
|
66
|
+
],
|
|
67
|
+
"storyPath": "data-display/ServiceLauncherCard.stories.tsx",
|
|
68
|
+
"subParts": [
|
|
69
|
+
"ServiceLauncherCardSkeleton"
|
|
70
|
+
],
|
|
71
|
+
"tagline": "Token-owned downstream-service launcher tile with semantic icon, status, metadata, action, disabled reason, matching skeleton, and companion catalog CTA.",
|
|
72
|
+
"usage": [
|
|
73
|
+
"DO provide status, hostname, plan, access state and action from the product's real API contract. ServiceLauncherCard deliberately performs no entitlement or URL inference — it has no href/entitlement/available prop at all.",
|
|
74
|
+
"DO own the layout with ResponsiveGrid columns={{ sm: 1, md: 2, lg: 3 }} — the canonical 3→2→1 launcher grid. ResponsiveGrid queries its OWN container (40/48/64rem), so never hand-write grid-template-columns or a media query in the page. The shorthand columns={3} also works but widens to 2 columns earlier (40rem).",
|
|
75
|
+
"DO render ServiceLauncherCard directly as a grid child; it already owns its Card shell, its 36px medallion (--control-height-lg tier) and the canonical internal rhythm.",
|
|
76
|
+
"DO replace it with ServiceLauncherCardSkeleton while loading (it carries a required `label` and aria-busy, and deliberately opens no live region). Use ServiceCatalogCta as the peer tile only when a real catalog/add route exists.",
|
|
77
|
+
"DO keep `metadata` to machine identifiers (hostname · plan) — it is the only mono line. Sentences belong in `description` / `disabledReason`.",
|
|
78
|
+
"DON'T recreate launcher geometry with page-local CSS, utility padding, grid tracks, or a hand-built Card hierarchy. Retune it with the --card-service-launcher-* tokens instead.",
|
|
79
|
+
"DON'T show LIVE, a hostname, subscribed plan, or launch action merely because a service is active in the global catalog."
|
|
80
|
+
],
|
|
81
|
+
"useCases": [
|
|
82
|
+
"Organization console launcher showing subscribed downstream applications with a real SSO launch action.",
|
|
83
|
+
"Service picker where unavailable apps remain visible with a disabled action and permission/subscription reason.",
|
|
84
|
+
"Responsive 3→2→1 launcher grid with a final ServiceCatalogCta tile linked to an existing catalog route."
|
|
85
|
+
]
|
|
86
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
{
|
|
2
|
+
"docPath": "layout/service-role-panel.tsx",
|
|
3
|
+
"example": "import { PermissionMatrix } from \"@godxjp/ui/data-display\";\nimport { ServiceRolePanel } from \"@godxjp/ui/layout\";\n\n<ServiceRolePanel\n roles={roles}\n value={selectedRoleId}\n onValueChange={setSelectedRoleId}\n onDeleteRole={(roleId) => deleteRole.mutate(roleId)}\n>\n {(role) =>\n role && <PermissionMatrix roles={[role]} permissions={permissions} grants={grants} readOnly={role.locked} />\n }\n</ServiceRolePanel>",
|
|
4
|
+
"group": "layout",
|
|
5
|
+
"importPath": "@godxjp/ui/layout",
|
|
6
|
+
"name": "ServiceRolePanel",
|
|
7
|
+
"props": [
|
|
8
|
+
{
|
|
9
|
+
"description": "Giá trị có kiểm soát: id vai trò đang chọn.",
|
|
10
|
+
"name": "value",
|
|
11
|
+
"type": "string"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"description": "Id vai trò khởi tạo khi không kiểm soát.",
|
|
15
|
+
"name": "defaultValue",
|
|
16
|
+
"type": "string"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"description": "Phát khi vai trò đổi.",
|
|
20
|
+
"name": "onValueChange",
|
|
21
|
+
"type": "(roleId: string) => void"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"description": "The role collection — consumer domain data. `locked` = system role: lock badge, never deletable. memberCount is CLDR-pluralized.",
|
|
25
|
+
"name": "roles",
|
|
26
|
+
"required": true,
|
|
27
|
+
"type": "{ id: string; name: string; description?: string; memberCount?: number; locked?: boolean }[]"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"description": "Controlled selection triad; defaults to the first role.",
|
|
31
|
+
"name": "value / defaultValue / onValueChange",
|
|
32
|
+
"type": "string / (roleId: string) => void"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"description": "The detail surface. A render function receives the selected role — typically renders a PermissionMatrix + role metadata.",
|
|
36
|
+
"name": "children",
|
|
37
|
+
"type": "ReactNode | (role) => ReactNode"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"description": "Its PRESENCE arms a per-role delete affordance behind the built-in destructive AlertDialog; it fires only after the user confirms. Locked roles never offer it.",
|
|
41
|
+
"name": "onDeleteRole",
|
|
42
|
+
"type": "(roleId: string) => void"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"defaultValue": "false",
|
|
46
|
+
"description": "Hide every mutating affordance.",
|
|
47
|
+
"name": "readOnly",
|
|
48
|
+
"type": "boolean"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"description": "The #216 lifecycle vocabulary (precedence loading → denied → error → empty), same semantics as DataTable/PermissionMatrix.",
|
|
52
|
+
"name": "loading / denied / error / empty / onRetry",
|
|
53
|
+
"type": "boolean | ReactNode / handler"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"description": "Forwarded to MasterDetail (localized region labels by default). Never re-derive tracks or breakpoints in the app.",
|
|
57
|
+
"name": "railWidth / masterViewport / collapseBelow / masterLabel / detailLabel",
|
|
58
|
+
"type": "MasterDetail geometry + region labels"
|
|
59
|
+
}
|
|
60
|
+
],
|
|
61
|
+
"related": [
|
|
62
|
+
"MasterDetail — the geometry underneath; use it directly for non-role collections.",
|
|
63
|
+
"PermissionMatrix — the canonical detail body for a selected role.",
|
|
64
|
+
"AlertDialog — the confirm primitive the panel embeds; use directly for other destructive flows."
|
|
65
|
+
],
|
|
66
|
+
"rules": [
|
|
67
|
+
24,
|
|
68
|
+
40
|
|
69
|
+
],
|
|
70
|
+
"storyPath": "layout/ServiceRolePanel.stories.tsx",
|
|
71
|
+
"tagline": "Geometry (1440/1024 two-track, 390 stacked) is MasterDetail's tokens.",
|
|
72
|
+
"usage": [
|
|
73
|
+
"DO compose the detail from real primitives — the panel deliberately owns NO detail layout; PermissionMatrix + Descriptions is the typical body.",
|
|
74
|
+
"DO rely on the built-in AlertDialog for deletion — never wire a bare onClick delete; the confirm gate is the contract.",
|
|
75
|
+
"DO pass masterViewport=\"compact\" for long role collections so the rail scrolls inside its region after stacking.",
|
|
76
|
+
"DO NOT nest interactive controls in a role row — the select button and the delete button are SIBLINGS by design; keep any custom row content non-interactive."
|
|
77
|
+
],
|
|
78
|
+
"useCases": [
|
|
79
|
+
"Service detail → roles tab: role list rail + permission matrix detail.",
|
|
80
|
+
"Org role management screen with locked system roles and confirmed deletion.",
|
|
81
|
+
"Read-only role browser for auditors (readOnly)."
|
|
82
|
+
]
|
|
83
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
{
|
|
2
|
+
"example": "import { Sheet, SheetTrigger, SheetContent, SheetHeader, SheetTitle } from \"@godxjp/ui/feedback\";\nimport { Button } from \"@godxjp/ui/general\";\n\n<Sheet open={open} onOpenChange={setOpen}>\n <SheetTrigger asChild><Button variant=\"outline\" size=\"sm\">絞り込み</Button></SheetTrigger>\n <SheetContent side=\"right\">\n <SheetHeader><SheetTitle>フィルター設定</SheetTitle></SheetHeader>\n {/* filter fields */}\n </SheetContent>\n</Sheet>",
|
|
3
|
+
"group": "feedback",
|
|
4
|
+
"importPath": "@godxjp/ui/feedback",
|
|
5
|
+
"name": "Sheet",
|
|
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
|
+
"defaultValue": "true",
|
|
19
|
+
"description": "On Sheet (root). `false` renders a NON-MODAL sheet (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 panel keeps role=dialog + its title as name, and the same side placement, width, responsive presentation 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). Same contract as Dialog `modal={false}`. gh#701.",
|
|
20
|
+
"name": "modal",
|
|
21
|
+
"type": "boolean"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"description": "On SheetContent (side left/right): desired panel width (number→px). Default w-3/4 sm:max-w-md.",
|
|
25
|
+
"name": "width",
|
|
26
|
+
"type": "number | string"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"defaultValue": "\"side\"",
|
|
30
|
+
"description": "On SheetContent: the responsive drawer / detail-panel contract. \"side\" (default) always renders the physical `side` you named. \"auto\" renders the desktop side panel above --sheet-responsive-breakpoint-width (48rem/768px) and the mobile BOTTOM sheet at/below it, capped by --sheet-bottom-max-height (85dvh). \"bottom\" pins the bottom-sheet presentation.",
|
|
31
|
+
"name": "responsive",
|
|
32
|
+
"type": "\"auto\" | \"side\" | \"bottom\""
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"description": "On SheetHeader (Ant-style): title (→ SheetTitle, accessible name), subtitle (→ SheetDescription), right-aligned extra actions, and a soft semantic `tone` background band. Children still supported.",
|
|
36
|
+
"name": "title / subtitle / extra / tone",
|
|
37
|
+
"type": "ReactNode / ReactNode / ReactNode / ToneProp"
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
"related": [
|
|
41
|
+
"Dialog — use Dialog (centered modal) when the action is destructive, requires full user focus, or needs a confirm/alertdialog (mode='confirm'). Use Sheet when the user benefits from seeing the page content behind the slide-over (filters, detail peek, quick-edit).",
|
|
42
|
+
"Toolbar/ToolbarGroup — use Toolbar for inline persistent filter controls above a DataTable (no overlay). Use Sheet when the filter set is large (>4 controls) or on mobile where inline controls collapse poorly.",
|
|
43
|
+
"Popover — use Popover for lightweight, anchor-positioned context menus or single-control overlays (date picker, color picker). Use Sheet when the panel has a header, multiple fields, or footer actions that need a dedicated panel.",
|
|
44
|
+
"SplitPane — use SplitPane for a persistent side-by-side layout where both panes are always visible. Use Sheet when the secondary panel is transient and should overlay the primary content."
|
|
45
|
+
],
|
|
46
|
+
"rules": [
|
|
47
|
+
3
|
|
48
|
+
],
|
|
49
|
+
"storyPath": "feedback/Sheet.stories.tsx",
|
|
50
|
+
"subParts": [
|
|
51
|
+
"SheetBody",
|
|
52
|
+
"SheetClose",
|
|
53
|
+
"SheetContent",
|
|
54
|
+
"SheetDescription",
|
|
55
|
+
"SheetFooter",
|
|
56
|
+
"SheetHeader",
|
|
57
|
+
"SheetOverlay",
|
|
58
|
+
"SheetPortal",
|
|
59
|
+
"SheetTitle",
|
|
60
|
+
"SheetTrigger"
|
|
61
|
+
],
|
|
62
|
+
"tagline": "Side-panel drawer / responsive detail panel (Radix Dialog). Parts: Sheet/SheetTrigger/SheetContent(side=right|left|top|bottom, responsive=auto|side|bottom)/SheetHeader/SheetBody/SheetTitle/SheetFooter.",
|
|
63
|
+
"usage": [
|
|
64
|
+
"DO build the panel with SheetHeader (pass `title`/`subtitle`/`extra`/`tone` OR children) > SheetBody (scrollable, ring-safe) > SheetFooter (pinned). SheetTitle is required for a11y — the `title` prop renders it for you. Never skip the title.",
|
|
65
|
+
"DO set `width` on SheetContent for a wider/narrower panel (e.g. width={480}); it caps at the viewport so small screens still get a full-width panel.",
|
|
66
|
+
"DO use responsive=\"auto\" for a record detail panel / drawer that must be a desktop side panel and a mobile bottom sheet — ONE <Sheet>, no page-local media query. The breakpoint is the --sheet-responsive-breakpoint-width token, so a service moves the line once for every overlay. `width` is ignored while the bottom presentation is active (a bottom sheet is full-bleed).",
|
|
67
|
+
"DON'T hardcode an overlay breakpoint in app code (useMediaQuery(\"(max-width: 390px)\")). If a composite must swap a desktop surface for a mobile sheet (a Popover→Sheet switcher, for example), call the exported useSheetResponsiveMode(\"auto\") hook so it reads the same themeable token.",
|
|
68
|
+
"DO use all named sub-parts in order: Sheet (root) > SheetTrigger (opener) > SheetContent (panel) > SheetHeader > SheetTitle (required for a11y — maps to Radix DialogPrimitive.Title, announced as the accessible name) > optional SheetDescription > body content > SheetFooter. Never skip SheetTitle inside an open SheetContent.",
|
|
69
|
+
"DO control state explicitly with open + onOpenChange on Sheet root when you need to close programmatically (e.g. after form submit). Uncontrolled (no props) works for simple trigger-only cases but gives you no hook to reset form state on close.",
|
|
70
|
+
"DO use SheetTrigger asChild to wrap a Button or other interactive element — this avoids a nested <button> in the DOM. Never render a raw <button> as a direct child of SheetTrigger.",
|
|
71
|
+
"DO wrap a long/scrolling body in SheetBody (between SheetHeader and a pinned SheetFooter). It is the ring-safe scroll slot: a hand-rolled <div className='overflow-y-auto'> clips the 3px focus ring of a full-width Input/Select at the scroll edges — SheetBody insets the content so the ring never clips.",
|
|
72
|
+
"DO use SheetFooter (renders at the bottom via mt-auto, symmetric 16/24 padding, full-bleed top border) for primary/cancel action Buttons. Never float action Buttons inside the body — they will not stick to the panel bottom.",
|
|
73
|
+
"DON'T set showCloseButton={false} on SheetContent unless you provide your own SheetClose element; omitting both leaves users with no keyboard-accessible close path and breaks a11y.",
|
|
74
|
+
"DO set `modal={false}` on Sheet when the user must keep working on the page behind the open panel (edit a list while a detail panel stays open). Control `open` yourself: an outside press no longer closes it, so keep the ✕ or a footer close action. Escape closes it only while focus is inside the panel.",
|
|
75
|
+
"DON'T put a Sheet inside a Dialog (nested Radix portals conflict). If you need a slide-over triggered from within a modal, close the Dialog first, then open the Sheet."
|
|
76
|
+
],
|
|
77
|
+
"useCases": [
|
|
78
|
+
"Filter/search panel: slide in from the right with filter FormFields (Select, `DatePicker range`, CheckboxGroup) that affect a DataTable — preferred over a Dialog because filters do not require confirmation and benefit from seeing the table behind the overlay.",
|
|
79
|
+
"Quick-edit drawer: open an entity's editable fields (e.g. invoice line items, account settings) without navigating away, with Save/Cancel in SheetFooter — use side='right' and keep the main page visible as context.",
|
|
80
|
+
"Detail peek panel: show read-only Descriptions / Timeline of a selected record (e.g. a journal entry or invoice) from a DataTable row click, using side='right' with showCloseButton={true}. Add responsive='auto' so the same panel becomes a bottom sheet on a phone instead of a 100%-wide slab.",
|
|
81
|
+
"Non-modal side panel: `modal={false}` keeps a list behind the sheet editable while the panel stays open (change order lines while their running total stays visible in the panel).",
|
|
82
|
+
"Mobile-first navigation drawer: side='left' sheet acting as a slide-in nav menu on small viewports when the AppShell Sidebar is hidden — triggered by a hamburger Button.",
|
|
83
|
+
"Step-by-step wizard side panel: multi-step form (Steps component inside SheetContent) for onboarding or import flows where full-page navigation would lose list context."
|
|
84
|
+
]
|
|
85
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
{
|
|
2
|
+
"example": "\n{`import { useState } from \"react\";\nimport { LayoutDashboard, FileText, Users, Shield, CreditCard, BookOpen } from \"lucide-react\";\nimport { Link } from \"react-router-dom\";\nimport { AppShell, createSidebarLink } from \"@godxjp/ui/layout\";\nimport { Sidebar, type SidebarSection } from \"@godxjp/ui/layout\";\nimport { Topbar, TopbarItem } from \"@godxjp/ui/layout\";\n\n// The WHOLE router integration: pass the element type, the library composes every row\n// (icon · label · badge · active · collapsed rail). Inertia: inertiaSidebarLink(Link) from\n// \"@godxjp/ui/inertia\". Next.js: createSidebarLink(Link).\nconst NavLink = createSidebarLink(Link, \"to\");\n\nconst sections: SidebarSection[] = [\n {\n label: \"Accounting\",\n items: [\n { id: \"dashboard\", label: \"Dashboard\", icon: LayoutDashboard, href: \"/dashboard\" },\n {\n id: \"ledger\",\n label: \"Ledger\",\n icon: BookOpen,\n children: [\n { id: \"journal\", label: \"Journal\", icon: FileText, href: \"/ledger/journal\" },\n { id: \"chart-of-accounts\", label: \"Chart of Accounts\", icon: CreditCard, href: \"/ledger/coa\" },\n ],\n },\n ],\n },\n {\n label: \"Administration\",\n items: [\n { id: \"users\", label: \"Users\", icon: Users, href: \"/users\" },\n { id: \"roles\", label: \"Roles\", icon: Shield, href: \"/roles\", disabled: true },\n ],\n },\n];\n\nexport default function Shell() {\n const [activeId, setActiveId] = useState(\"dashboard\");\n const [collapsed, setCollapsed] = useState(false);\n\n return (\n <AppShell\n sidebarCollapsed={collapsed}\n sidebar={\n <Sidebar\n activeId={activeId}\n collapsed={collapsed}\n onSelect={setActiveId}\n sections={sections}\n linkComponent={NavLink}\n product={{ name: \"CoreBooks\", role: \"Admin Console\", color: \"hsl(var(--primary))\" }}\n onProductClick={() => {/* open entity switcher */}}\n footer={\n <div className=\"text-muted-foreground text-xs\">\n <div className=\"text-foreground font-medium\">Satoshi Yamamoto</div>\n <div>Online · Tokyo branch</div>\n </div>\n }\n />\n }\n topbar={\n <Topbar\n start={\n <>\n {/* A bar cell is a TopbarItem, never a Button: a Button in a bar is a\n --control-height pill floating in a taller strip, with its own hover\n fill and its own focus ring. */}\n <TopbarItem aria-label=\"メニュー\" onClick={() => setCollapsed((c) => !c)}>\n <PanelLeft />\n </TopbarItem>\n <Logo mark=\"godx\" label=\"CoreBooks\" />\n </>\n }\n end={<TopbarItem aria-label=\"検索\" onClick={() => {}}><Search /></TopbarItem>}\n />\n }\n >\n {/* page content */}\n </AppShell>\n );\n}`}\n",
|
|
3
|
+
"group": "layout",
|
|
4
|
+
"importPath": "@godxjp/ui/layout",
|
|
5
|
+
"name": "Sidebar",
|
|
6
|
+
"props": [
|
|
7
|
+
{
|
|
8
|
+
"description": "Tên khả truy cập của landmark điều hướng. Bắt buộc khi một tài liệu có nhiều hơn một `<nav>`.",
|
|
9
|
+
"name": "ariaLabel",
|
|
10
|
+
"type": "string"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"description": "The id of the currently active nav item. For group items, the parent is automatically highlighted when any descendant id matches.",
|
|
14
|
+
"name": "activeId",
|
|
15
|
+
"required": true,
|
|
16
|
+
"type": "string"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"description": "Ordered list of nav sections. Each section has an optional string label and a required items array of SidebarItemProp. A row's count pill is `item.badge` (CONTENT ONLY — a number, a string, \"9+\"; never a <Badge> element, which would nest a pill inside the pill the row already draws) and its emphasis is `item.badgeTone`: \"neutral\" (default, the quiet unread pill) or \"destructive\" (the count is addressed to the user — an @mention, a DM, a failure waiting on them). A row's TRAILING GLYPH is a different slot: `item.trailingIcon` takes the COMPONENT (like `item.icon`, not an element) and renders a bare 16px mark at the row's inline end — the ⌃⌄ of a workspace switcher, a → on a row that leaves the app — with no pill, no background and no radius, hidden on the collapsed rail exactly like the badge.",
|
|
20
|
+
"name": "sections",
|
|
21
|
+
"required": true,
|
|
22
|
+
"type": "SidebarSectionProp[]"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"description": "Called with the item id when a leaf nav item is clicked. Not called for group triggers or disabled items.",
|
|
26
|
+
"name": "onSelect",
|
|
27
|
+
"type": "(id: string) => void"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"defaultValue": "false",
|
|
31
|
+
"description": "When true, renders the icon-only collapsed rail. Labels become Tooltips on hover; group items open a portaled flyout popover on click. Section labels are hidden.",
|
|
32
|
+
"name": "collapsed",
|
|
33
|
+
"type": "boolean"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"description": "Renders a product/app chip at the top of the sidebar (name, optional role subtitle, optional color swatch). Mutually exclusive with brand — brand takes precedence.",
|
|
37
|
+
"name": "product",
|
|
38
|
+
"type": "SidebarProductProp"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"description": "Click handler for the product chip button. Use to open an entity/workspace switcher sheet or dropdown.",
|
|
42
|
+
"name": "onProductClick",
|
|
43
|
+
"type": "() => void"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"description": "Custom brand slot rendered above the nav scroll area. When provided, the product chip is not rendered. PASS A FUNCTION and it is called with the EFFECTIVE collapsed value, which is what a plain node cannot see: AppShell reuses this same Sidebar for the mobile drawer and the drawer un-collapses the rows, so a lockup built from the consumer's own `collapsed` boolean renders glyph-only inside a full-width drawer. The workaround consumers reach for — a second hand-built Sidebar passed as AppShell's `mobileNav` — is exactly the override that switches off `railInDrawer`, silently dropping the `navRail` from mobile. This slot is APP scope (the product lockup); a PLATFORM switch does not go here.",
|
|
47
|
+
"name": "brand",
|
|
48
|
+
"type": "ReactNode | ((collapsed: boolean) => ReactNode)"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"description": "Slot pinned to the bottom of the sidebar below the scrollable nav area. Commonly used for user identity, online status, or a mode switch. PASS A FUNCTION for the same reason brand takes one, and it is the same reason twice: both slots sit INSIDE the collapsible rail, so both need the EFFECTIVE collapsed value, which a node built outside the component cannot see. Reading your own `collapsed` boolean renders a glyph-only footer inside AppShell's full-width drawer (the drawer un-collapses the rail); building a second Sidebar for AppShell's `mobileNav` is exactly the override that switches railInDrawer off; doing neither leaves the expanded footer to reflow inside a 64px rail (measured: a two-line identity block went 255x66 docked to 63x111 collapsed, wrapping the name over three lines). A plain ReactNode still works unchanged.",
|
|
52
|
+
"name": "footer",
|
|
53
|
+
"type": "ReactNode | ((collapsed: boolean) => ReactNode)"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"description": "THE framework-router contract. Supply only the link ELEMENT TYPE; the library keeps composing every row — icon slot, label, badge, data-active/aria-current, the icon-only collapsed rail and its tooltip name — and passes it to the link as SidebarLinkProp children. Applied to every row that carries an href: top-level leaves, submenu children, collapsed-rail leaves and collapsed flyout entries. Build it with createSidebarLink(Link, 'to') for React Router / TanStack, createSidebarLink(Link) or inertiaSidebarLink(Link) from @godxjp/ui/inertia for Inertia / Next.js.",
|
|
57
|
+
"name": "linkComponent",
|
|
58
|
+
"type": "SidebarLinkComponentProp"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"description": "DEPRECATED — use linkComponent, or asChild on SidebarItem. Legacy escape hatch that left row CONTENT to the caller, which is how a <Link>{item.label}</Link> silently dropped every icon and badge in production. Still supported and still wins over linkComponent; rowProps now also carries the library-composed children, so spreading rowProps (or rendering rowProps.children) restores the canonical row.",
|
|
62
|
+
"name": "renderItem",
|
|
63
|
+
"type": "(item: SidebarItemData, rowProps: SidebarRenderItemProp) => ReactNode"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"description": "Override the nav landmark's accessible name (defaults to a localized \"Main navigation\"). Required when more than one Sidebar renders at once (e.g. a docked sidebar + its mobile-drawer twin) — two nav landmarks sharing one name/role fail landmark-unique.",
|
|
67
|
+
"name": "aria-label",
|
|
68
|
+
"type": "string"
|
|
69
|
+
}
|
|
70
|
+
],
|
|
71
|
+
"related": [
|
|
72
|
+
"AppShell — the shell that hosts Sidebar in its sidebar slot and owns the sidebarCollapsed layout grid; always compose Sidebar inside AppShell, not standalone in a page.",
|
|
73
|
+
"Topbar — the horizontal bar that renders the collapse toggle (onToggleCollapsed) and its collapsed prop must mirror the sidebar's collapsed state.",
|
|
74
|
+
"PageContainer — used for page-level title/subtitle/extra/breadcrumb inside AppShell's children slot, not inside Sidebar."
|
|
75
|
+
],
|
|
76
|
+
"rules": [
|
|
77
|
+
3,
|
|
78
|
+
6,
|
|
79
|
+
23,
|
|
80
|
+
31
|
|
81
|
+
],
|
|
82
|
+
"storyPath": "layout/Sidebar.preview.tsx",
|
|
83
|
+
"subParts": [
|
|
84
|
+
"SidebarHeader",
|
|
85
|
+
"SidebarItem",
|
|
86
|
+
"SidebarSection"
|
|
87
|
+
],
|
|
88
|
+
"tagline": "Data-driven vertical nav rail with collapsible submenu groups and a collapsed icon-only mode — never build nav manually with raw buttons.",
|
|
89
|
+
"usage": [
|
|
90
|
+
"DO: Define all nav items as a SidebarSectionProp[] data structure and pass it to sections — never hand-roll nav buttons alongside or instead of the Sidebar.",
|
|
91
|
+
"DO: Add children: SidebarItemProp[] to any SidebarItemProp to create a collapsible submenu group. The group auto-opens and highlights when activeId matches any descendant. The SAME field drives NavList (gh#815), so one grouped settings nav is one NavList and one <nav> landmark.",
|
|
92
|
+
"DO: Mirror the collapsed boolean between AppShell's sidebarCollapsed prop and Sidebar's collapsed prop — they must stay in sync so the shell layout grid adjusts correctly.",
|
|
93
|
+
"DO: Use the footer prop for user info or status — it is pinned below the scroll area and does not scroll away. Pass a FUNCTION `(collapsed) => node` whenever that content has to shrink on the rail: it is called with the EFFECTIVE collapsed value, so ONE Sidebar serves both the docked rail and AppShell's drawer. Returning null for a surface draws no footer chrome at all (no top border, no padding).",
|
|
94
|
+
"DO: Put a row's trailing disclosure mark in `item.trailingIcon`, never in `item.badge`. `badge` wraps whatever it is given in the `.sb-badge` count capsule — a chevron passed there measures 36x24 with a 24x24 SVG inside, beside a 16x16 leading icon in the same 32px row. `trailingIcon` takes the component (`trailingIcon: ChevronsUpDown`) and the rail pins it to the leading icon's 16px box with no surface of its own.",
|
|
95
|
+
"DO: Give every navigable item an `item.href` and let the library render it. A plain `href` becomes a real <a> (context-menu open-in-new-tab, middle-click); with `linkComponent` that same href drives your framework router's <Link>. Either way the link IS the row and the ONLY interactive element (no nested <button>). Never put a <button>/<a> inside a default row.",
|
|
96
|
+
"DO: Wire a framework router with `linkComponent` — `createSidebarLink(Link, 'to')` (React Router / TanStack), `createSidebarLink(Link)` (Next.js), or `inertiaSidebarLink(Link)` from `@godxjp/ui/inertia`. That is the WHOLE integration: you pass the element type, the library composes the row. It threads through leaves, submenu children, the collapsed rail and the collapsed flyout. When you compose rows by hand, the same contract is `<SidebarItem item={item} asChild><RouterLink to=… /></SidebarItem>` — write NO children; the library injects the icon, label and badge. (`RouterLink` here is YOUR router's link component; this library now exports a `Link` of its own — antd's Typography.Link — which takes `href`, not `to`.)",
|
|
97
|
+
"DON'T: Use `renderItem` in new code — it is DEPRECATED. It hands you a className + active state and leaves the row CONTENT to you, so `renderItem={(item) => <RouterLink href={…}>{item.label}</RouterLink>}` renders a row with NO icon and NO badge. That is the exact production regression that motivated `linkComponent`. If you must keep it, render `rowProps.children` (the library-composed row) instead of hand-writing `.sb-icon` / `.sb-label` spans, which are internal class names and not a public contract.",
|
|
98
|
+
"DO: Rely on route-synchronized group expansion — a group OPENS automatically whenever `activeId` moves to one of its children (e.g. after a deep-link navigation), revealing the newly-active child; users can still collapse/expand manually.",
|
|
99
|
+
"DO: Theme the nav ICON and the row/label SEPARATELY with tokens — the icon reads `--sidebar-nav-icon-foreground` (+ `-hover-`/`-active-`/`-disabled-` variants) and the row/label reads `--sidebar-nav-item-foreground` (+ `-hover-`/`-disabled-`). Resting defaults are unchanged (both = `hsl(var(--muted-foreground))`, hover = `hsl(var(--foreground))`; the ACTIVE row is its own group, see below), so setting `--sidebar-nav-icon-foreground: hsl(var(--foreground))` in your theme is all it takes to get canonical darker 16px icons beside muted labels. NEVER write a page-local `.sb-nav-item svg { color: … }` rule and never re-tint `--muted-foreground` globally to fix sidebar icons.",
|
|
100
|
+
"DO: Give every RAIL item an `icon`. It is OPTIONAL on SidebarItemProp (gh#815 — NavList has no collapsed rail and routinely mixes rows), but the canonical rail collapses to icon-only, so a row without one renders an EMPTY 16px slot: the geometry and the label column survive, and the collapsed rail reads as a hole.",
|
|
101
|
+
"DO: Distinguish an UNREAD count from one ADDRESSED TO THE USER with `item.badgeTone` — 'neutral' (the default, the pill unchanged) versus 'destructive' for an @mention, a direct message or a failure awaiting them. It emits `data-tone=\"destructive\"` on the existing `.sb-badge` and swaps two colour tokens (`--sidebar-badge-destructive-background` / `-foreground`); the pill's min-width, radius, inline pad and font size are shared by both tones, so mention rows and unread rows stay aligned in the same column. Retune all four `--sidebar-badge-*` knobs in your theme rather than styling the pill.",
|
|
102
|
+
"DON'T: Reach for `item.badge` to place a GLYPH (a chevron, an arrow, a status dot). That slot is a COUNT capsule — 9999px radius, `hsl(var(--secondary))` fill, sized for digits — and it does not pin the SVG, so a lucide glyph renders at its 24px default inside a 36x24 grey pill. The row's trailing glyph slot is `item.trailingIcon`.",
|
|
103
|
+
"DON'T: Put a `<Badge>` (or anything else that draws its own pill) inside `item.badge` to colour a count — the row ALREADY wraps whatever you pass in a `.sb-badge` pill, so you get two nested pills with two borders (measured: a 37.11x19.14 `.sb-badge` wrapping a 25.11x19.14 `<Badge>`). Pass the CONTENT only (`badge: 3`, `badge: '9+'`) and say what it MEANS with `badgeTone`.",
|
|
104
|
+
"DON'T: Change icon SIZE or row geometry through these colour knobs — icon size stays `--sidebar-nav-icon-size` (16px) and row geometry stays `--sidebar-nav-item-height` / `--sidebar-nav-item-gap` / `--sidebar-nav-item-padding-x`. The active row's fill/label keep `--sidebar-item-active-background` / `--sidebar-item-active-foreground` — and since gh#651 BOTH nav depths default to the brand, not to neutral grey: the level-1 fill is `--primary` composited at `--sidebar-item-active-background-alpha` (12%, capped at 16% — above that the label drops under WCAG 2.2 SC 1.4.3) and the label is `hsl(var(--primary))` at level 1 and level 2 alike. If your theme was setting `--sidebar-item-active-background: hsl(var(--primary) / 0.12)` and `--sidebar-item-active-foreground: hsl(var(--primary))` to get that look, DELETE the block — it is the default now. The level-2 label knob used to be spelled `--sidebar-item-active-color`; that name is gone with no alias.",
|
|
105
|
+
"DON'T: Manage collapse state inside the Sidebar — it is stateless. Hoist the boolean to your shell/page state and pass it down via both AppShell.sidebarCollapsed and Sidebar.collapsed.",
|
|
106
|
+
"DON'T: Nest children more than one level deep — only top-level items can have children; grandchild items are not rendered.",
|
|
107
|
+
"DON'T: Put a PLATFORM switch in the sidebar — not an `OrgSwitcher`, not an app switcher, neither as a row nor stacked into `brand` under the product lockup. The sidebar is APP scope (this app's own sections); which organization or which app you are in survives changing app and belongs to `AppShell`'s `navRail` or to the topbar. Stacking a second lockup under the first also gives the sidebar header a different height from the topbar, which is the visible symptom people report as \"the two sides do not line up\".",
|
|
108
|
+
"DON'T: Build a second Sidebar by hand for `AppShell.mobileNav` just to un-collapse it — the drawer already un-collapses the rows on its own (`NavSurface`), and `brand` takes a function so the lockup follows too. An explicit `mobileNav` also turns off the drawer's rail strip, so a `navRail` you passed stops appearing on mobile."
|
|
109
|
+
],
|
|
110
|
+
"useCases": [
|
|
111
|
+
"Admin application shell nav with grouped sections (e.g. Operations / Fulfillment / Administration) where the sidebar can be collapsed to an icon rail for more content space.",
|
|
112
|
+
"Accounting app with a collapsible 'Ledger' group containing Journal, Chart of Accounts, and Period Close sub-pages — activeId reflects the current sub-page and the group stays open automatically.",
|
|
113
|
+
"Multi-tenant SaaS where onProductClick opens an entity/legal-entity switcher sheet and product.role shows the active tenant name beneath the product logo.",
|
|
114
|
+
"Any app using AppShell where navigation must degrade gracefully to an icon-only rail on narrow viewports or via a user toggle in the Topbar.",
|
|
115
|
+
"Apps with infrequent-access admin pages (Users, Roles, Password) grouped in a dedicated section that appears below primary operations sections.",
|
|
116
|
+
"A chat / messaging rail listing channels, where most rows carry a neutral unread count and only the channels that @mentioned the user carry `badgeTone: 'destructive'` — the rail answers \"does anything need me personally?\" at a glance, without a second pill or a hand-styled dot."
|
|
117
|
+
]
|
|
118
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"example": "import { Skeleton } from \"@godxjp/ui/feedback\";\n\n<Skeleton className=\"h-6 w-48\" />\n\n// The same component is the antd namespace:\n<Skeleton.Button size=\"sm\" />",
|
|
3
|
+
"group": "feedback",
|
|
4
|
+
"importPath": "@godxjp/ui/feedback",
|
|
5
|
+
"name": "Skeleton",
|
|
6
|
+
"props": [
|
|
7
|
+
{
|
|
8
|
+
"description": "Size and layout classes for the block.",
|
|
9
|
+
"name": "className",
|
|
10
|
+
"type": "string"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"defaultValue": "false",
|
|
14
|
+
"description": "Swap the resting pulse for a travelling sheen (Ant Design's `active`). The block already pulses without it — `active` picks the louder of the two motions, it does not turn motion on. Both stop under prefers-reduced-motion.",
|
|
15
|
+
"name": "active",
|
|
16
|
+
"type": "boolean"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"description": "Pass `false` to render `children` INSTEAD of the placeholder, so a call site can swap without a ternary. Omitting it keeps the placeholder (Ant Design's `loading || !('loading' in props)`).",
|
|
20
|
+
"name": "loading",
|
|
21
|
+
"type": "boolean"
|
|
22
|
+
}
|
|
23
|
+
],
|
|
24
|
+
"related": [
|
|
25
|
+
"SkeletonArticle",
|
|
26
|
+
"SkeletonRows",
|
|
27
|
+
"SkeletonTable",
|
|
28
|
+
"SkeletonStat",
|
|
29
|
+
"SkeletonButton",
|
|
30
|
+
"SkeletonInput",
|
|
31
|
+
"SkeletonAvatar",
|
|
32
|
+
"SkeletonNode",
|
|
33
|
+
"SkeletonImage",
|
|
34
|
+
"Activity — the ambient 'working…' mark. Skeleton is a SHAPE standing in for content that has not arrived; Activity is motion beside content that is already there."
|
|
35
|
+
],
|
|
36
|
+
"rules": [
|
|
37
|
+
3,
|
|
38
|
+
31
|
|
39
|
+
],
|
|
40
|
+
"storyPath": "feedback/Skeleton.stories.tsx",
|
|
41
|
+
"subParts": [
|
|
42
|
+
"SkeletonDetail",
|
|
43
|
+
"SkeletonStat"
|
|
44
|
+
],
|
|
45
|
+
"tagline": "Base pulsing skeleton block, and the namespace the shaped presets hang off (Skeleton.Avatar / .Button / .Input / .Node / .Image / .Article).",
|
|
46
|
+
"usage": [
|
|
47
|
+
"DO use Skeleton for a custom block when SkeletonRows/Table/Stat/Article do not match the final layout.",
|
|
48
|
+
"DO reach for a shaped preset before sizing a bare block by hand: SkeletonButton, SkeletonInput, SkeletonAvatar, SkeletonNode and SkeletonImage already carry the box of the control they stand in for, from the --control-height tier.",
|
|
49
|
+
"DON'T use a spinner overlay for skeletonable page content.",
|
|
50
|
+
"DON'T reuse Skeleton as an ambient 'something is happening' mark — it hard-codes aria-busy + aria-live because it means CONTENT IS LOADING. Use Activity for ambient motion."
|
|
51
|
+
],
|
|
52
|
+
"useCases": [
|
|
53
|
+
"Single loading line",
|
|
54
|
+
"Custom card media placeholder",
|
|
55
|
+
"Inline metadata placeholder"
|
|
56
|
+
]
|
|
57
|
+
}
|