@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,91 @@
|
|
|
1
|
+
{
|
|
2
|
+
"example": "import { RadioGroup } from \"@godxjp/ui/data-entry\";\n\n<RadioGroup value={trigger} onValueChange={setTrigger} orientation=\"horizontal\" options={[\n { label: \"初回購入\", value: \"first_purchase\" },\n { label: \"誕生日\", value: \"birthday\" },\n]} />",
|
|
3
|
+
"group": "data-entry",
|
|
4
|
+
"importPath": "@godxjp/ui/data-entry",
|
|
5
|
+
"name": "RadioGroup",
|
|
6
|
+
"props": [
|
|
7
|
+
{
|
|
8
|
+
"defaultValue": "\"default\"",
|
|
9
|
+
"description": "antd `optionType` — how each choice is DRAWN. `default` is a radio dot beside its label; `button` welds the choices into one segmented bar. The role stays `radiogroup`/`radio` either way: this is paint, never semantics, so a screen reader hears the same group in both.",
|
|
10
|
+
"name": "optionType",
|
|
11
|
+
"type": "\"default\" | \"button\""
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"defaultValue": "\"outline\"",
|
|
15
|
+
"description": "antd `buttonStyle` — fill of the SELECTED choice while `optionType=\"button\"`. Inert on the default option type.",
|
|
16
|
+
"name": "buttonStyle",
|
|
17
|
+
"type": "\"outline\" | \"solid\""
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"description": "Id của nhóm; `FormField` tự truyền xuống để nối nhãn ↔ control.",
|
|
21
|
+
"name": "id",
|
|
22
|
+
"type": "string"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"description": "Controlled selected value.",
|
|
26
|
+
"name": "value",
|
|
27
|
+
"type": "string"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"description": "Fires on selection change.",
|
|
31
|
+
"name": "onValueChange",
|
|
32
|
+
"type": "(value: string) => void"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"description": "Declarative list: { label, value, disabled?, description? }.",
|
|
36
|
+
"name": "options",
|
|
37
|
+
"type": "ChoiceOptionProp[]"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"defaultValue": "\"vertical\"",
|
|
41
|
+
"description": "Layout direction.",
|
|
42
|
+
"name": "orientation",
|
|
43
|
+
"type": "\"horizontal\" | \"vertical\""
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"description": "Uncontrolled initial selected value.",
|
|
47
|
+
"name": "defaultValue",
|
|
48
|
+
"type": "string"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"description": "Disable the whole group.",
|
|
52
|
+
"name": "disabled",
|
|
53
|
+
"type": "boolean"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"description": "Form field name for native submission.",
|
|
57
|
+
"name": "name",
|
|
58
|
+
"type": "string"
|
|
59
|
+
}
|
|
60
|
+
],
|
|
61
|
+
"related": [
|
|
62
|
+
"CheckboxGroup — use when the user may select zero or more values simultaneously (multi-select); RadioGroup enforces exactly one selection. Both share the same options array shape and orientation prop.",
|
|
63
|
+
"Select — use when there are 5 or more options or the option list is dynamic/searchable; RadioGroup is preferred for 2-4 fixed visible choices where scanning all options at once matters.",
|
|
64
|
+
"Field — use when there are exactly two states that map to on/off (boolean); RadioGroup is the right pick when the two-or-more options are semantically distinct named values, not a toggle.",
|
|
65
|
+
"Field — the low-level label+description wrapper that RadioGroup uses internally for each item. Use it directly only when manually composing Radio.Item children inside Radio.Group; never hand-roll a label alongside a bare Radio.Item without it."
|
|
66
|
+
],
|
|
67
|
+
"rules": [
|
|
68
|
+
23
|
|
69
|
+
],
|
|
70
|
+
"storyPath": "data-entry/RadioGroup.stories.tsx",
|
|
71
|
+
"subParts": [
|
|
72
|
+
"RadioGroupRoot"
|
|
73
|
+
],
|
|
74
|
+
"tagline": "Radio group accepting an options array or RadioItem children.",
|
|
75
|
+
"usage": [
|
|
76
|
+
"DO use the `options` prop for the data-driven path: pass `{ label, value, disabled?, description? }[]` and RadioGroup renders every option as a correctly labelled Field automatically — never hand-roll Radio.Item + Label pairs in a loop yourself.",
|
|
77
|
+
"DO provide `name` whenever the group lives inside an HTML form: Radix renders a hidden `<input name={name}>` carrying the selected string value, making the field natively form-submittable without a separate hidden input.",
|
|
78
|
+
"DO use controlled mode (`value` + `onValueChange`) for any form managed by useForm or a state manager. Use `defaultValue` only for truly uncontrolled UI where you never need to read the value in code.",
|
|
79
|
+
"DO NOT reach for children / manual composition unless the options list is dynamic-JSX (e.g. each item needs a custom rendered label with an icon). When you do compose children manually, wrap each Radio.Item in a Field — rendering a bare Radio.Item without Field skips the label and breaks a11y.",
|
|
80
|
+
"DO NOT use RadioGroup when the user may select zero or multiple items — that is CheckboxGroup. RadioGroup enforces exactly one selection at all times (or none before first interaction when uncontrolled).",
|
|
81
|
+
"A11y: the root emits `role=radiogroup`; each item is a real `<input type=\"radio\">` (implicit `role=radio`) and is keyboard-navigable with arrow keys. Never suppress `name` on the Root when inside a form — without it the input is unnamed and won't submit. Name an item with `<Label htmlFor>` (or `Field`) BESIDE it, never by wrapping it: the control already sits inside its own `<label>`, and a second one nested outside leaves it nameless."
|
|
82
|
+
],
|
|
83
|
+
"useCases": [
|
|
84
|
+
"Selecting a single billing cycle (monthly / quarterly / annual) in an invoice or subscription settings form where all 2-4 options must be visible at once.",
|
|
85
|
+
"Choosing a report output format (PDF / CSV / Excel) before triggering an async export job — keeps all options scannable without opening a dropdown.",
|
|
86
|
+
"Picking a transaction type (income / expense / transfer) on an accounting entry form where the choice changes which subsequent fields are shown.",
|
|
87
|
+
"Selecting a sync trigger mode (first purchase / birthday / manual) in a campaign or automation settings panel — matches the catalog example exactly.",
|
|
88
|
+
"Filtering a compact inline control (horizontal orientation) such as date granularity (day / week / month) inside a dashboard filter bar where a full Select dropdown would be over-engineered.",
|
|
89
|
+
"Choosing an approval status (pending / approved / rejected) on an admin detail sheet where all states must be visible so reviewers can compare them without interaction."
|
|
90
|
+
]
|
|
91
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{
|
|
2
|
+
"docPath": "docs/data-display/timeline.tsx",
|
|
3
|
+
"example": "<RangeTimeline label=\"Schedule\" columns={[{ label: \"Week\", units: 7 }]} rows={[{ id: \"task\", label: \"Task\", start: 0, end: 6, startLabel: \"Start: day 1\", endLabel: \"End: day 7\" }]} />",
|
|
4
|
+
"group": "data-display",
|
|
5
|
+
"importPath": "@godxjp/ui/data-display",
|
|
6
|
+
"name": "RangeTimeline",
|
|
7
|
+
"props": [
|
|
8
|
+
{
|
|
9
|
+
"description": "Localized accessible name for the scrollable schedule.",
|
|
10
|
+
"name": "label",
|
|
11
|
+
"required": true,
|
|
12
|
+
"type": "string"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"description": "Positive unit counts determine proportional column widths. Column count determines the minimum canvas width, so coarse grouping zooms out. `muted: true` tints that column down the whole body (a weekend, a holiday, a closed period) with `--range-timeline-muted-column-background` (default `hsl(var(--muted))`, the header surface; body text keeps 14.25:1 / 12.4:1 and `--muted-foreground` 5.2:1 / 5.47:1 on it).",
|
|
16
|
+
"name": "columns",
|
|
17
|
+
"required": true,
|
|
18
|
+
"type": "{ label: string; units: number; muted?: boolean }[]"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"description": "Optional grouped axis labels above the ticks, for example months above days. Counts use the same units as columns and cover the same range.",
|
|
22
|
+
"name": "bands",
|
|
23
|
+
"type": "{ label: string; units: number }[]"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"description": "Each row has id, label, inclusive start/end unit offsets, and localized startLabel/endLabel including current values. Optional `depth` (0 = top level) nests rows: pass them flat and depth-first (a parent, then its descendants); a row is a parent when the row after it is deeper. The component indents the label cell by `--range-timeline-indent-width` per level (logical padding, so RTL indents from the right; the label column keeps its width) and gives each parent a disclosure button. With no `depth > 0` anywhere the markup is unchanged.",
|
|
27
|
+
"name": "rows",
|
|
28
|
+
"required": true,
|
|
29
|
+
"type": "RangeTimelineRow[]"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"description": "Optional current unit marker.",
|
|
33
|
+
"name": "today",
|
|
34
|
+
"type": "number | null"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"description": "Committed endpoint movement in units. Omit for read-only. Dates remain consumer data; do not replace true anchors with clipped positions.",
|
|
38
|
+
"name": "onRangeChange",
|
|
39
|
+
"type": "(id: string, edge: \"start\" | \"end\", delta: number) => void"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"defaultValue": "true",
|
|
43
|
+
"description": "Rule the body as a Gantt grid: a line between every row (label column and track), a line under the header and between band and tick rows, and a vertical line per column down the whole body, exactly under its header column edge for unequal `units` too. ON BY DEFAULT, like `Calendar bordered`. The lines are one decorative layer (aria-hidden, pointer-events none) behind the bars. TWO TIERS, and they are different knobs: the INSIDE grid is `--range-timeline-grid-color`, default `hsl(var(--border))` (L* 93.80 / 1.149:1 on the card in light, L* 20.76 / 1.270:1 in dark) at `--range-timeline-grid-width` (hairline); the OUTER FRAME and the label-column divider are `--range-timeline-border-color`, default `var(--border)` — the same tier, so the grid can never read heavier than the box around it or than a DataTable row rule. It was `hsl(var(--input) / 0.5)` (L* 78.67 / 1.738:1) through 27.6.0 and was reported darker than every card and table in the system (gh#730); set `--range-timeline-grid-color: hsl(var(--input) / 0.5)` to get that weight back. `bordered={false}` restores header-only ruling; muted columns still paint.",
|
|
44
|
+
"name": "bordered",
|
|
45
|
+
"type": "boolean"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"defaultValue": "\"default\"",
|
|
49
|
+
"description": "Width of ONE axis unit — the canonical density vocabulary, the same three steps `DataTable density` takes. `default` is the shipped 56px/day, so no existing Gantt moves; `compact` is 42px/day (a 31-day axis needs 1558px instead of 1992px — measured, the widest two-digit day label across ja/en/ar/fa/hi/th/bn numbering systems is 19.86px in a 24px content box, so the tick still fits); `comfortable` is 70px/day. It moves the COLUMN WIDTH ONLY — row height, bar height and the label column are identical at every step, so a compact Gantt is the same schedule with more days on screen, not a smaller one. Re-points `--range-timeline-unit-width` on the element; retune a step with `--range-timeline-unit-width-{compact,default,comfortable}`. Unrelated to `PageContainer density` / `AppProvider density`, which rescale the whole page through `--scaling`.",
|
|
50
|
+
"name": "density",
|
|
51
|
+
"type": "\"compact\" | \"default\" | \"comfortable\""
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"description": "Controlled ids of the EXPANDED parent rows (same spelling as `Tree expandedValues`). A folded parent removes every descendant row — label AND bar — from the DOM and the accessibility tree; the body grid, row rules and today marker stay aligned.",
|
|
55
|
+
"name": "expandedValues",
|
|
56
|
+
"type": "readonly string[]"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"description": "Uncontrolled initial expanded parents. Omitted: every parent starts expanded (unlike `Tree`, whose branches start closed), so adding `depth` never hides a row.",
|
|
60
|
+
"name": "defaultExpandedValues",
|
|
61
|
+
"type": "readonly string[]"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"description": "Fires with the next expanded parent ids when a disclosure is toggled, for controlled and uncontrolled timelines alike.",
|
|
65
|
+
"name": "onExpandedValuesChange",
|
|
66
|
+
"type": "(values: string[]) => void"
|
|
67
|
+
}
|
|
68
|
+
],
|
|
69
|
+
"rules": [],
|
|
70
|
+
"storyPath": "data-display/timeline.tsx",
|
|
71
|
+
"tagline": "Horizontal intervals with token-owned geometry and optional endpoint editing.",
|
|
72
|
+
"usage": [
|
|
73
|
+
"Provide a precise non-drag editor in each row label when enabling changes. Clipped endpoints and short intervals omit grips; labels and their editors remain available.",
|
|
74
|
+
"An interval wholly outside the columns shows a localized direction indicator at that edge of its row: a chevron plus \"before\" at the inline-start edge, \"after\" plus a chevron at the inline-end edge; the chevrons mirror under RTL.",
|
|
75
|
+
"Mark non-working columns with `columns[].muted` rather than tinting cells yourself; retint the grid through `--range-timeline-grid-color` / `--range-timeline-muted-column-background`, never page CSS.",
|
|
76
|
+
"Need more days on one screen? Pass `density=\"compact\"` — do NOT declare `--range-timeline-unit-width` in an app stylesheet. Tick and band labels are CENTRED on their column by the component; a band clipped by the range (a month the axis starts inside) centres its label on the VISIBLE part, since that is the box the band owns.",
|
|
77
|
+
"Use TimelineGrid for time-of-day columns; RangeTimeline is a horizontal range axis.",
|
|
78
|
+
"Nested Gantt (parent/child work items): pass the server's depth-first rows with `depth` on each (`{ id, label, start, end, startLabel, endLabel, depth }`), and fold with `expandedValues` / `onExpandedValuesChange` (or `defaultExpandedValues`). Never fake an indent inside `label` — the component owns the indent, the disclosure button (named from `rangeTimeline.childRows` + the row label, `aria-expanded`) and the `list` / `listitem` + `aria-level` / `aria-setsize` / `aria-posinset` structure."
|
|
79
|
+
]
|
|
80
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
{
|
|
2
|
+
"example": "import { Rating } from \"@godxjp/ui/data-entry\";\n\n<Rating name=\"score\" defaultValue={4} onValueChange={(v) => console.log(v)} />",
|
|
3
|
+
"group": "data-entry",
|
|
4
|
+
"importPath": "@godxjp/ui/data-entry",
|
|
5
|
+
"name": "Rating",
|
|
6
|
+
"props": [
|
|
7
|
+
{
|
|
8
|
+
"defaultValue": "5",
|
|
9
|
+
"description": "antd `count` — number of symbols. This library's older `max` still works; `count` wins when both are given.",
|
|
10
|
+
"name": "count",
|
|
11
|
+
"type": "number"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"defaultValue": "false",
|
|
15
|
+
"description": "antd `allowHalf` — a symbol can be half-filled, so the scale steps by 0.5 (keyboard included). It adds a hit area per symbol WITHOUT doubling the radios: a half is a position inside a step, and ten radios announced for a five-star scale would misstate the scale.",
|
|
16
|
+
"name": "allowHalf",
|
|
17
|
+
"type": "boolean"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"defaultValue": "false",
|
|
21
|
+
"description": "antd `allowClear` — choosing the chosen value clears it to 0. antd defaults this ON; it is OFF here, because a rating in a business form is usually required and a silent reset on a second click reads as a lost answer.",
|
|
22
|
+
"name": "allowClear",
|
|
23
|
+
"type": "boolean"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"description": "antd `character` — what a symbol IS. A node for every symbol, or a function of the 1-based index for a scale whose symbols differ (A/B/C, 松竹梅).",
|
|
27
|
+
"name": "character",
|
|
28
|
+
"type": "React.ReactNode | ((index: number) => React.ReactNode)"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"description": "antd `tooltips` — a label per step, in order. Folded into each symbol's ACCESSIBLE NAME rather than shown only on hover: a `title` is invisible to a keyboard and to touch, and saying what '3 of 5' means is the whole point of the prop.",
|
|
32
|
+
"name": "tooltips",
|
|
33
|
+
"type": "readonly string[]"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"description": "Controlled rating (1..max).",
|
|
37
|
+
"name": "value",
|
|
38
|
+
"type": "number"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"defaultValue": "0",
|
|
42
|
+
"description": "Uncontrolled initial rating.",
|
|
43
|
+
"name": "defaultValue",
|
|
44
|
+
"type": "number"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"description": "Rating callback.",
|
|
48
|
+
"name": "onValueChange",
|
|
49
|
+
"type": "(value: number) => void"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"defaultValue": "5",
|
|
53
|
+
"description": "Number of stars.",
|
|
54
|
+
"name": "max",
|
|
55
|
+
"type": "number"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"description": "Display-only (e.g. an average score).",
|
|
59
|
+
"name": "readOnly",
|
|
60
|
+
"type": "boolean"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"description": "Hidden input name for native form submission.",
|
|
64
|
+
"name": "name",
|
|
65
|
+
"type": "string"
|
|
66
|
+
}
|
|
67
|
+
],
|
|
68
|
+
"related": [
|
|
69
|
+
"RadioGroup (non-star single choice)",
|
|
70
|
+
"Slider (continuous 0-100 value)"
|
|
71
|
+
],
|
|
72
|
+
"rules": [
|
|
73
|
+
3,
|
|
74
|
+
6,
|
|
75
|
+
23
|
|
76
|
+
],
|
|
77
|
+
"storyPath": "data-entry/Rating.stories.tsx",
|
|
78
|
+
"tagline": "Star-rating input (radiogroup) — controlled via value/onValueChange, form-submittable via name, supports readOnly display.",
|
|
79
|
+
"usage": [
|
|
80
|
+
"DO theme the stars with `--rating-star-filled-color` (default `var(--warning)`) and `--rating-star-empty-color` (default `var(--muted-foreground)`, at `--rating-star-empty-alpha` 0.45) — HSL components, set on :root or a scoped [data-tenant] (gh#694). NEVER override `.ui-rating-star-filled`: it is an internal class. Unset, the stars paint exactly as before.",
|
|
81
|
+
"DO use readOnly to DISPLAY a score (e.g. product average); interactive (default) for collecting a rating.",
|
|
82
|
+
"DO pass `name` to submit the value in a plain form.",
|
|
83
|
+
"DON'T render raw star icons for input — this handles keyboard (radiogroup), hover preview, and a11y.",
|
|
84
|
+
"NOTE a long scale WRAPS rather than overflowing. At `max={10}` the row needs ~276px of hit area and a 320px viewport offers ~212 inside a card, so the stars fall onto a second line instead of being painted where no one can reach them. Don't add `flex-nowrap` or a fixed width to force one line; use a smaller `max` if a single row matters."
|
|
85
|
+
],
|
|
86
|
+
"useCases": [
|
|
87
|
+
"Product / vendor review input",
|
|
88
|
+
"Display an average score (readOnly)",
|
|
89
|
+
"Feedback / CSAT survey",
|
|
90
|
+
"Priority or quality scoring in admin"
|
|
91
|
+
]
|
|
92
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"example": "import { ResizablePanelGroup, ResizablePanel, ResizableHandle } from \"@godxjp/ui/layout\";\n\n<ResizablePanelGroup orientation=\"horizontal\">\n <ResizablePanel id=\"list\" defaultSize=\"35%\" minSize=\"20%\">Panel A</ResizablePanel>\n <ResizableHandle />\n <ResizablePanel id=\"detail\" defaultSize=\"65%\" minSize=\"40%\">Panel B</ResizablePanel>\n</ResizablePanelGroup>",
|
|
3
|
+
"group": "layout",
|
|
4
|
+
"importPath": "@godxjp/ui/layout",
|
|
5
|
+
"name": "ResizablePanel",
|
|
6
|
+
"props": [
|
|
7
|
+
{
|
|
8
|
+
"defaultValue": "\"horizontal\"",
|
|
9
|
+
"description": "ResizablePanelGroup prop — axis the panels are laid out / resized along (horizontal = side-by-side).",
|
|
10
|
+
"name": "orientation",
|
|
11
|
+
"type": "\"horizontal\" | \"vertical\""
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"description": "ResizablePanel identifier — required for collapse/expand control and for layout persistence.",
|
|
15
|
+
"name": "id",
|
|
16
|
+
"type": "string"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"description": "For a percentage of the group pass a string like \"35%\" — `defaultSize={35}` means 35px (a sliver), not 35%.",
|
|
20
|
+
"name": "defaultSize",
|
|
21
|
+
"type": "string | number"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"description": "ResizablePanel minimum size — drag can't shrink below this. Same unit rule: \"20%\" for percent, bare number = px.",
|
|
25
|
+
"name": "minSize",
|
|
26
|
+
"type": "string | number"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"description": "ResizablePanel maximum size. \"60%\" for percent, bare number = px.",
|
|
30
|
+
"name": "maxSize",
|
|
31
|
+
"type": "string | number"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"defaultValue": "false",
|
|
35
|
+
"description": "ResizablePanel — allow the panel to collapse to collapsedSize when dragged below minSize. Pair with onResize to react to collapse.",
|
|
36
|
+
"name": "collapsible",
|
|
37
|
+
"type": "boolean"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"description": "ResizablePanel — fires while/after the panel is resized (e.g. to persist layout).",
|
|
41
|
+
"name": "onResize",
|
|
42
|
+
"type": "(size: PanelSize, id, prevSize) => void"
|
|
43
|
+
}
|
|
44
|
+
],
|
|
45
|
+
"rules": [
|
|
46
|
+
3,
|
|
47
|
+
6
|
|
48
|
+
],
|
|
49
|
+
"storyPath": "layout/ResizablePanel.stories.tsx",
|
|
50
|
+
"subParts": [
|
|
51
|
+
"ResizableHandle",
|
|
52
|
+
"ResizablePanelGroup"
|
|
53
|
+
],
|
|
54
|
+
"tagline": "Resizable panel group/child/handle primitives from react-resizable-panels.",
|
|
55
|
+
"usage": [
|
|
56
|
+
"DO put the layout on `<ResizablePanelGroup orientation=\"horizontal|vertical\">`, the resizable regions in `<ResizablePanel>`, and a `<ResizableHandle>` BETWEEN every adjacent pair — a group of N panels needs N-1 handles or there is nothing to drag.",
|
|
57
|
+
"DO express `defaultSize`/`minSize`/`maxSize` as PERCENTAGE STRINGS like `defaultSize=\"35%\"` (react-resizable-panels v4: a bare number is PIXELS, so `defaultSize={35}` renders a 35px sliver, NOT 35% — pass the string). Don't fight sizing with a fixed `w-[280px]` className on the panel.",
|
|
58
|
+
"DON'T reach for ResizablePanel when the split is fixed and never user-adjustable — use a plain `Flex`/`ResponsiveGrid`, or `SplitPane` for a simple two-pane layout. Resizable is for *user-draggable* boundaries only.",
|
|
59
|
+
"DO give each panel a stable `id` and use `collapsible` + `collapsedSize` for a side panel the user can fully tuck away (e.g. a filters rail), reacting via `onResize`.",
|
|
60
|
+
"DON'T hand-roll a draggable divider with mouse-move listeners — the primitive handles pointer + keyboard resizing, ARIA separator semantics, and min/max clamping. Always render `<ResizableHandle>`, never a bare styled `<div>`.",
|
|
61
|
+
"DON'T add your own `overflow: auto` wrapper inside a panel: the panel IS the scroll container. Let it own the scrolling and it also manages keyboard reachability — when a panel scrolls but holds nothing focusable, ResizablePanel gives it `tabindex=0` so keyboard users can still reach the clipped content (WCAG 2.1.1); a panel whose content is focusable is left alone, so no redundant tab stop appears. Your own nested scroller gets neither."
|
|
62
|
+
],
|
|
63
|
+
"useCases": [
|
|
64
|
+
"Master–detail admin layout: a draggable list pane on the left and a detail/preview pane on the right (e.g. 仕訳一覧 | 仕訳詳細).",
|
|
65
|
+
"Collapsible filters or navigation rail beside a data table that operators can widen for long labels or tuck away to maximize the table.",
|
|
66
|
+
"Stacked vertical split (orientation='vertical') such as a results table over a live JSON/log preview in a data-import tool.",
|
|
67
|
+
"Three-pane workbench (nav | content | inspector) where each boundary is independently draggable and layout is persisted via id + onResize."
|
|
68
|
+
]
|
|
69
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"example": "import { ResponsiveGrid } from \"@godxjp/ui/layout\";\nimport { StatCard } from \"@godxjp/ui/data-display\";\n\n<ResponsiveGrid columns={4}>\n <StatCard label=\"総会員数\" value=\"12,400\" />\n <StatCard label=\"公開中クーポン\" value=\"8\" />\n <StatCard label=\"月間利用数\" value=\"3,210\" />\n <StatCard label=\"割引総額\" value=\"¥480,000\" />\n</ResponsiveGrid>",
|
|
3
|
+
"group": "layout",
|
|
4
|
+
"importPath": "@godxjp/ui/layout",
|
|
5
|
+
"name": "ResponsiveGrid",
|
|
6
|
+
"props": [
|
|
7
|
+
{
|
|
8
|
+
"description": "Measured inset escape; stamped data-pad-raw.",
|
|
9
|
+
"name": "padRaw",
|
|
10
|
+
"type": "PadRawProp"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"description": "Token inset, scalar or logical sides.",
|
|
14
|
+
"name": "pad",
|
|
15
|
+
"type": "PadProp"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"defaultValue": "\"rows\"",
|
|
19
|
+
"description": "Rows adapt to container width. Columns keep a horizontal collection in order with token-owned column width; compose inside ScrollArea orientation=\"horizontal\". Column flow ignores columns and preset geometry.",
|
|
20
|
+
"name": "flow",
|
|
21
|
+
"type": "\"rows\" | \"columns\""
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"description": "antd `Row align`, spelled with Flex's FlexAlignProp values (antd `top` → \"start\"). \"stretch\" makes every cell as tall as the tallest one. Omitted, flow=\"columns\" keeps \"start\" and flow=\"rows\" keeps the grid's natural stretch.",
|
|
25
|
+
"name": "align",
|
|
26
|
+
"type": "\"start\" | \"stretch\""
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"defaultValue": "4",
|
|
30
|
+
"description": "Numeric desktop target, or container column counts. Base defaults to 1; omitted object steps inherit upward. Use { base: 2, sm: 4 } for compact metric tiles.",
|
|
31
|
+
"name": "columns",
|
|
32
|
+
"type": "number | { base?: number; sm?: number; md?: number; lg?: number }"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"defaultValue": "\"md\"",
|
|
36
|
+
"description": "Token gap between cells, the same steps as Flex. \"none\" is a DELIBERATE zero for tiles that must read as one continuous surface (a segmented bar, a seamless tile strip) — not a way to opt out of the token scale.",
|
|
37
|
+
"name": "gap",
|
|
38
|
+
"type": "\"none\" | \"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\""
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"description": "Grid items — typically Card or StatCard.",
|
|
42
|
+
"name": "children",
|
|
43
|
+
"required": true,
|
|
44
|
+
"type": "ReactNode"
|
|
45
|
+
}
|
|
46
|
+
],
|
|
47
|
+
"related": [
|
|
48
|
+
"Flex — use Flex (direction col or row) for sequential blocks of mixed-width content (forms, description lists, button rows). Use ResponsiveGrid only when you want equal-width, auto-reflowing tile columns.",
|
|
49
|
+
"SplitPane — use SplitPane for a fixed two-panel side-by-side layout with a defined primary/secondary ratio that does NOT collapse to stacked tiles. Use ResponsiveGrid when you want automatic column count collapse on narrow screens.",
|
|
50
|
+
"StatCard — the canonical direct child of ResponsiveGrid for KPI tiles. StatCard is self-contained (draws its own bordered card); never wrap it in Card/CardContent when placing it inside ResponsiveGrid.",
|
|
51
|
+
"SkeletonStat — the loading-state sibling of StatCard, used as a drop-in placeholder child of ResponsiveGrid with the same columns count while KPI data is in flight."
|
|
52
|
+
],
|
|
53
|
+
"rules": [
|
|
54
|
+
24,
|
|
55
|
+
40
|
|
56
|
+
],
|
|
57
|
+
"storyPath": "layout/ResponsiveGrid.stories.tsx",
|
|
58
|
+
"tagline": "Container-responsive card grid with configurable base, sm, md and lg columns.",
|
|
59
|
+
"usage": [
|
|
60
|
+
"ResponsiveGrid.Item span={2} owns a responsive column span; an object {base:1,lg:2} sets explicit steps, clamped to the parent columns.",
|
|
61
|
+
"KANBAN LANES: <ScrollArea orientation=\"horizontal\"><ResponsiveGrid flow=\"columns\" align=\"stretch\">{lanes.map(lane => <Card onDragOver onDrop>…<Card draggable>…</Card></Card>)}</ResponsiveGrid></ScrollArea>. align=\"stretch\" keeps an empty lane as tall as the fullest one, so it stays a full-height drop target; a Select inside a draggable card does not enlarge the drag image.",
|
|
62
|
+
"DO place StatCard tiles directly as immediate children — StatCard IS already a bordered card; never wrap it in an extra <Card><CardContent>. The canonical pattern is <ResponsiveGrid columns={4}><StatCard .../><StatCard .../></ResponsiveGrid>.",
|
|
63
|
+
"DO use columns={2|3|4} to declare the target desktop column count — the grid collapses automatically to 1 column on narrow containers (mobile-first via CSS container queries), via 2-column intermediate at ≥640px, then full target count at ≥1024px. Use columns={{ base: 2, sm: 4 }} for two mobile columns and four wider-container columns; no consumer CSS is needed.",
|
|
64
|
+
"DO NOT place a DataTable inside a ResponsiveGrid column beside a card or chart. DataTable must occupy its own full-width row in a Card with CardContent flush. Nesting a multi-column table in a grid column squeezes CJK text to one character per line (see rule 37).",
|
|
65
|
+
"DO use ResponsiveGrid for page-level spacing — it applies the correct gap token (--space-stack-md) automatically. Never add raw gap-* / p-* / space-* utilities to the page layout around tiles; compose spacing through this component instead (rule 40).",
|
|
66
|
+
"DO render SkeletonStat children in place of StatCard tiles while KPIs are loading — same columns prop, same count as the real tiles. Switch to real StatCard once data resolves.",
|
|
67
|
+
"The grid uses CSS container queries, not viewport media queries — it responds to its containing block width, not the window. Ensure the container is not artificially constrained (e.g. inside a narrow SplitPane column) or column expansion will never trigger."
|
|
68
|
+
],
|
|
69
|
+
"useCases": [
|
|
70
|
+
"Dashboard KPI row: rendering 3–4 StatCard tiles (revenue, member count, active invoices, overdue amount) that reflow to a 2-column stacked grid on tablet and a single column on mobile.",
|
|
71
|
+
"Summary header above a list page: a 2-column grid of two StatCard totals (e.g. total payable vs total paid) sitting above a Toolbar and DataTable.",
|
|
72
|
+
"Accounting period overview: 4 StatCard tiles (opening balance, total debits, total credits, closing balance) that collapse gracefully on narrow viewports without any custom CSS.",
|
|
73
|
+
"Loading state for a KPI row: identical <ResponsiveGrid columns={4}> wrapping four <SkeletonStat /> placeholders rendered while async data is in flight, swapped for real StatCard tiles once resolved.",
|
|
74
|
+
"Settings or profile summary cards: 2- or 3-column grid of Card+CardContent blocks (not StatCard) showing categorized read-only data groups before a detail form below.",
|
|
75
|
+
"Entity comparison panel: a columns={3} grid comparing three legal entities side-by-side with a Card+CardContent per entity, which collapses to 2-up on tablet and stacks on mobile."
|
|
76
|
+
]
|
|
77
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"example": "import { Reveal } from \"@godxjp/ui/general\";\nimport { Card, CardContent } from \"@godxjp/ui/data-display\";\n\n// single entrance\n<Reveal>\n <Card><CardContent>…</CardContent></Card>\n</Reveal>\n\n// staggered column\n{items.map((item, i) => (\n <Reveal key={item.id} delay={Math.min(i + 1, 6) as 1 | 2 | 3 | 4 | 5 | 6}>\n <Card><CardContent>{item.label}</CardContent></Card>\n </Reveal>\n))}\n\n// scroll reveal — same component, the trigger moved to the viewport\n<Reveal on=\"view\">\n <Card><CardContent>…</CardContent></Card>\n</Reveal>\n\n// wait until a quarter of the section is visible, and replay on every re-entry\n<Reveal on=\"view\" amount={0.25} once={false}>\n <Card><CardContent>…</CardContent></Card>\n</Reveal>",
|
|
3
|
+
"group": "general",
|
|
4
|
+
"importPath": "@godxjp/ui/general",
|
|
5
|
+
"name": "Reveal",
|
|
6
|
+
"props": [
|
|
7
|
+
{
|
|
8
|
+
"description": "Content to reveal on enter.",
|
|
9
|
+
"name": "children",
|
|
10
|
+
"required": true,
|
|
11
|
+
"type": "ReactNode"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"defaultValue": "0",
|
|
15
|
+
"description": "Stagger ordinal — an INDEX into the motion ladder, never a raw ms. Each step adds one `--reveal-stagger-step` of delay so a column of reveals cascades. 0 = enter immediately.",
|
|
16
|
+
"name": "delay",
|
|
17
|
+
"type": "0 | 1 | 2 | 3 | 4 | 5 | 6"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"defaultValue": "\"mount\"",
|
|
21
|
+
"description": "What STARTS the entrance. `mount` is the historical behaviour (plays as soon as the element renders), so existing call sites are unchanged. `view` holds the entrance until the element reaches the viewport. A trigger, not a second component — same keyframes, same tokens, same reduced-motion contract, which is why there is no `ScrollReveal` here.",
|
|
22
|
+
"name": "on",
|
|
23
|
+
"type": "\"mount\" | \"view\""
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"defaultValue": "true",
|
|
27
|
+
"description": "on=\"view\" only. Reveal once and never re-hide. Default `true` because a Reveal is a ONE-SHOT entrance under either trigger — this keeps `view` semantically identical to `mount`. (Motion's useInView, where the prop comes from, defaults it to `false`; that is the neutral reading for a general-purpose observer, not for an entrance primitive.) `false` replays the entrance on every re-entry.",
|
|
28
|
+
"name": "once",
|
|
29
|
+
"type": "boolean"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"defaultValue": "\"some\"",
|
|
33
|
+
"description": "on=\"view\" only. How much of the element must be visible before it reveals — Motion's `amount`, name and type unchanged, mapped to IntersectionObserver `threshold` the same way (`some` → 0, `all` → 1, a number passes through). `all` is clamped to the most the element's own box can attain inside the viewport, because the ratio is measured against the TARGET's box: without the clamp a section taller than the viewport could never reach 1 and would stay hidden forever.",
|
|
34
|
+
"name": "amount",
|
|
35
|
+
"type": "\"some\" | \"all\" | number"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"defaultValue": "false",
|
|
39
|
+
"description": "Merge the reveal onto the single child element (no wrapper <div>) — use when an extra box would break a grid/flex layout. Under `on=\"view\"` the observed box is that child's own.",
|
|
40
|
+
"name": "asChild",
|
|
41
|
+
"type": "boolean"
|
|
42
|
+
}
|
|
43
|
+
],
|
|
44
|
+
"related": [
|
|
45
|
+
"AuthShell — pairs with Reveal for the auth card entrance; AuthShell delegates all motion to Reveal.",
|
|
46
|
+
"Card — the most common thing to wrap in <Reveal> (dashboard cards, auth card, settings sections).",
|
|
47
|
+
"ResponsiveGrid — combine with `<Reveal delay={n}>` (or `asChild`) per grid item for a staggered grid reveal."
|
|
48
|
+
],
|
|
49
|
+
"rules": [],
|
|
50
|
+
"storyPath": "general/Reveal.stories.tsx",
|
|
51
|
+
"tagline": "The official entrance-motion primitive (staggered fade-up) — reads DS motion tokens and honours prefers-reduced-motion, replacing hand-rolled @keyframes + .app-reveal/.d1..d6 classes.",
|
|
52
|
+
"usage": [
|
|
53
|
+
"DO use <Reveal> INSTEAD of hand-rolling `@keyframes auth-fade-up` + `.app-reveal` + `.d1..d6` in a consumer global.css — that repeats literal durations/delays and violates the tokens-only rule. Reveal reads `--duration-slow` / `--ease-emphasized` / `--reveal-distance` / `--reveal-stagger-step`.",
|
|
54
|
+
"DO use `on=\"view\"` for a scroll reveal INSTEAD of adding a scroll listener or a second component. It is the same primitive with the trigger moved; the library deliberately ships no ScrollReveal/AnimateOnScroll, and no animation runtime.",
|
|
55
|
+
"DO NOT gate your own visibility on an observer. `on=\"view\"` never does: the stylesheet's resting state is the finished, fully visible one, and only a mounted component holding a live IntersectionObserver writes the hidden state. A server render, jsdom, a browser without the API, and `prefers-reduced-motion: reduce` (under which NO observer is attached at all) therefore all show the content.",
|
|
56
|
+
"DO reach for `amount` rather than a margin when a reveal should wait — the default `\"some\"` fires on the first visible pixel (threshold 0), which is the earliest and never reads as late. There is deliberately no `margin`/`rootMargin` prop: it accepts px/% only, so it could not read a design token.",
|
|
57
|
+
"DO stagger a list/column by passing an increasing `delay` (1, 2, 3…) to successive siblings — the ordinal maps to `--reveal-stagger-step`, so a service retunes the cascade rhythm from one token.",
|
|
58
|
+
"DO pass `asChild` when wrapping an element that must keep its own box in a grid/flex row (the reveal merges onto that element instead of adding a <div>).",
|
|
59
|
+
"DO rely on the built-in reduced-motion behaviour — under `prefers-reduced-motion: reduce` the animation is dropped and content renders in its final, fully-visible position with no layout shift. Never gate visibility on the animation.",
|
|
60
|
+
"DO NOT set a raw ms delay or a literal translate distance — the whole point is that `delay` is a controlled ordinal and the distance/duration come from tokens."
|
|
61
|
+
],
|
|
62
|
+
"useCases": [
|
|
63
|
+
"Auth card entrance: `<AuthShell><Reveal><Card/></Reveal></AuthShell>` — the sign-in card fades up on load, respecting reduced-motion.",
|
|
64
|
+
"Staggered dashboard: map stat cards with `<Reveal delay={i + 1}>` so the row cascades in.",
|
|
65
|
+
"Section reveal on a settings/detail page — wrap each Card in <Reveal> for a calm entrance without hand-written CSS.",
|
|
66
|
+
"asChild on a grid item: `<Reveal asChild delay={2}><ResponsiveGrid.Item/></Reveal>` keeps the grid cell intact while animating it in.",
|
|
67
|
+
"Long marketing/landing page: `<Reveal on=\"view\">` per section so each band enters as the reader reaches it, instead of all of them firing above the fold on load.",
|
|
68
|
+
"A long feed or report where only the first screen should animate on load: `<Reveal on=\"view\" amount={0.25}>` — a quarter visible before the entrance starts."
|
|
69
|
+
]
|
|
70
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
{
|
|
2
|
+
"example": "import { Card, CardContent, ScrollArea } from \"@godxjp/ui/data-display\";\nimport { Text } from \"@godxjp/ui/general\";\nimport { Flex } from \"@godxjp/ui/layout\";\n\n// Vertical-only (default). The FRAME is a Card, not a rounded-md border — a hand-rolled surface\n// freezes the radius and the border colour at the call site, where no theme can reach them. The\n// bounded height stays a class because it is a measurement of this SCREEN, not a shape of the\n// component.\n<Card>\n <CardContent flush>\n <ScrollArea className=\"h-64\">\n <Flex direction=\"col\" gap=\"sm\" pad={4}>\n {entries.map((entry) => (\n <Text key={entry.id} size=\"sm\">{entry.label}</Text>\n ))}\n </Flex>\n </ScrollArea>\n </CardContent>\n</Card>\n\n// Horizontal + vertical (e.g. wide Cascader columns). Use border-e, never border-r: a physical\n// edge puts the rule on the wrong side under RTL.\n<ScrollArea className=\"w-full\" orientation=\"both\">\n <Flex className=\"max-h-[min(280px,50vh)]\">\n {columns.map((col, i) => (\n <ul key={i} className=\"min-w-36 border-e last:border-e-0\">\n {col.map((item) => <li key={item.value}>{item.label}</li>)}\n </ul>\n ))}\n </Flex>\n</ScrollArea>\n\n// Live stream — pinned to the newest post, never yanked while reading history\nconst viewport = React.useRef<HTMLDivElement>(null);\nconst [atNewest, setAtNewest] = React.useState(true);\n\n<Card>\n <CardContent flush>\n <ScrollArea\n anchor=\"bottom\"\n viewportRef={viewport}\n onAnchoredChange={setAtNewest}\n className=\"h-64\"\n >\n <Flex direction=\"col\" pad={{ inline: 3 }}>\n {posts.map((post) => <PostRow key={post.id} post={post} />)}\n </Flex>\n </ScrollArea>\n </CardContent>\n</Card>\n<Button\n type=\"button\"\n disabled={atNewest}\n onClick={() => {\n const node = viewport.current;\n if (node) node.scrollTop = node.scrollHeight;\n }}\n>\n {t(\"chat.jumpToNewest\")}\n</Button>",
|
|
3
|
+
"group": "data-display",
|
|
4
|
+
"importPath": "@godxjp/ui/data-display",
|
|
5
|
+
"name": "ScrollArea",
|
|
6
|
+
"props": [
|
|
7
|
+
{
|
|
8
|
+
"description": "Extra classes applied to the root element. Must include a height constraint (h-*, max-h-*) — without one, the viewport expands to fit its content and no scrollbar is rendered.",
|
|
9
|
+
"name": "className",
|
|
10
|
+
"type": "string"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"description": "The content to make scrollable. Wrap it in a single element so the Viewport can measure its full size correctly.",
|
|
14
|
+
"name": "children",
|
|
15
|
+
"required": true,
|
|
16
|
+
"type": "React.ReactNode"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"description": "Rides through to the element as a plain attribute. Rarely needed: the element inherits the page's direction, and the component stamps NO direction of its own (the Radix root used to stamp `ltr`, which reset the inline axis for everything inside it on an RTL page).",
|
|
20
|
+
"name": "dir",
|
|
21
|
+
"type": "\"ltr\" | \"rtl\""
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"defaultValue": "\"vertical\"",
|
|
25
|
+
"description": "Axes that scroll — this IS the element's `overflow`, so an axis you do not ask for is `hidden` and its content is CLIPPED, not merely missing a bar. Use 'horizontal' for a strip of non-shrinking columns (a board, a lane of cards) — the element keeps its tab stop so the strip stays keyboard-scrollable, and the consumer writes no overflow class of its own. Pair it with a width constraint, exactly as the vertical case needs a height one. 'both' is the replacement for the pre-v23 shape of a vertical area that also mounted <ScrollBar orientation=\"horizontal\" />.",
|
|
26
|
+
"name": "orientation",
|
|
27
|
+
"type": "\"vertical\" | \"horizontal\" | \"both\""
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"description": "Ref to the element that actually SCROLLS. Since v23 that is the component's own element, so `ref` and `viewportRef` hand back the SAME node (before v23 `ref` pointed at an overflow:hidden root that never scrolled). Use it to read scrollTop/scrollHeight, call scrollTo(), restore a saved position or drive a 'jump to newest' button. `[data-radix-scroll-area-viewport]` no longer exists anywhere — query `[data-slot=\"scroll-area-viewport\"]` only if you truly cannot hold a ref.",
|
|
31
|
+
"name": "viewportRef",
|
|
32
|
+
"type": "React.Ref<HTMLDivElement>"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"defaultValue": "\"auto\"",
|
|
36
|
+
"description": "Whether the bar is ALWAYS drawn or left to the platform. `auto` styles whatever bar the platform decides to draw — and on macOS/iPadOS with the system default \"Show scroll bars: when scrolling\" that is an OVERLAY bar, present only while the reader is already scrolling, so a wide area does not look scrollable at rest (measured: clientWidth 727 of scrollWidth 3476, nothing occupying layout). `always` forces a classic bar that occupies layout, from the same `--scroll-area-*` tokens. Reach for it where the affordance IS the information (a wide table, a roster, a board); leave it `auto` for a chat stream.",
|
|
37
|
+
"name": "scrollbar",
|
|
38
|
+
"type": "\"auto\" | \"always\""
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"defaultValue": "\"none\"",
|
|
42
|
+
"description": "Edge the viewport sticks to as its content grows. 'none' leaves the scroll offset entirely alone (today's behaviour).",
|
|
43
|
+
"name": "anchor",
|
|
44
|
+
"type": "\"none\" | \"bottom\""
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"defaultValue": "--scroll-area-anchor-offset (3rem)",
|
|
48
|
+
"description": "Distance in px from the bottom edge inside which the reader still counts as 'following' for anchor=\"bottom\". Read from the --scroll-area-anchor-offset token at mount, so a theme moves it globally; this prop overrides it per instance. Size it to the row height the service renders (a dense log line vs a chat bubble with an avatar).",
|
|
49
|
+
"name": "anchorOffset",
|
|
50
|
+
"type": "number"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"description": "Fires when the pinned state flips — false when the reader scrolls away from the bottom, true when they return inside anchorOffset. Render a real focusable 'jump to newest' Button from it: anchoring must never be the only route back to new content.",
|
|
54
|
+
"name": "onAnchoredChange",
|
|
55
|
+
"type": "(anchored: boolean) => void"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"defaultValue": "the localized \"Scrollable region\"",
|
|
59
|
+
"description": "Accessible name for the scroll REGION — the tabindex=\"0\" element a keyboard user lands on to scroll content bigger than the box. Optional: left out it takes the localized dataDisplay.scrollArea.region default, so no consumer has to invent a name merely to stop shipping an anonymous focus stop (gh#821). Pass a plain string when the screen can say WHICH region ('Activity log'); a non-string node cannot be an aria-label and falls back to the default. The role, the name and the stop are all emitted only while there IS overflow on the axes `orientation` opens.",
|
|
60
|
+
"name": "label",
|
|
61
|
+
"type": "React.ReactNode"
|
|
62
|
+
}
|
|
63
|
+
],
|
|
64
|
+
"related": [
|
|
65
|
+
"DataTable — use DataTable (not ScrollArea) when data is tabular and needs sorting/selection; DataTable manages its own overflow internally.",
|
|
66
|
+
"Collapsible — use Collapsible to show/hide a section; pair with ScrollArea when the revealed content can itself overflow.",
|
|
67
|
+
"Card/CardContent — when the card body should scroll, put ScrollArea inside CardContent rather than applying overflow directly to CardContent."
|
|
68
|
+
],
|
|
69
|
+
"rules": [
|
|
70
|
+
2,
|
|
71
|
+
3,
|
|
72
|
+
24,
|
|
73
|
+
31
|
|
74
|
+
],
|
|
75
|
+
"storyPath": "data-display/ScrollArea.stories.tsx",
|
|
76
|
+
"subParts": [
|
|
77
|
+
"ScrollBar"
|
|
78
|
+
],
|
|
79
|
+
"tagline": "A native scrolling box (no Radix since v23): one `overflow: auto` element whose scrollbar is styled from --scroll-area-* tokens. Always set an explicit height/max-height, or nothing overflows and no scrollbar appears. Owns the scrolling element, so it also owns reaching it (viewportRef) and bottom anchoring for a live stream (anchor).",
|
|
80
|
+
"usage": [
|
|
81
|
+
"DO set an explicit height or max-height on ScrollArea via className (e.g. `className=\"h-64\"` or `className=\"max-h-[min(300px,50vh)]\"`). Without a height constraint the viewport grows to fit content and the scrollbar is never rendered.",
|
|
82
|
+
"DO wrap content in a single child element inside ScrollArea — the Viewport observes its single child's size to decide whether overflow exists.",
|
|
83
|
+
"DO open a second axis with `orientation=\"both\"`. `ScrollBar` still exports but RENDERS NOTHING — before v23 mounting it was what enabled an axis; now `orientation` is the only thing that does, so a leftover `<ScrollBar orientation=\"horizontal\" />` silently leaves that axis `hidden`.",
|
|
84
|
+
"DO reach for ScrollArea rather than your own `overflow-auto` div: it is the same native scrolling, plus the token-styled scrollbar, the keyboard tab stop the axe rule wants, and bottom anchoring. A bare overflow div gets none of that and each call site re-decides the scrollbar's look.",
|
|
85
|
+
"DON'T put ScrollArea inside a flex parent without giving it a `flex-1` or fixed size — it will collapse to zero height and appear broken.",
|
|
86
|
+
"For horizontal-only scrolling use `orientation=\"horizontal\"` with a width constraint; the content is allowed to grow past the box on that axis, so a row of non-shrinking columns overflows instead of squashing.",
|
|
87
|
+
"DO use `viewportRef` (or plain `ref`) when you need the scrolling element, never a `querySelector` for an internal attribute — `[data-radix-scroll-area-viewport]` is gone with Radix, and any selector matches the wrong node the moment two ScrollAreas nest.",
|
|
88
|
+
"DO build a live stream (chat, log tail, streaming response, activity feed) with `anchor=\"bottom\"` instead of writing `viewport.scrollTop = viewport.scrollHeight` in an effect. That naive version is the BUG, not the feature: it yanks a reader who has scrolled up back to the bottom on every arriving message (WCAG 3.2.5).",
|
|
89
|
+
"DO pair `anchor=\"bottom\"` with `onAnchoredChange` and a real Button (\"jump to newest\"). A keyboard user who has scrolled up needs a focusable route back to new content — the anchor alone is a pointer affordance.",
|
|
90
|
+
"DON'T remove the viewport's `tabIndex={0}`, and don't wrap the scrolling content in something that swallows arrow keys: the region must stay keyboard-scrollable (WCAG 2.1.1 / axe scrollable-region-focusable). The component measures its own overflow and emits the stop only while there is something to scroll, so you never have to withhold it yourself.",
|
|
91
|
+
"DO pass `label` on a scroll area the screen can NAME ('Activity log', 'Board'). The stop the keyboard user lands on carries role=\"group\" plus that name; without one it still announces the localized 'Scrollable region', never the anonymous stop it used to be (gh#821). `group`, not a landmark `region` — several named regions on one page collide under axe landmark-unique.",
|
|
92
|
+
"DON'T add an `aria-live` region to the ScrollArea to announce arriving items — a live region on a scroll container re-announces on reflow. Announce from your own small status region next to it.",
|
|
93
|
+
"Retune the stickiness once per service with the `--scroll-area-anchor-offset` token rather than passing `anchorOffset` on every instance."
|
|
94
|
+
],
|
|
95
|
+
"useCases": [
|
|
96
|
+
"Long dropdown lists inside Popovers or Selects where the list height must be capped (e.g. TreeSelect, Cascader columns, Combobox options).",
|
|
97
|
+
"Sidebar navigation panels or filter drawers whose content can exceed viewport height.",
|
|
98
|
+
"Transfer-list panels with a fixed height that must scroll through a large item list.",
|
|
99
|
+
"Cascader multi-column layouts where both axes may overflow (orientation=\"both\").",
|
|
100
|
+
"Detail panels or audit-log timelines inside a fixed-height Card that should not stretch the page.",
|
|
101
|
+
"Code or JSON viewers with a fixed max-height needing both axes scrollable.",
|
|
102
|
+
"Live message streams and log tails: `anchor=\"bottom\"` pins to the newest item while the reader is at the bottom, freezes when they scroll up to read history, and preserves their position when a page of older history is prepended."
|
|
103
|
+
]
|
|
104
|
+
}
|