@godxjp/ui 28.8.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/lib/variants.js +4 -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/props/vocabulary/layout.prop.d.ts +1 -1
- package/dist/styles/base.css +47 -14
- package/dist/styles/card-layout.css +6 -6
- package/dist/styles/chart-layout.css +6 -6
- package/dist/styles/control.css +41 -6
- package/dist/styles/data-display-layout.css +21 -6
- package/dist/styles/density.css +2 -0
- package/dist/styles/dialog-layout.css +4 -1
- package/dist/styles/focus-ring.css +4 -1
- package/dist/styles/layout.css +30 -3
- package/dist/styles/navigation-layout.css +3 -1
- package/dist/styles/shell-layout.css +27 -21
- package/dist/styles/table-layout.css +50 -9
- package/dist/styles/text-layout.css +94 -23
- package/dist/tokens/components/activity.css +13 -4
- package/dist/tokens/components/attachments.css +1 -1
- package/dist/tokens/components/badge.css +1 -1
- package/dist/tokens/components/card.css +28 -7
- package/dist/tokens/components/chart.css +4 -1
- package/dist/tokens/components/chat-composer.css +4 -1
- package/dist/tokens/components/control.css +69 -30
- package/dist/tokens/components/conversations.css +4 -1
- package/dist/tokens/components/data-display.css +42 -15
- package/dist/tokens/components/data-entry.css +8 -2
- package/dist/tokens/components/descriptions.css +1 -1
- package/dist/tokens/components/feedback.css +8 -5
- package/dist/tokens/components/float-button.css +8 -2
- package/dist/tokens/components/legal-document.css +12 -3
- package/dist/tokens/components/logo.css +15 -6
- package/dist/tokens/components/mega-menu.css +14 -5
- package/dist/tokens/components/navigation.css +37 -13
- package/dist/tokens/components/segmented.css +9 -2
- package/dist/tokens/components/separator.css +4 -1
- package/dist/tokens/components/shell.css +96 -31
- package/dist/tokens/components/table.css +13 -6
- package/dist/tokens/components/thought-chain.css +4 -1
- package/dist/tokens/components/toggle.css +4 -1
- package/dist/tokens/components/tree.css +1 -1
- package/dist/tokens/components/upload.css +21 -9
- package/dist/tokens/foundation.css +24 -30
- package/dist/tokens/semantic/layout.css +19 -5
- package/docs/COMPOSITION-VS-COMPONENT.md +41 -0
- package/docs/DESIGN-AUTHORITY.md +14 -0
- package/docs/DEVELOPMENT.md +81 -6
- package/docs/TOKENS.md +16 -1
- 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/case4-login.tsx +10 -2
- package/docs/showcase/case5-shift-calendar.tsx +1 -1
- package/docs/showcase/case6-agency-handy.tsx +6 -6
- package/docs/showcase/futurelastic-web.tsx +7 -9
- package/docs/showcase/marketing-page.tsx +61 -52
- package/docs/showcase/table-expandable-rows.tsx +4 -1
- package/docs/showcase/table-pagination.tsx +88 -18
- package/docs/showcase/theme-customization.tsx +25 -2
- package/package.json +8 -5
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
{
|
|
2
|
+
"example": "import { AppProvider } from \"@godxjp/ui/app\";\n\n<AppProvider defaultLocale=\"ja\" defaultTimezone=\"Asia/Tokyo\" defaultDateFormat=\"iso\" defaultTimeFormat=\"24h\">\n {children}\n</AppProvider>",
|
|
3
|
+
"group": "providers",
|
|
4
|
+
"importPath": "@godxjp/ui/app",
|
|
5
|
+
"name": "AppProvider",
|
|
6
|
+
"props": [
|
|
7
|
+
{
|
|
8
|
+
"defaultValue": "\"vi\"",
|
|
9
|
+
"description": "Initial locale.",
|
|
10
|
+
"name": "defaultLocale",
|
|
11
|
+
"type": "\"ja\" | \"en\" | \"vi\""
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"defaultValue": "\"browser\"",
|
|
15
|
+
"description": "Initial IANA timezone.",
|
|
16
|
+
"name": "defaultTimezone",
|
|
17
|
+
"type": "string | \"browser\" | \"system\""
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"defaultValue": "\"locale\"",
|
|
21
|
+
"description": "Initial date display format.",
|
|
22
|
+
"name": "defaultDateFormat",
|
|
23
|
+
"type": "\"iso\" | \"ymd\" | \"dmy\" | \"mdy\" | \"locale\""
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"defaultValue": "\"locale\"",
|
|
27
|
+
"description": "Initial clock format.",
|
|
28
|
+
"name": "defaultTimeFormat",
|
|
29
|
+
"type": "\"24h\" | \"12h\" | \"locale\""
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"defaultValue": "\"light\"",
|
|
33
|
+
"description": "Theme axis → <html data-theme>. Equal alias of the legacy .dark class. \"system\" defers to prefers-color-scheme and is followed LIVE (matchMedia listener), so <html data-theme> flips with the OS; the persisted value stays \"system\" (the CHOICE), never the resolved light/dark. Persisted; change via setTheme / <AppSettingPicker kind=\"theme\"> / <Segmented>.",
|
|
34
|
+
"name": "theme",
|
|
35
|
+
"type": "\"light\" | \"dark\" | \"system\""
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"defaultValue": "null",
|
|
39
|
+
"description": "Brand-palette axis → <html data-brand> (sets --primary/--ring/--accent). OPT-IN: null keeps the --primary your own theme.css defines. \"dxs\" is THE CANONICAL DXS PRESET and is more than a tint: it also binds the canonical hosted-identity surface contract (36px auth controls, 22.5rem auth card measure, 16px page inset / 15px below 30rem), so a DXS surface needs ZERO page CSS for auth geometry, density, insets, logo colour, divider or footer. Stylesheet-only apps (no provider) import \"@godxjp/ui/theme/dxs.canonical.css\" instead — same contract, guarded against drift by a test.",
|
|
40
|
+
"name": "brand",
|
|
41
|
+
"type": "\"brand\" | \"crm\" | \"logistics\" | \"partner\" | \"slate\" | \"dxs\" | null"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"defaultValue": "\"default\"",
|
|
45
|
+
"description": "Density axis → <html data-density>. A named preset of the global --scaling factor (compact .92 / default 1 / comfortable 1.08): every size token rescales in proportion app-wide. PageContainer density= overrides locally.",
|
|
46
|
+
"name": "density",
|
|
47
|
+
"type": "\"compact\" | \"default\" | \"comfortable\""
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"defaultValue": "null",
|
|
51
|
+
"description": "Continuous global size multiplier → inline --scaling on <html> (Radix model). Scales spacing, control/table/checkbox/switch heights, radius in proportion. null defers to the density preset; a number (e.g. 0.95) overrides it. Type is NOT scaled (separate fontSize axis).",
|
|
52
|
+
"name": "scaling",
|
|
53
|
+
"type": "number | null"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"defaultValue": "\"default\"",
|
|
57
|
+
"description": "Base type-size axis → <html data-font-size>. A preset sets --font-size-base and the whole golden scale rescales. Orthogonal to --scaling.",
|
|
58
|
+
"name": "fontSize",
|
|
59
|
+
"type": "\"sm\" | \"default\" | \"lg\""
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"defaultValue": "true",
|
|
63
|
+
"description": "Which viewer preferences survive a reload: `true` every axis, `false` none, or a LIST — `persist={[\"theme\", \"density\", \"fontSize\"]}`. The list exists because THE AXES DO NOT SHARE AN OWNER. theme/brand/density/fontSize/scaling are the VIEWER's and belong in this browser; locale/timezone/timeFormat/dateFormat are frequently the SERVER's, resolved per request from a cookie, an account row or a header — and a stored copy then WINS over the value the server just sent, because storage is read after the props. Given one all-or-nothing flag, that consumer sets `persist={false}` and loses the viewer's theme along with it (measured: a hosted Inertia app whose theme toggle reset on every full page load). Naming the axes keeps both owners.",
|
|
64
|
+
"name": "persist",
|
|
65
|
+
"type": "boolean | readonly AppPreferenceAxis[]"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"defaultValue": "false",
|
|
69
|
+
"description": "OFF by default and deliberately opt-in: `name` decides what a native <form> submit sends, so switching it on for every consumer of a shared package would make apps start posting new keys to their backend on a library upgrade. Turn it on for apps that need native form posts or a screen-automation (RPA) contract on their controls. The inert `data-field` companion attribute is emitted regardless; a `name` written on the control itself always wins. NOT persisted to localStorage: it is the app's configuration, not a user choice.",
|
|
70
|
+
"name": "emitFieldNames",
|
|
71
|
+
"type": "boolean"
|
|
72
|
+
}
|
|
73
|
+
],
|
|
74
|
+
"related": [
|
|
75
|
+
"LocalePicker — the language-selector control that reads/writes AppProvider locale context automatically when used as a zero-prop child. Prefer LocalePicker over calling setLocale from useAppContext() directly in UI.",
|
|
76
|
+
"TimezonePicker — the timezone-selector control; inherits `timezoneOptions` from AppProvider context when its own `options` prop is omitted. Both pickers require AppProvider to be in the tree unless controlled props are passed.",
|
|
77
|
+
"formatDate — the MANDATORY date/time formatter that reads locale, timezone, timeFormat, and dateFormat from AppProvider context. Do NOT call date-fns or Intl.DateTimeFormat directly; formatDate is the single source of truth for display.",
|
|
78
|
+
"AppShell — the top-level application shell that composes AppProvider, AppShell, Sidebar, and Topbar into a single ready-to-use layout. If your project uses AppShell, AppProvider is already mounted inside it — do not add a second one.",
|
|
79
|
+
"OverlayPortalProvider — the other root-level provider, and the one to reach for when this library is mounted inside a SHADOW ROOT. Every overlay here (Popover, Dialog, Sheet, Tooltip, DropdownMenu, HoverCard) portals to `document.body` by default, which is correct on an ordinary page and wrong in a shadow root: the panel lands outside the tree carrying the stylesheet and renders with none of it — measured on an embedded bar as border 0, radius 0, a transparent background, and the anchor maths 40px off. `<OverlayPortalProvider container={shadowRoot}>` moves all of them at once; a per-component prop cannot, because a component that owns its own overlay (AppLauncher) is unreachable from the outside."
|
|
80
|
+
],
|
|
81
|
+
"rules": [
|
|
82
|
+
5
|
|
83
|
+
],
|
|
84
|
+
"storyPath": "app/AppProvider.stories.tsx",
|
|
85
|
+
"tagline": "Root locale/timezone/date-time context — wrap the app ONCE. All pickers + formatDate read from it. Import from @godxjp/ui/app.",
|
|
86
|
+
"usage": [
|
|
87
|
+
"DO drive the four theme axes (theme / brand / density / fontSize) from AppProvider props ONLY — they are written to <html data-*> and read by every component via tokens. Never hand-set --font-size-base or .ui-density-* in app CSS; that bypasses persistence + the runtime switchers. For runtime switching mount `<AppSettingPicker kind=\"density\" | \"fontSize\" | \"theme\" | \"brand\" >` or call setDensity/setFontSize/setTheme/setBrand from useAppContext().",
|
|
88
|
+
"DO mount AppProvider ONCE at the application root (e.g. in app.tsx or the Inertia layout), wrapping ALL children — every godx-ui picker (LocalePicker, TimezonePicker, DateFormatPicker, TimeFormatPicker), every formatDate call, and the Toaster all rely on the single context it provides. Nesting two AppProviders creates split contexts; inner pickers silently read the wrong one.",
|
|
89
|
+
"DO NOT omit AppProvider and then try to use LocalePicker, TimezonePicker, or formatDate standalone — useAppContext() throws 'useAppContext must be used within <AppProvider>' at runtime. The only exception is using those pickers in fully controlled mode (value + onChange) which reads useOptionalAppContext() and returns null safely.",
|
|
90
|
+
"DO use `persist={false}` on AppProvider for isolated tests and standalone settings forms where localStorage must not be read or written. With the default `persist={true}` the provider reads localStorage key `godxjp.app` on mount (after first render), so initial state may differ between SSR and client.",
|
|
91
|
+
"DO NOT reach for `persist={false}` because the SERVER owns the locale — pass the axes the BROWSER owns instead: `persist={[\"theme\", \"brand\", \"density\", \"fontSize\", \"scaling\"]}`. Storage is read after the props, so a stored `locale` overrides the one the server just resolved; turning persistence off wholesale fixes that and silently takes the viewer's theme with it, which is how a hosted app shipped a theme toggle that reset on every reload.",
|
|
92
|
+
"DO set `defaultTimezone='system'` together with `systemTimezone={serverTimezone}` when your backend knows the legal entity's canonical timezone (e.g. 'Asia/Ho_Chi_Minh'). Use `defaultTimezone='browser'` (the default) only when you want the user's browser clock. Do NOT pass a raw IANA string to `defaultTimezone` if the user may be in a different zone — use the named aliases.",
|
|
93
|
+
"DO wire `onLocaleChange`, `onTimezoneChange`, `onTimeFormatChange`, `onDateFormatChange` to persist changes server-side (e.g. patch user profile via Inertia router) in addition to the automatic localStorage write. These callbacks fire after state is set, so the new value is already reflected in context.",
|
|
94
|
+
"DO set `emitFieldNames` on AppProvider when the app is driven by screen automation (RPA) or posts native forms — every control under a FormField then carries a real `name` taken from the field key, and legacy automation that addressed controls by `name` keeps working after a rewrite. Leave it off (the default) otherwise: it changes what a native submit sends. The `data-field` attribute is emitted either way, so e2e selectors do not depend on this flag.",
|
|
95
|
+
"DO restrict the timezone dropdown by passing `timezoneOptions={APP_TIMEZONE_PRESET}` (an exported constant) to AppProvider — all TimezonePicker instances that omit their own `options` prop will inherit this restricted list automatically from context. Without it, TimezonePicker renders the full IANA list (~600 entries)."
|
|
96
|
+
],
|
|
97
|
+
"useCases": [
|
|
98
|
+
"App bootstrap in a multi-locale SaaS admin (ja/en/vi) — mount AppProvider at the root with the tenant's preferred locale and IANA timezone so every DataTable date column, every formatDate call, and every picker renders consistently in the user's locale without any per-component configuration.",
|
|
99
|
+
"User settings page — render LocalePicker, TimezonePicker, DateFormatPicker, and TimeFormatPicker as zero-prop children inside the existing AppProvider; each picker reads and writes context automatically. Wire `onLocaleChange` to an Inertia form submit to persist the change to the server profile.",
|
|
100
|
+
"Server-rendered Inertia app with SSR hydration — pass `defaultTimezone='system'` and `systemTimezone={sharedProps.timezone}` (injected via HandleInertiaRequests) so the initial render is timezone-deterministic and avoids hydration mismatches caused by browser-timezone detection.",
|
|
101
|
+
"Multi-entity accounting dashboard — use `timezoneOptions` to restrict the picker to the legal entity's permissible zones (e.g. Southeast Asian IANA ids only), preventing users from accidentally switching to an out-of-scope timezone that would misrepresent transaction timestamps.",
|
|
102
|
+
"Isolated preview / Storybook story — wrap a single component in `<AppProvider persist={false} defaultLocale='en'>` to give it a stable context without polluting localStorage between stories.",
|
|
103
|
+
"Test harness — wrap the component under test in `<AppProvider persist={false} defaultLocale='ja' defaultDateFormat='iso'>` to assert locale-sensitive formatting output deterministically, independent of whatever the browser or stored preferences report."
|
|
104
|
+
]
|
|
105
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
{
|
|
2
|
+
"absorbed": [
|
|
3
|
+
"LocalePicker",
|
|
4
|
+
"LanguagePicker",
|
|
5
|
+
"TimezonePicker",
|
|
6
|
+
"DateFormatPicker",
|
|
7
|
+
"TimeFormatPicker",
|
|
8
|
+
"ThemePicker",
|
|
9
|
+
"DensityPicker"
|
|
10
|
+
],
|
|
11
|
+
"example": "{`// Uncontrolled — AppProvider manages and persists every setting\nimport { AppProvider } from \"@godxjp/ui/app\";\nimport { AppSettingPicker } from \"@godxjp/ui/navigation\";\n\nexport function SettingsPanel() {\n return (\n <AppProvider defaultLocale=\"ja\" defaultTimezone=\"Asia/Tokyo\">\n <AppSettingPicker kind=\"locale\" />\n <AppSettingPicker kind=\"timezone\" />\n <AppSettingPicker kind=\"dateFormat\" />\n <AppSettingPicker kind=\"timeFormat\" />\n </AppProvider>\n );\n}\n\n// Controlled — no AppProvider required\nimport { useState } from \"react\";\nimport { AppSettingPicker } from \"@godxjp/ui/navigation\";\n\nexport function LocaleField() {\n const [locale, setLocale] = useState(\"en\");\n return <AppSettingPicker kind=\"locale\" value={locale} onValueChange={setLocale} />;\n}\n\n// Topbar locale switcher (globe) — a cell OF the bar, no CSS overrides\nimport { Topbar } from \"@godxjp/ui/layout\";\nimport { AppSettingPicker } from \"@godxjp/ui/navigation\";\n\nexport function TopbarLocale() {\n // \"bar\" inside a Topbar slot; \"icon\" everywhere else.\n return <Topbar end={<AppSettingPicker kind=\"locale\" appearance=\"bar\" />} />;\n}`}",
|
|
12
|
+
"group": "navigation",
|
|
13
|
+
"importPath": "@godxjp/ui/navigation",
|
|
14
|
+
"name": "AppSettingPicker",
|
|
15
|
+
"props": [
|
|
16
|
+
{
|
|
17
|
+
"description": "Which AppProvider setting this picker reads and writes. Determines the option list, icon, trigger width, and the context value/setter used. The theme-axis kinds (theme/brand/density/fontSize) write <html data-*>; brand's first option opts out (null → app token).",
|
|
18
|
+
"name": "kind",
|
|
19
|
+
"type": "\"locale\" | \"timezone\" | \"dateFormat\" | \"timeFormat\" | \"theme\" | \"brand\" | \"density\" | \"fontSize\""
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"description": "Controlled value for the chosen kind. When omitted, reads the current value from AppProvider context for that kind.",
|
|
23
|
+
"name": "value",
|
|
24
|
+
"type": "string"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"description": "Controlled change handler. When omitted, calls the matching AppProvider setter (setLocale/setTimezone/setDateFormat/setTimeFormat). Required together with value when no AppProvider is present.",
|
|
28
|
+
"name": "onValueChange",
|
|
29
|
+
"type": "(value: string) => void"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"description": "Trigger presentation. \"icon\" is the supported icon-only topbar trigger (e.g. a globe locale switcher): it structurally drops the value text and the picker's owned width and hides the chevron, squares the box to the density-aware --control-height tap target (≥44px on touch), and always keeps the localized aria-label so it can never ship nameless. \"bar\" is the SAME structural drops re-shaped as a cell OF the bar rather than a control in it — it fills the bar height and squares its corners (--topbar-item-radius, the knob TopbarItem uses), so the hover surface paints the whole strip. Reach for \"bar\" inside a Topbar slot or AppShell's bar and \"icon\" everywhere else: \"icon\" in a taller bar leaves a --control-height pill floating mid-strip, which reads as a different control family from the bar's own chrome. \"inline\" renders the selected value as a chrome-less text trigger for a legal/auth footer (no border, no box). DEFAULT IS KIND-DEPENDENT: kind=\"locale\" defaults to \"icon\" (its product contract is the compact language switcher); every other kind defaults to \"labeled\".",
|
|
33
|
+
"name": "appearance",
|
|
34
|
+
"type": "\"labeled\" | \"icon\" | \"bar\" | \"inline\""
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"defaultValue": "false",
|
|
38
|
+
"description": "Compact trigger density: re-tiers the box to the official --control-height-sm tier and DROPS the picker's owned per-kind width, so a labelled trigger hugs its value. This is the supported auth/legal-footer locale switch — `<AppSettingPicker kind=\"locale\" appearance=\"labeled\" compact />` — when the square icon-only default reads as a stray button but the full labelled trigger is too tall. All geometry is tokenized (--app-setting-picker-compact-{control-height,padding-x,gap,font-size}); the accessible name and the visible value are both preserved. No effect on appearance=\"inline\", which is already chrome-less.",
|
|
39
|
+
"name": "compact",
|
|
40
|
+
"type": "boolean"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"description": "Extra CSS classes merged onto the SelectTrigger.",
|
|
44
|
+
"name": "className",
|
|
45
|
+
"type": "string"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"description": "Disables the Select control.",
|
|
49
|
+
"name": "disabled",
|
|
50
|
+
"type": "boolean"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"description": "HTML id forwarded to the SelectTrigger for label association.",
|
|
54
|
+
"name": "id",
|
|
55
|
+
"type": "string"
|
|
56
|
+
}
|
|
57
|
+
],
|
|
58
|
+
"related": [
|
|
59
|
+
"AppProvider — required peer unless fully controlled. Supplies locale/timezone/dateFormat/timeFormat plus their setters and the i18n context.",
|
|
60
|
+
"Select — the data-entry primitive AppSettingPicker is built on; reach for Select directly for any non-AppProvider dropdown.",
|
|
61
|
+
"formatDate — reads the same AppProvider date/time context that kind='dateFormat'/'timeFormat' write to."
|
|
62
|
+
],
|
|
63
|
+
"rules": [
|
|
64
|
+
3,
|
|
65
|
+
5,
|
|
66
|
+
6,
|
|
67
|
+
23
|
|
68
|
+
],
|
|
69
|
+
"storyPath": "navigation/AppSettingPicker.stories.tsx",
|
|
70
|
+
"tagline": "One provider-bound Select for a single AppProvider setting, chosen by `kind` (locale | timezone | dateFormat | timeFormat | theme | brand | density | fontSize) — covers locale/format AND the four theme axes. Throws if used without AppProvider AND without controlled value+onValueChange.",
|
|
71
|
+
"usage": [
|
|
72
|
+
"DO: Mount inside <AppProvider> for zero-config use — the picker reads and writes the context value named by kind, no value/onValueChange needed.",
|
|
73
|
+
"DO: Use controlled mode (value + onValueChange) when managing state outside AppProvider, e.g. a standalone settings form or a Storybook story. Both are required together in this mode.",
|
|
74
|
+
"DO NOT: Render without AppProvider and without both controlled props — it throws 'AppSettingPicker requires <AppProvider> or controlled value + onValueChange'.",
|
|
75
|
+
"DO: Render four instances with different kind values to build a full preferences panel; they all share the same AppProvider context and stay in sync.",
|
|
76
|
+
"DO: For an icon-only topbar utility (a globe language switcher), pass appearance=\"icon\" — the supported compact trigger. NEVER hand-roll it by hiding the value/width with descendant-selector CSS.",
|
|
77
|
+
"DO: For an AUTH/LEGAL FOOTER locale switch, pass appearance=\"labeled\" compact — a small, content-hugging labelled trigger (readable language name, --control-height-sm box). Use appearance=\"inline\" instead when the footer wants no control chrome at all. NEVER re-size the trigger with a page-local height/width class.",
|
|
78
|
+
"DON'T hand-roll a locale/timezone/format Select — AppSettingPicker already composes Select + the right icon + translated, context-wired options. There is no separate LocalePicker/TimezonePicker/DateFormatPicker/TimeFormatPicker anymore; use kind."
|
|
79
|
+
],
|
|
80
|
+
"useCases": [
|
|
81
|
+
"App-shell top-nav language switcher: <AppSettingPicker kind=\"locale\" /> under AppProvider, persisting to localStorage with no extra state.",
|
|
82
|
+
"Topbar locale switcher (globe): <AppSettingPicker kind=\"locale\" appearance=\"bar\" /> in a Topbar `end` slot — a CELL of the bar: full bar height, squared to --topbar-item-radius, so its hover surface matches the TopbarItem beside it. appearance=\"icon\" is the same structural drops shaped as a CONTROL, for a toolbar or card header; in a taller bar it leaves a --control-height pill floating mid-strip and reads as a foreign control family. This line said \"icon\" while the prop doc said \"bar\", and a consumer duly shipped the pill.",
|
|
83
|
+
"Auth-footer locale switch: <AppSettingPicker kind=\"locale\" appearance=\"labeled\" compact /> inside an <AuthFooter locale={…}> slot — the readable language name at the small control tier, hugging its value.",
|
|
84
|
+
"User settings page with all four preferences — render kind=locale, kind=timezone, kind=dateFormat, kind=timeFormat together under one AppProvider.",
|
|
85
|
+
"Onboarding step that picks language/timezone before the rest of the app is configured — AppProvider persist={false} + controlled values to keep state local.",
|
|
86
|
+
"Storybook/test harness without AppProvider — fully controlled: <AppSettingPicker kind=\"timeFormat\" value=\"24h\" onValueChange={fn} />."
|
|
87
|
+
]
|
|
88
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"example": "{`import { AppProvider } from \"@godxjp/ui/app\";\nimport { Topbar } from \"@godxjp/ui/layout\";\nimport { AppSettingToggle } from \"@godxjp/ui/navigation\";\n\n// Context-bound: one tap steps light -> dark -> system -> light.\nexport function AppBar() {\n return (\n <AppProvider>\n <Topbar end={<AppSettingToggle kind=\"theme\" />} />\n </AppProvider>\n );\n}\n\n// Controlled - no AppProvider required.\nimport { useState } from \"react\";\n\nexport function ThemeField() {\n const [theme, setTheme] = useState(\"light\");\n return <AppSettingToggle kind=\"theme\" appearance=\"icon\" value={theme} onValueChange={setTheme} />;\n}`}",
|
|
3
|
+
"group": "navigation",
|
|
4
|
+
"importPath": "@godxjp/ui/navigation",
|
|
5
|
+
"name": "AppSettingToggle",
|
|
6
|
+
"props": [
|
|
7
|
+
{
|
|
8
|
+
"description": "Which AppProvider setting this button cycles. Deliberately the CLOSED-value-set subset of AppSettingKind — locale/timezone/dateFormat/brand are absent because a long or service-extensible list is a menu, not a cycle; reach for AppSettingPicker there. The cycle order is the SAME list AppSettingPicker offers for that kind (APP_THEMES / APP_DENSITIES / APP_FONT_SIZES / APP_TIME_FORMAT_OPTIONS), read from those constants rather than copied, so the two controls can never drift.",
|
|
9
|
+
"name": "kind",
|
|
10
|
+
"type": "\"theme\" | \"density\" | \"fontSize\" | \"timeFormat\""
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"defaultValue": "\"bar\"",
|
|
14
|
+
"description": "The BOX the button takes; there is no labeled/inline form because there is no menu to label. \"bar\" (default) renders a TopbarItem — a CELL of the bar: it stretches to the full bar height (AppShell grid row, --topbar-height, or the coarse-pointer bar), squares its corners to --topbar-item-radius, and paints the bar's own hover surface across the whole strip. It emits NO height of its own, deliberately: a length here would freeze a --control-height pill inside a taller bar, which is exactly the mismatch it exists to remove. \"icon\" is a square --control-height ghost Button for everywhere that is NOT a bar (a settings row, a card header); the kinds that show value TEXT (timeFormat) take the small labelled tier instead of a square that would clip them.",
|
|
15
|
+
"name": "appearance",
|
|
16
|
+
"type": "\"bar\" | \"icon\""
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"description": "Controlled value for the chosen kind. Omit to read the current value from AppProvider context.",
|
|
20
|
+
"name": "value",
|
|
21
|
+
"type": "string"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"description": "Controlled change handler, called with the NEXT value in the cycle. Omit to call the matching AppProvider setter (setTheme/setDensity/setFontSize/setTimeFormat). Required together with value when no AppProvider is present.",
|
|
25
|
+
"name": "onValueChange",
|
|
26
|
+
"type": "(value: string) => void"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"description": "Extra CSS classes merged onto the button.",
|
|
30
|
+
"name": "className",
|
|
31
|
+
"type": "string"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"description": "Disables the button.",
|
|
35
|
+
"name": "disabled",
|
|
36
|
+
"type": "boolean"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"description": "HTML id forwarded to the button.",
|
|
40
|
+
"name": "id",
|
|
41
|
+
"type": "string"
|
|
42
|
+
}
|
|
43
|
+
],
|
|
44
|
+
"related": [
|
|
45
|
+
"AppSettingPicker — the same settings as a Select. Use it when the value list is long (locale, timezone) or when the user must SEE the options before choosing.",
|
|
46
|
+
"TopbarItem — the bar-cell shape appearance=\"bar\" renders; use it directly for your own bar triggers.",
|
|
47
|
+
"AppProvider — required peer unless fully controlled; supplies the value and the setter for each kind."
|
|
48
|
+
],
|
|
49
|
+
"rules": [
|
|
50
|
+
3,
|
|
51
|
+
5,
|
|
52
|
+
6,
|
|
53
|
+
23
|
|
54
|
+
],
|
|
55
|
+
"storyPath": "navigation/AppSettingToggle.stories.tsx",
|
|
56
|
+
"tagline": "ONE button that steps a single AppProvider setting to its NEXT value and shows that value as its glyph — theme (Sun/Moon/Monitor), density, fontSize, timeFormat. The no-menu counterpart to AppSettingPicker: same binding contract, same option order, one tap instead of open-then-choose. Renders disabled (never throws) outside AppProvider when uncontrolled.",
|
|
57
|
+
"usage": [
|
|
58
|
+
"DO: Reach for it in a top bar when the value set is closed and short — <AppSettingToggle kind=\"theme\" /> is the light/dark/system switcher, and a dropdown for three values is a menu nobody wanted to open.",
|
|
59
|
+
"DO: Trust the accessible name — it always names BOTH the setting and the current value (\"Theme: Dark\"), because the glyph is the only visible state. Never override it with a kind-only aria-label.",
|
|
60
|
+
"DON'T: Set a height, a radius or a background on it in a bar. The bar cell shape is TopbarItem's, and any utility you add outranks @layer components and re-creates the floating-pill defect.",
|
|
61
|
+
"DON'T: Reach for it for locale, timezone, dateFormat or brand — those are not in `kind` on purpose. Use AppSettingPicker.",
|
|
62
|
+
"DON'T: Hand-roll a theme button with useAppContext + a Sun/Moon ternary — that loses the localized value-bearing name, the shared option order, and the bar-cell shape."
|
|
63
|
+
],
|
|
64
|
+
"useCases": [
|
|
65
|
+
"Topbar light/dark/system switcher: <AppSettingToggle kind=\"theme\" /> in a Topbar `end` slot, beside the other TopbarItem cells.",
|
|
66
|
+
"Density or font-size step-through in an admin bar, for users who resize the grid all day: <AppSettingToggle kind=\"density\" />.",
|
|
67
|
+
"Clock-format flip (24h/12h) next to a schedule view: <AppSettingToggle kind=\"timeFormat\" /> — the only kind that shows its value as text, since no glyph can say \"24-hour\".",
|
|
68
|
+
"Settings row outside a bar: <AppSettingToggle kind=\"theme\" appearance=\"icon\" /> beside its label."
|
|
69
|
+
]
|
|
70
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
{
|
|
2
|
+
"example": "import { AppShell, Sidebar } from \"@godxjp/ui/layout\";\nimport { LayoutDashboard, Users } from \"lucide-react\";\nimport { router } from \"@inertiajs/react\";\n\nconst sidebar = (\n <Sidebar\n activeId=\"/dashboard\"\n onSelect={(id) => router.visit(id)}\n sections={[{ items: [\n { id: \"/dashboard\", label: \"ダッシュボード\", icon: LayoutDashboard },\n { id: \"/users\", label: \"ユーザー\", icon: Users },\n ] }]}\n product={{ name: \"JOVY CRM\", role: \"本部\", color: \"var(--color-primary)\" }}\n />\n);\n\nexport function CrmLayout({ children }: { content: React.ReactNode }) {\n return <AppShell sidebar={sidebar}>{children}</AppShell>;\n}",
|
|
3
|
+
"group": "layout",
|
|
4
|
+
"importPath": "@godxjp/ui/layout",
|
|
5
|
+
"name": "AppShell",
|
|
6
|
+
"props": [
|
|
7
|
+
{
|
|
8
|
+
"description": "Optional sidebar node, typically a Sidebar. Omit or pass null/false to remove its landmark and grid track. Logo remains in the topbar; navRail is independent.",
|
|
9
|
+
"name": "sidebar",
|
|
10
|
+
"type": "ReactNode"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"description": "Main page content rendered in <main>.",
|
|
14
|
+
"name": "children",
|
|
15
|
+
"required": true,
|
|
16
|
+
"type": "ReactNode"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"description": "Full topbar override; else a rail is built from topbarLeft/topbarRight/logo. Omit ALL FOUR bar slots (topbar, topbarLeft, topbarRight, logo) and there is NO `<header class=\"app-topbar\">` in the DOM at all: the shell publishes `data-topbar=\"none\"` on its root and the bar's grid row collapses to zero, for a shell whose PAGE owns the top row. The one exception is the AppShell-owned drawer trigger — at or below 900px the header comes back carrying the hamburger alone, so navigation is never unreachable.",
|
|
20
|
+
"name": "topbar",
|
|
21
|
+
"type": "ReactNode"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"description": "Right slot of the auto-built topbar rail (user menu, switcher).",
|
|
25
|
+
"name": "topbarRight",
|
|
26
|
+
"type": "ReactNode"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"description": "Left slot of the auto-built topbar rail.",
|
|
30
|
+
"name": "topbarLeft",
|
|
31
|
+
"type": "ReactNode"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"description": "The shell's brand lockup. ALWAYS rendered — unlike topbarLeft/topbarRight it survives a custom `topbar`, because identity belongs to the frame, not to the bar's contents. WHERE it lands follows `topbarSpan`, the axis that already says who owns the top-left corner: `content` puts it at the sidebar's head, aligned to that track; `full` puts it in the bar beside the space-level chrome. Do not place it yourself in `Sidebar.brand` or a `Topbar` slot — that pins it to one arrangement while the axis moves the rest of the shell.",
|
|
35
|
+
"name": "logo",
|
|
36
|
+
"type": "ReactNode"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"description": "The brand node a NARROW bar gets instead of `logo` — a mark without the wordmark, a shorter lockup, a different viewBox (gh#728). The brand cell already shrinks without it: below the sm step it is capped at --app-shell-brand-compact-max-inline-size (the bar's own height) and anything past the cap is cropped from the inline-end, which is all a stylesheet can do to artwork it did not author — viewBox is an ATTRIBUTE, which is why consumers were re-cropping their own SVG through a media-query hook. Pass a node here to CHOOSE what the narrow bar shows. Both nodes are rendered and the breakpoint drops one with display:none, so exactly one brand is ever in the accessibility tree. Applies to the brand IN THE BAR (topbarSpan=\"full\", or a shell with no sidebar); under the default content span the brand sits at the rail's head, which the drawer has already replaced at that width.",
|
|
40
|
+
"name": "logoCompact",
|
|
41
|
+
"type": "ReactNode"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"defaultValue": "\"sm\"",
|
|
45
|
+
"description": "The step at which `logoCompact` takes over — Flex hideBelow's vocabulary on the same canonical scale (sm 40rem · md 48rem · lg 64rem · xl 80rem). Ignored without `logoCompact` (gh#728).",
|
|
46
|
+
"name": "logoCompactBelow",
|
|
47
|
+
"type": "BreakpointProp"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"defaultValue": "false",
|
|
51
|
+
"description": "Collapse the sidebar to icon-only mode.",
|
|
52
|
+
"name": "sidebarCollapsed",
|
|
53
|
+
"type": "boolean"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"defaultValue": "\"drawer\"",
|
|
57
|
+
"description": "Navigation strategy below the canonical 900px breakpoint. drawer exposes the accessible mobile Sheet; docked retains the token-sized sidebar grid track and suppresses the redundant drawer trigger.",
|
|
58
|
+
"name": "responsiveNavigation",
|
|
59
|
+
"type": "\"drawer\" | \"docked\""
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"defaultValue": "\"content\"",
|
|
63
|
+
"description": "Which columns the topbar spans. content starts it beside the sidebar, so the rail runs the full window height and the bar sits over the content only. full runs the bar edge to edge with the rail beneath it, for a bar carrying space-level chrome (global search, account, notifications) that outranks the current section. full also renders the header before the aside so keyboard order follows the visual order.",
|
|
64
|
+
"name": "topbarSpan",
|
|
65
|
+
"type": "\"content\" | \"full\""
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"description": "A SECOND navigation column, narrower than `sidebar` and placed before it — the workspace/organization switcher shape (Slack, Teams, Discord): rail → sidebar → content. THE THREE COLUMNS ARE THREE SCOPES, and that is what decides where a control goes. The rail is PLATFORM scope: what is true across every app in the organization — which organization, which app, notifications, messages, events, organization settings, cross-app shortcuts. The sidebar is APP scope: this app's own sections, channels, routes. The topbar is PAGE scope: where you are and what you can do here. App navigation never goes in the rail and a platform switch never goes in the sidebar; a destination that would fit both belongs to the rail, because it survives changing apps. A rail repeating the sidebar's own entries is a second chrome band carrying the first one's rank, just vertical. Passing a node adds the grid track and publishes `data-nav-rail` on the root; omitting it leaves the two-column shell unchanged. ONE THICKNESS, BOTH AXES: `--app-shell-nav-rail-width` (2.5rem) and `--app-shell-nav-rail-height`, which resolves to it, so the rail is the same measure on every edge and a service retunes both with one line. Deliberately not the collapsed sidebar's 4rem — at equal widths the two nav tracks fuse into one block when the sidebar collapses. THE RAIL SIZES ITS OWN CELLS through `--app-shell-nav-rail-item-size` (2.25rem): a control carries its own band token, so narrowing the TRACK alone does not narrow it, it CLIPS it (the rail clips) — the same relationship the bar has with TopbarItem, which stretches to the bar rather than the other way round. `@media (pointer: coarse)` lifts the cell to the 44px tap floor of rule #24 and the track with it. Fully orthogonal to `topbarSpan` — the rail says how many navigation COLUMNS exist, `topbarSpan` says how far the BAR reaches, and all four combinations are supported. `sidebarCollapsed` folds the sidebar track only; the rail keeps its width (the Slack behaviour). Rendered as its own `complementary` landmark, and its content is added to the mobile drawer automatically.",
|
|
69
|
+
"name": "navRail",
|
|
70
|
+
"type": "ReactNode"
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"defaultValue": "\"start\"",
|
|
74
|
+
"description": "WHICH EDGE the rail sits on. `start` (default) and `end` are the INLINE edges — logical, so an RTL document mirrors them with no `[dir]` rule, because grid columns lay out in the inline direction. `top` and `bottom` are the BLOCK edges: the rail becomes a full-measure horizontal strip and the shell grows a ROW instead of a column, which is the phone tab-bar shape at the bottom and a platform band above the app's own bar at the top. THE SCOPE CONTRACT DOES NOT MOVE WITH IT — wherever it sits, the rail is platform scope and the sidebar is app scope; the edge is a presentation choice, not a re-ranking. Thickness follows the orientation (`--app-shell-nav-rail-width` as a column, `--app-shell-nav-rail-height` as a strip), and `sidebarCollapsed` folds the sidebar track only at every position.",
|
|
75
|
+
"name": "navRailPosition",
|
|
76
|
+
"type": "\"start\" | \"end\" | \"top\" | \"bottom\""
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"description": "Rail content pinned to its FAR end — the counterpart of Sidebar's `footer`, and the tray end of a taskbar: appearance, settings, the account glyph. It follows the orientation, so it is the bottom of a column and the inline-end of a strip, and it stays put while `navRail` scrolls. A SLOT rather than \"whatever you put last\", because pinning needs an auto margin on whichever axis the rail currently runs — geometry that would otherwise land in consumer CSS, which this library does not accept. It reaches the mobile drawer with the rest of the rail; a control that exists on only some viewports is a trap, not a control. Ignored without `navRail`: there is no rail to pin it to.",
|
|
80
|
+
"name": "navRailEnd",
|
|
81
|
+
"type": "ReactNode"
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"description": "Accessible name for the `navRail` landmark. Defaults to the localized 'Workspaces'. The rail and the sidebar are two `complementary` landmarks on one page, so ARIA requires distinct names; the shell supplies both defaults so the two columns of equal rank behave the same way.",
|
|
85
|
+
"name": "navRailLabel",
|
|
86
|
+
"type": "string"
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"description": "App-level footer outside the main content area.",
|
|
90
|
+
"name": "footer",
|
|
91
|
+
"type": "ReactNode"
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"description": "Breadcrumb trail rendered in the topbar header for back-navigation.",
|
|
95
|
+
"name": "breadcrumb",
|
|
96
|
+
"type": "BreadcrumbProp"
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"description": "Navigation shown in the AppShell-owned mobile drawer at or below 900px (where the docked sidebar is hidden). Defaults to the `sidebar` node; pass a tailored menu, or null to opt out.",
|
|
100
|
+
"name": "mobileNav",
|
|
101
|
+
"type": "ReactNode"
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"description": "Accessible title for the mobile navigation drawer. Defaults to localized 'Menu'.",
|
|
105
|
+
"name": "mobileNavLabel",
|
|
106
|
+
"type": "string"
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"description": "Controlled open state of the mobile drawer. Omit for AppShell-owned state.",
|
|
110
|
+
"name": "mobileNavOpen",
|
|
111
|
+
"type": "boolean"
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"description": "Change handler for the mobile drawer open state.",
|
|
115
|
+
"name": "onMobileNavOpenChange",
|
|
116
|
+
"type": "(open: boolean) => void"
|
|
117
|
+
}
|
|
118
|
+
],
|
|
119
|
+
"related": [
|
|
120
|
+
"AppShell — opinionated wrapper that composes AppShell + a frozen default Topbar in three props (menu, children, breadcrumb). Use AppShell for quick scaffolding when the default GodX product chip and no-op search/notification handlers are acceptable; switch to AppShell directly the moment you need a custom entity switcher, real onSearchOpen, user slot, or any topbar configuration.",
|
|
121
|
+
"Sidebar — the canonical node to pass as AppShell's `sidebar` prop; owns activeId, collapsible submenu groups, collapsed icon-only mode, and section labels. Never hand-roll a nav list inside the sidebar slot.",
|
|
122
|
+
"Topbar — the structured topbar component to pass to AppShell's `topbar` prop when you need live product/project chip switchers, search, notifications, sidebar toggle, user avatar, or rightSlot extras. A custom `topbar` replaces the DEFAULT bar layout, so `topbarLeft`/`topbarRight` are ignored — but `logo` is not: the shell always renders it, and `topbarSpan` decides whether it lands in the bar or at the sidebar's head.",
|
|
123
|
+
"PageContainer — the mandatory direct child inside AppShell's `children` for every page; provides title, subtitle, extra actions, breadcrumb, footer, variant (flush/narrow/ghost), and density. Never render raw content directly as AppShell's child without a PageContainer wrapper."
|
|
124
|
+
],
|
|
125
|
+
"rules": [
|
|
126
|
+
23
|
|
127
|
+
],
|
|
128
|
+
"storyPath": "layout/AppShell.stories.tsx",
|
|
129
|
+
"tagline": "Root application shell — composes sidebar, topbar rail, main content area, and optional footer.",
|
|
130
|
+
"usage": [
|
|
131
|
+
"useAppShellNavigationMode() returns drawer/docked at the same 56.25rem boundary as AppShell. The root also publishes data-navigation. Use this instead of a consumer media query.",
|
|
132
|
+
"DO pass a <Sidebar> node to `sidebar` (required) and page content to `children` (required) — these are the only two required props. Everything else is optional and omitting optional slots simply removes that zone from the rendered DOM.",
|
|
133
|
+
"DO rely on AppShell's OWNED mobile drawer at or below 900px (NOT the Tailwind `lg` 1024px step — the shipped media query is `width <= 56.25rem`): it renders a hamburger trigger in the topbar and a focus-trapped Sheet (Esc + overlay close, focus returns to the trigger). `mobileNav` defaults to the `sidebar` node, so the same nav is reachable on mobile with no wiring — never hide the sidebar without providing this. Pass a tailored `mobileNav`, or `mobileNav={null}` only when navigation lives elsewhere (e.g. a bottom bar).",
|
|
134
|
+
"DO set `responsiveNavigation=\"docked\"` only when the approved product contract retains its sidebar below 900px. AppShell keeps the same sidebar/footer/active navigation in a token-sized grid track and removes the redundant drawer trigger; never reproduce this with consumer media queries.",
|
|
135
|
+
"DO let the drawer nav own its own inset: AppShell renders `mobileNav` in a Sheet body whose inline padding is the `--app-shell-mobile-nav-inset` token (near-zero by default) instead of the generic 24px sheet chrome inset, so a <Sidebar> in the drawer is not double-padded (its own --sidebar-nav-scroll-padding already insets each row). If a custom `mobileNav` node needs the full chrome inset, set `--app-shell-mobile-nav-inset: var(--space-6)` in the service theme — never patch the drawer with a `[data-slot='sheet-body']` selector in app CSS.",
|
|
136
|
+
"DO use the auto-built topbar rail (logo / topbarLeft / topbarRight) for simple shells. Pass a fully configured <Topbar> to the `topbar` prop when you need live handlers (entity switcher via productMenu, search, notifications, user avatar) — `topbarLeft`/`topbarRight` are then ignored (they are slots of the DEFAULT bar layout, and a custom `topbar` IS replacing that layout; a dev-mode warning names them). `logo` is NOT one of them: it is always rendered, because brand identity belongs to the frame rather than to the bar's contents. Two repos passed both for months and got no logo at all — no error, no warning, and a bar that still looked right because it had other content.",
|
|
137
|
+
"DO pass `logo` and let the shell place it — do NOT put the lockup in `Sidebar`'s `brand` slot or hand-position it in a `Topbar` slot. THE TOP-LEFT CORNER BELONGS TO WHOEVER `topbarSpan` SAYS: under `content` the rail runs the full window height and the brand sits at the sidebar's head, aligned to that track; under `full` the bar runs edge to edge and the brand goes in the bar with the rest of the space-level chrome. Placing it yourself pins it to one of those answers, and the axis then moves the rest of the shell out from under it (measured on a shipped consumer: the logo floating in the content column at x=280, indented 24px past the rail it should have sat above).",
|
|
138
|
+
"DO give a wide brand lockup a narrow-bar answer with `logoCompact` instead of cropping your own artwork in the app: `<AppShell topbarSpan=\"full\" logo={<FullLockup />} logoCompact={<MarkOnly />} />` swaps the NODE below the `sm` step, which is the only way to change a `viewBox` (no stylesheet can set an attribute). Without it the brand cell still gives room back — it is capped at --app-shell-brand-compact-max-inline-size below that step and cropped from the inline-end — where before it kept its intrinsic width at every viewport: measured with a 6:1 lockup at 390px, brand 143.7px, `.ui-topbar` 150.3px, `.ui-topbar-start` width 0 and the end cluster's last cell painting at x=384.8 on a 390px viewport (gh#728).",
|
|
139
|
+
"DO build a chat / mail / IDE shell by omitting ALL FOUR bar slots (`topbar`, `topbarLeft`, `topbarRight`, `logo`) — a shell whose PAGE owns the top row. AppShell then renders no `<header class='app-topbar'>` and marks its root `data-topbar='none'`, so the bar's grid row collapses to zero and the page header IS the first row of chrome. Keeping an empty bar instead costs a fixed `--app-shell-bar-height` band plus its border and card background, stacking a second row of chrome (~48px + the page header) over exactly the region a transcript or an editor needs most. The mobile drawer survives: at or below 900px the header returns carrying the hamburger alone.",
|
|
140
|
+
"DO NOT fake the bar-less shell with `topbar={<></>}` (or `topbarLeft={<div />}`, `logo={null}`) — any defined slot counts as bar content, so the `<header>` is still rendered, still paints its border and background, and still eats the grid row. The trigger is the slot being UNDEFINED; pass nothing at all (a conditional slot must resolve to `undefined`, not to an empty node).",
|
|
141
|
+
"DO wire a single `sidebarCollapsed` boolean between AppShell's `sidebarCollapsed` prop and Sidebar's `collapsed` prop — AppShell sets `data-collapsed='true'` on the root div (which CSS reads for width transitions) but does NOT own the collapsed state itself; lift the state and pass it down to both.",
|
|
142
|
+
"DO place breadcrumb content in AppShell's `breadcrumb` prop (renders in the `app-breadcrumb` div inside `<main>` ABOVE children) — do NOT hand-roll a breadcrumb bar as the first child of children, and do NOT put breadcrumbs inside <Sidebar>.",
|
|
143
|
+
"DO build a three-column shell (a narrow workspace/org rail, then the channel or section sidebar, then content) by passing `navRail` — NEVER by putting two columns inside the single `sidebar` slot. Hand-rolling it hits two measured traps: `Sidebar` renders `.sb-root { display: contents }`, so two Sidebars dropped side by side dissolve into one flex row and both collapse to zero unless each is separately wrapped in its own flex-column box; and sizing the one available track for two columns means overriding `--app-shell-sidebar-width`, which is how a shipped consumer moved its content edge 64px between routes. `navRail` owns the track, so neither is needed.",
|
|
144
|
+
"DO leave `sidebarCollapsed` wired to the sidebar alone when a `navRail` is present — collapse folds the sidebar track (16rem → 4rem) and the rail keeps its width, so the rail's destinations stay reachable while collapsed. That is the Slack behaviour and it is the shell's, not something to reproduce with consumer CSS.",
|
|
145
|
+
"DO NOT pass `mobileNav` just to re-add the rail on mobile — when `navRail` is present the drawer already defaults to the rail followed by the sidebar, because BOTH docked columns are hidden below 900px and a `sidebar`-only default would silently delete every app-level destination the rail carries.",
|
|
146
|
+
"DO NOT nest a second AppShell or AppShell inside AppShell's children — AppShell renders the root `app-root` div; nesting shells breaks the CSS grid layout.",
|
|
147
|
+
"DO NOT add padding directly to children expecting it to reach the viewport edge — AppShell's `<main>` is a scroll container; use <PageContainer> (or <PageContainer.Inset> inside a flush PageContainer) inside children to get standard page padding.",
|
|
148
|
+
"DO let the page be FLUID: inside AppShell a <PageContainer> spans the whole main column at every sidebar state (gh#672 — the old default 80rem cap left a 168px dead strip at a 1512px viewport once the sidebar collapsed, and cut a sticky footer short). A service that wants ONE bounded column sets `--app-shell-page-max-width` once in its theme (default `none`); it caps the page header, toolbar and body — never the `.ui-page-footer` band, which keeps spanning `.app-main` while its CONTENT ends on the end edge of the body (gh#682) — and a page-level `measure` overrides it on that page. Bound a single readable page with `measure=\"narrow\" | \"medium\"`, never a page-local max-width or a wrapper div."
|
|
149
|
+
],
|
|
150
|
+
"useCases": [
|
|
151
|
+
"Full admin SPA shell: AppShell wraps a <Sidebar> nav rail and a <Topbar> (with productMenu entity-switcher, onSearchOpen, onNotificationsOpen, user avatar) and every Inertia page renders as children inside a <PageContainer>.",
|
|
152
|
+
"Collapsible-sidebar layout: maintain a `collapsed` boolean in a persistent Inertia layout component, pass it to both AppShell's `sidebarCollapsed` and Sidebar's `collapsed`, wire Topbar's `onToggleCollapsed` to flip it — AppShell handles the CSS transition automatically.",
|
|
153
|
+
"Multi-tenant accounting app: pass a <Topbar start={<DropdownMenu>…</DropdownMenu>}> to AppShell's `topbar` slot so the legal-entity chip opens an inline switcher without a modal.",
|
|
154
|
+
"App-level footer (e.g. version/build info, compliance notice): pass a <footer> node to AppShell's `footer` prop — it renders outside `<main>` so it stays pinned below the scroll area.",
|
|
155
|
+
"Rapid prototype or internal tool where you want a branded shell with minimal topbar: skip the `topbar` prop entirely and use `logo`, `topbarLeft`, `topbarRight` to build the rail declaratively without instantiating <Topbar>.",
|
|
156
|
+
"Breadcrumb-aware shell: pass a <Breadcrumb items={…}> node to AppShell's `breadcrumb` prop so the breadcrumb strip appears above all page content without each page having to render it separately.",
|
|
157
|
+
"Chat / mail / IDE shell: a channel rail in `sidebar`, no bar slots at all, and a <PageContainer fill toolbar={…} footer={<Composer/>} stickyFooter> as children — the page's own header is the top row and the shell reserves no band above it, while the 900px drawer still reaches the channel list."
|
|
158
|
+
]
|
|
159
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
{
|
|
2
|
+
"example": "import { AreaChart } from \"@godxjp/ui/charts\";\n\n<AreaChart\n label={t(\"dashboard.trafficByChannel\")}\n data={data}\n categoryKey=\"day\"\n series={[\n { dataKey: \"organic\", label: t(\"channel.organic\") },\n { dataKey: \"paid\", label: t(\"channel.paid\") },\n ]}\n stacked\n/>",
|
|
3
|
+
"group": "data-display",
|
|
4
|
+
"importPath": "@godxjp/ui/charts",
|
|
5
|
+
"name": "AreaChart",
|
|
6
|
+
"props": [
|
|
7
|
+
{
|
|
8
|
+
"description": "Row data: one category per row with a numeric value per series.",
|
|
9
|
+
"name": "data",
|
|
10
|
+
"required": true,
|
|
11
|
+
"type": "ChartDatum[]"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"description": "Plotted series: { dataKey, label?, color? }.",
|
|
15
|
+
"name": "series",
|
|
16
|
+
"required": true,
|
|
17
|
+
"type": "ChartSeriesProp[]"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"description": "Key into each datum holding the x-axis category label.",
|
|
21
|
+
"name": "categoryKey",
|
|
22
|
+
"required": true,
|
|
23
|
+
"type": "string"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"description": "Accessible name + visible caption.",
|
|
27
|
+
"name": "label",
|
|
28
|
+
"required": true,
|
|
29
|
+
"type": "string"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"defaultValue": "true",
|
|
33
|
+
"description": "Paint `label` as a visible caption. Set false when a CardTitle or section heading already says it — the caption stays in the DOM as sr-only, so role=img keeps its accessible name.",
|
|
34
|
+
"name": "showCaption",
|
|
35
|
+
"type": "boolean"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"description": "Extra context appended to the screen-reader description.",
|
|
39
|
+
"name": "description",
|
|
40
|
+
"type": "string"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"defaultValue": "\"md\"",
|
|
44
|
+
"description": "Canvas height preset. Ignored when `height` is set.",
|
|
45
|
+
"name": "size",
|
|
46
|
+
"type": "\"xs\" | \"sm\" | \"md\" | \"lg\""
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"description": "Explicit canvas height in px (overrides `size`).",
|
|
50
|
+
"name": "height",
|
|
51
|
+
"type": "number"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"defaultValue": "true",
|
|
55
|
+
"description": "Show the series legend.",
|
|
56
|
+
"name": "showLegend",
|
|
57
|
+
"type": "boolean"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"defaultValue": "true",
|
|
61
|
+
"description": "Show the cartesian background grid.",
|
|
62
|
+
"name": "showGrid",
|
|
63
|
+
"type": "boolean"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"description": "Locale-aware formatting for ticks + tooltip values.",
|
|
67
|
+
"name": "numberFormat",
|
|
68
|
+
"type": "Intl.NumberFormatOptions"
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"defaultValue": "false",
|
|
72
|
+
"description": "Stack series areas instead of overlaying them.",
|
|
73
|
+
"name": "stacked",
|
|
74
|
+
"type": "boolean"
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"defaultValue": "false",
|
|
78
|
+
"description": "Render smooth (monotone) areas instead of straight segments.",
|
|
79
|
+
"name": "curved",
|
|
80
|
+
"type": "boolean"
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"description": "Message shown when `data` is empty.",
|
|
84
|
+
"name": "emptyMessage",
|
|
85
|
+
"type": "string"
|
|
86
|
+
}
|
|
87
|
+
],
|
|
88
|
+
"related": [
|
|
89
|
+
"LineChart — when only the trend line matters, not the filled magnitude.",
|
|
90
|
+
"BarChart — discrete category comparison."
|
|
91
|
+
],
|
|
92
|
+
"rules": [],
|
|
93
|
+
"storyPath": "charts/AreaChart.stories.tsx",
|
|
94
|
+
"tagline": "Magnitude over an ordered category axis — overlay or `stacked` areas, optional `curved` smoothing, localized formatting + text alternative. Data-visualization graph / plot.",
|
|
95
|
+
"usage": [
|
|
96
|
+
"DO import from the charts entry: `import { AreaChart } from \"@godxjp/ui/charts\";` (recharts optional peer required).",
|
|
97
|
+
"DO import only the chart a screen uses — `import { AreaChart } from \"@godxjp/ui/charts/area-chart\";` — when the `./charts` barrel should not link the whole chart family. Without the `recharts` peer the build then fails ONCE, naming the package and the fix.",
|
|
98
|
+
"DO use `stacked` to show how parts accumulate into a total over time.",
|
|
99
|
+
"DON'T overlay more than 2-3 unstacked areas — fill opacity makes dense overlays unreadable; switch to LineChart."
|
|
100
|
+
],
|
|
101
|
+
"useCases": [
|
|
102
|
+
"Cumulative volume over time (e.g. total transactions per day).",
|
|
103
|
+
"Stacked composition trend (traffic sources, revenue streams)."
|
|
104
|
+
]
|
|
105
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"example": "import { AspectRatio } from \"@godxjp/ui/layout\";\n\n<AspectRatio ratio={16 / 9}>...</AspectRatio>",
|
|
3
|
+
"group": "layout",
|
|
4
|
+
"importPath": "@godxjp/ui/layout",
|
|
5
|
+
"name": "AspectRatio",
|
|
6
|
+
"props": [
|
|
7
|
+
{
|
|
8
|
+
"defaultValue": "16 / 9",
|
|
9
|
+
"description": "Width divided by height.",
|
|
10
|
+
"name": "ratio",
|
|
11
|
+
"type": "number"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"description": "Content constrained to the ratio.",
|
|
15
|
+
"name": "children",
|
|
16
|
+
"type": "ReactNode"
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"related": [
|
|
20
|
+
"CardCover",
|
|
21
|
+
"Skeleton"
|
|
22
|
+
],
|
|
23
|
+
"rules": [
|
|
24
|
+
2,
|
|
25
|
+
3
|
|
26
|
+
],
|
|
27
|
+
"storyPath": "layout/AspectRatio.stories.tsx",
|
|
28
|
+
"tagline": "Radix AspectRatio wrapper for stable media and preview frames.",
|
|
29
|
+
"usage": [
|
|
30
|
+
"DO use AspectRatio for media, maps, charts, or previews that must not jump during load.",
|
|
31
|
+
"DON'T use it for unconstrained text content."
|
|
32
|
+
],
|
|
33
|
+
"useCases": [
|
|
34
|
+
"Video embed frame",
|
|
35
|
+
"Image preview slot",
|
|
36
|
+
"Dashboard chart placeholder"
|
|
37
|
+
]
|
|
38
|
+
}
|