@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,98 @@
|
|
|
1
|
+
{
|
|
2
|
+
"example": "import { SearchInput } from \"@godxjp/ui/data-entry\";\n\n<SearchInput placeholder=\"クーポン名・IDで検索\" value={search} onSearch={setSearch} />",
|
|
3
|
+
"group": "data-entry",
|
|
4
|
+
"importPath": "@godxjp/ui/data-entry",
|
|
5
|
+
"name": "SearchInput",
|
|
6
|
+
"props": [
|
|
7
|
+
{
|
|
8
|
+
"description": "Validation state the field paints — Ant Design `status`. `error` also reports `aria-invalid`, so the red boundary and what a screen reader hears are one fact; `warning` paints only, because a warning is not a validity failure. antd's `success`/`validating` are not implemented: antd only draws them together with its `hasFeedback` icon slot, which FormField owns here.",
|
|
9
|
+
"name": "status",
|
|
10
|
+
"type": "\"error\" | \"warning\""
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"defaultValue": "\"outlined\"",
|
|
14
|
+
"description": "Chrome level — Ant Design `variant`. `outlined` is the historical field; `filled` swaps the boundary for a tinted surface (dense forms); `borderless` drops both, for a field inside a box that already draws one. antd's fourth member `underlined` is deliberately absent — a single bottom rule is a Material convention and SmartHR, the JP authority here, draws every field as a full box.",
|
|
15
|
+
"name": "variant",
|
|
16
|
+
"type": "\"outlined\" | \"filled\" | \"borderless\""
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"description": "Called after a changed query settles. Never fires for the initial value on mount or callback-only rerenders. Optional when filtering uses onValueChange.",
|
|
20
|
+
"name": "onSearch",
|
|
21
|
+
"type": "(q: string) => void"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"description": "Controlled value.",
|
|
25
|
+
"name": "value",
|
|
26
|
+
"type": "string"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"defaultValue": "\"\"",
|
|
30
|
+
"description": "Initial uncontrolled value.",
|
|
31
|
+
"name": "defaultValue",
|
|
32
|
+
"type": "string"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"description": "Input placeholder.",
|
|
36
|
+
"name": "placeholder",
|
|
37
|
+
"type": "string"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"defaultValue": "250",
|
|
41
|
+
"description": "Debounce delay (ms).",
|
|
42
|
+
"name": "debounce",
|
|
43
|
+
"type": "number"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"description": "Input id; pair with `label` or an external `<label htmlFor>`.",
|
|
47
|
+
"name": "id",
|
|
48
|
+
"type": "string"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"description": "Optional visible label rendered above the search box (falls back to an sr-only label).",
|
|
52
|
+
"name": "label",
|
|
53
|
+
"type": "React.ReactNode"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"description": "Fires on EVERY keystroke (immediate) — required to keep a controlled `value` responsive.",
|
|
57
|
+
"name": "onValueChange",
|
|
58
|
+
"type": "(value: string) => void"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"description": "Accessible search name when there is no visible label.",
|
|
62
|
+
"name": "ariaLabel",
|
|
63
|
+
"type": "string"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"defaultValue": "false",
|
|
67
|
+
"description": "Disable search input and clearing.",
|
|
68
|
+
"name": "disabled",
|
|
69
|
+
"type": "boolean"
|
|
70
|
+
}
|
|
71
|
+
],
|
|
72
|
+
"related": [
|
|
73
|
+
"Input — use `Input` (inside `FormField`) when the search field is part of a submitted form and needs a `name` attribute, or when you need full `onChange` control without any debounce or clear button. SearchInput is the right pick when the field only triggers filtering, not form submission.",
|
|
74
|
+
"Toolbar — SearchInput is almost always placed as a direct child of `Toolbar`, which provides the surrounding strip, clear-all button, and active-filter state. Do not use SearchInput as a standalone header widget when a full filter strip (with selects etc.) already exists — compose them together.",
|
|
75
|
+
"Command — use `Command` + `CommandInput` when you need a keyboard-navigable command palette or combobox list with grouped items and keyboard selection. `Command` is only meaningful when paired with `CommandList`; SearchInput is the right pick for a plain filter box with no item-selection behavior.",
|
|
76
|
+
"Select (with showSearch) — when users must pick a value from a list AND search to narrow it, use `<Select options={...} showSearch>` (which has its own built-in search input). SearchInput is for filtering an external data set, not for value selection from an option list."
|
|
77
|
+
],
|
|
78
|
+
"rules": [
|
|
79
|
+
23
|
|
80
|
+
],
|
|
81
|
+
"storyPath": "data-entry/SearchInput.stories.tsx",
|
|
82
|
+
"tagline": "Debounced search box with a clear button. Fires onSearch (NOT onChange) after the debounce. Controlled (value) or uncontrolled (defaultValue).",
|
|
83
|
+
"usage": [
|
|
84
|
+
"DO: listen to `onSearch`, not `onChange`. The component debounces internally (default 250 ms) and fires `onSearch(q)` after the delay — never wire your filter logic to `onChange` on SearchInput because it does not expose one.",
|
|
85
|
+
"DO: choose controlled vs uncontrolled deliberately. Pass `value` + `onValueChange` together for controlled mode; optionally add `onSearch` for debounced effects (e.g. when search state lives in a URL param or shared parent). For local-only ephemeral search pass only `defaultValue` + `onSearch` — omitting `value` puts the component in uncontrolled mode.",
|
|
86
|
+
"DO: supply an `ariaLabel` (or visible `label`) when no adjacent label exists. Without either prop, SearchInput falls back to the i18n key `common.search` rendered as a visually-hidden `<Label>` — still accessible, but providing a context-specific string (e.g. `ariaLabel='請求書を検索'`) is more descriptive for screen readers.",
|
|
87
|
+
"DON'T: use SearchInput inside a `<form>` expecting native form submission. The component has no `name` prop and does not emit a form field value — it is a filter-trigger widget. For a form search field, use a plain `Input` inside `FormField`.",
|
|
88
|
+
"DON'T: hand-roll a debounced input when you need a search box. SearchInput ships the debounce, clear button (×), search icon, and accessible label — recreating these with a raw `<Input>` adds code and misses the UX contract.",
|
|
89
|
+
"DON'T: place SearchInput inside a `ToolbarGroup` wrapper — `ToolbarGroup` is for Select/DatePicker controls with a label chip. SearchInput goes directly as a child of `Toolbar` (or standalone above a table), not wrapped in `ToolbarGroup`."
|
|
90
|
+
],
|
|
91
|
+
"useCases": [
|
|
92
|
+
"List-page filter bar: placed as the first child of `Toolbar` (before any `ToolbarGroup` children) to drive text-based filtering of a `DataTable`. The `onSearch` callback updates a query param or state variable that the table's data fetch reads.",
|
|
93
|
+
"Inline client-side search over a small in-memory list (e.g. a sidebar nav list, a transfer panel, a settings category list) where results narrow immediately as the user types without a server round-trip — use uncontrolled mode (`defaultValue`) so no state is needed in the parent.",
|
|
94
|
+
"URL-synced search: controlled mode where `value` comes from `useSearchParams()` and `onSearch` pushes to the URL, enabling deep-linkable, bookmarkable filtered views on invoice/transaction/customer index pages.",
|
|
95
|
+
"Panel or dialog search: filtering a long dropdown list, a tree, or a multi-item selection panel that does not use the built-in `Command` palette — SearchInput provides the search box while the parent renders the filtered result set.",
|
|
96
|
+
"Toolbar search on a data-heavy accounting page (e.g. journal-entry search, partner lookup in a subledger view) where the 250 ms debounce prevents a flood of API calls on every keystroke without requiring the developer to implement debounce logic."
|
|
97
|
+
]
|
|
98
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
{
|
|
2
|
+
"example": "import { Segmented } from \"@godxjp/ui/data-entry\";\n\n<Segmented\n aria-label=\"Theme\"\n value={theme}\n onValueChange={(next) => setTheme(next as AppTheme)}\n options={[\n { value: \"light\", label: \"Light\" },\n { value: \"dark\", label: \"Dark\" },\n { value: \"system\", label: \"System\" },\n ]}\n/>\n\n<Segmented\n aria-label=\"Status filter\"\n value={status}\n onValueChange={setStatus}\n options={[\n { value: \"all\", label: \"All\", count: 128 },\n { value: \"active\", label: \"Active\", count: 96 },\n ]}\n/>",
|
|
3
|
+
"group": "data-entry",
|
|
4
|
+
"importPath": "@godxjp/ui/data-entry",
|
|
5
|
+
"name": "Segmented",
|
|
6
|
+
"props": [
|
|
7
|
+
{
|
|
8
|
+
"defaultValue": "false",
|
|
9
|
+
"description": "antd `block` — stretch the bar to its container and share the width EQUALLY between the choices, so the selected pill does not resize as the label under it changes length.",
|
|
10
|
+
"name": "block",
|
|
11
|
+
"type": "boolean"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"defaultValue": "false",
|
|
15
|
+
"description": "antd `vertical` — stack the choices in a column. It changes the ARROW KEYS as well as the layout: the primitive reads `orientation` to decide which arrows move the roving focus.",
|
|
16
|
+
"name": "vertical",
|
|
17
|
+
"type": "boolean"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"defaultValue": "\"md\"",
|
|
21
|
+
"description": "Control height tier — reads the shared `--control-height` ladder: xs 24px · sm 28px · md 32px · lg 36px. The TRACK measures exactly one control height, so the bar sits level with an Input, a Button or a ToggleGroup of the same step on the same row. Pick the step the ROW already has: xs (gh#719) is the one that fits a 24px-dense toolbar or audit-log row — before it, that row could only get a hand-rolled set of Buttons, which loses the radiogroup semantics and the arrow keys. xs is the DENSE step, not a smaller default: the track spends its 2px inset at every step, so the individual SEGMENT measures 20px and clears WCAG 2.2 SC 2.5.8 through the Spacing exception (adjacent segments 26.47px apart for a one-glyph label) rather than the 24px minimum. Keep labels at a glyph or more, and stay on sm or md wherever the row height is yours to choose.",
|
|
22
|
+
"name": "size",
|
|
23
|
+
"type": "\"xs\" | \"sm\" | \"md\" | \"lg\""
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"description": "The closed set of choices, in reading order. `label` is the visible content AND the item's accessible name. Use `count` for a filter total — do not nest `Badge` in `label` (gh#602).",
|
|
27
|
+
"name": "options",
|
|
28
|
+
"type": "{ value: string; label: ReactNode; icon?: ReactNode; disabled?: boolean; count?: number | string; overflowCount?: number; showZero?: boolean; countLabel?: string }[]"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"description": "Controlled selection.",
|
|
32
|
+
"name": "value",
|
|
33
|
+
"type": "string"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"description": "Uncontrolled initial selection.",
|
|
37
|
+
"name": "defaultValue",
|
|
38
|
+
"type": "string"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"description": "Selection callback.",
|
|
42
|
+
"name": "onValueChange",
|
|
43
|
+
"type": "(value: string) => void"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"description": "Disable the whole group.",
|
|
47
|
+
"name": "disabled",
|
|
48
|
+
"type": "boolean"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"description": "Form field name — submits the selected value with a native form.",
|
|
52
|
+
"name": "name",
|
|
53
|
+
"type": "string"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"description": "Root id, for a label that points at it.",
|
|
57
|
+
"name": "id",
|
|
58
|
+
"type": "string"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"description": "Root class override.",
|
|
62
|
+
"name": "className",
|
|
63
|
+
"type": "string"
|
|
64
|
+
}
|
|
65
|
+
],
|
|
66
|
+
"related": [
|
|
67
|
+
"ToggleGroup (independently pressed buttons, or a multi-select toolbar)",
|
|
68
|
+
"RadioGroup (the same semantics with a vertical, described list)",
|
|
69
|
+
"Select (the same choice when the set is long or hidden by default)",
|
|
70
|
+
"Tabs (switches PANELS, not a value)"
|
|
71
|
+
],
|
|
72
|
+
"rules": [
|
|
73
|
+
3,
|
|
74
|
+
6,
|
|
75
|
+
23
|
|
76
|
+
],
|
|
77
|
+
"storyPath": "data-entry/Segmented.stories.tsx",
|
|
78
|
+
"tagline": "One-of-N from a small, closed, always-visible set — the enterprise Segmented / filter bar drawn on react-aria-components' RadioGroup. A track with the chosen item as a lifted slab. Reach for it INSTEAD OF a Select when there are 2-4 options and all of them fit on screen, and instead of ToggleGroup when exactly one must always be chosen.",
|
|
79
|
+
"usage": [
|
|
80
|
+
"DO use it for a closed set of 2-4 peer choices that are cheap to show — theme, view mode, a date range preset.",
|
|
81
|
+
"DO give it an aria-label (or aria-labelledby) — the group needs a name, and each item takes its own from its label.",
|
|
82
|
+
"DON'T use ToggleGroup for a one-of-N choice unless you also pass `disallowEmptySelection`: without it, type=single is a row of aria-pressed toggle buttons that can end up with nothing selected, which a setting can never be (gh#744). Segmented is the one-of-N control and needs no such switch.",
|
|
83
|
+
"DON'T use it past ~4 options — that is a Select. Up to four, a horizontal track WRAPS to a second row when its options cannot share one (a phone-width status filter with counts), so no label or count is truncated while its item fits on a row. `block` is the exception: it promises EQUAL widths, so it still truncates — don't use `block` for four labelled options at phone width.",
|
|
84
|
+
"DO show a short mark and speak a long name by putting BOTH in `label`: an aria-hidden span for the glyph and a VisuallyHidden for the words. `label` is a ReactNode, the item takes its accessible name from its content, and the glyph drops out of that name once it is aria-hidden — so a bar of circle/triangle/cross marks still announces the state in words. There is no separate accessible-name prop and there does not need to be.",
|
|
85
|
+
"DO pass per-option totals via `count` — the DS paints an opaque pill that reads on both the recessed track and the selected slab. Do not put `Badge` in `label` for counts: `secondary` is `--muted`, which is the track fill (1.00:1, gh#602).",
|
|
86
|
+
"DO stack with `vertical` when the labels are too long to sit side by side: a stacked row is a WHOLE `--control-height` tall, where a horizontal bar spends part of that height on the track padding so the bar as a whole lines up with an Input beside it. Inside a MobileShell, which scopes the control tier to the touch step, that is what makes each row a 44px target.",
|
|
87
|
+
"DO remember that `size` and any scoped `--control-height` both reach the track: the item height is composed on the Segmented root, not frozen at :root.",
|
|
88
|
+
"DO set `size=\"xs\"` for a 24px-dense row — an audit-log toolbar, a table header strip, a row that already carries `<Button size=\"xs\">` or an `xs` ToggleGroup. All three measure 24px off the same `--control-height-xs` step, so the row stays level, and the label type and the item inline padding step down with the band (gh#719). DON'T hand-roll that row out of Buttons to get the height: a segmented control is a radiogroup, and a row of buttons loses the arrow keys and the \"1 of 3, selected\" announcement."
|
|
89
|
+
],
|
|
90
|
+
"useCases": [
|
|
91
|
+
"Theme switch (light / dark / system)",
|
|
92
|
+
"List vs board vs calendar view mode",
|
|
93
|
+
"Chart range: day / week / month",
|
|
94
|
+
"Status filter above a list, each option carrying its count (All 128 / Active 96 / Pending 12 / Archived 0)"
|
|
95
|
+
]
|
|
96
|
+
}
|
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
{
|
|
2
|
+
"absorbed": [
|
|
3
|
+
"Combobox",
|
|
4
|
+
"Autocomplete",
|
|
5
|
+
"CountrySelect",
|
|
6
|
+
"SearchSelect",
|
|
7
|
+
"Typeahead",
|
|
8
|
+
"AsyncSelect"
|
|
9
|
+
],
|
|
10
|
+
"example": "import {\n Select,\n SelectContent,\n SelectGroup,\n SelectItem,\n SelectLabel,\n SelectSeparator,\n SelectTrigger,\n SelectValue,\n} from \"@godxjp/ui/data-entry\";\nimport { Text } from \"@godxjp/ui/general\";\n// as=\"span\" on both: an option row is a phrasing context, so a <div> would be invalid HTML.\nimport { Flex } from \"@godxjp/ui/layout\";\n\n// ── 1. Data-driven (Ant-style) — static list, no search ──────────────────────\nexport function StatusSelect({ value, onChange }) {\n return (\n <Select\n value={value}\n onValueChange={onChange}\n options={[\n { value: \"draft\", label: \"Draft\" },\n { value: \"sent\", label: \"Sent\" },\n { value: \"paid\", label: \"Paid\" },\n { value: \"overdue\", label: \"Overdue\" },\n ]}\n placeholder=\"Select status\"\n name=\"status\"\n id=\"status\"\n />\n );\n}\n\n// ── 2. Data-driven, searchable static list with groups ────────────────────────\nexport function CurrencySelect({ value, onChange }) {\n return (\n <Select\n value={value}\n onValueChange={onChange}\n showSearch\n options={[\n { value: \"JPY\", label: \"Japanese Yen\", group: \"Asia\" },\n { value: \"VND\", label: \"Vietnamese Dong\", group: \"Asia\" },\n { value: \"EUR\", label: \"Euro\", group: \"Europe\" },\n { value: \"GBP\", label: \"Pound Sterling\", group: \"Europe\" },\n ]}\n placeholder=\"Select currency\"\n searchPlaceholder=\"Search currencies…\"\n name=\"currency\"\n />\n );\n}\n\n// ── 3. Data-driven, async (loadOptions) ──────────────────────────────────────\nexport function AccountSelect({ value, onChange, selectedLabel }) {\n async function loadOptions({ query, page }) {\n const res = await fetch(`/api/accounts?q=${query}&page=${page}`);\n const json = await res.json();\n return { options: json.data, hasMore: json.hasMore };\n }\n return (\n <Select\n value={value}\n onValueChange={onChange}\n loadOptions={loadOptions}\n selectedLabel={selectedLabel}\n placeholder=\"Search accounts…\"\n renderOption={(opt) => (\n <Flex as=\"span\" align=\"center\" gap=\"xs\">\n <Text as=\"span\" tone=\"muted\" mono>\n {opt.value}\n </Text>\n {opt.label}\n </Flex>\n )}\n name=\"account_id\"\n />\n );\n}\n\n// ── 4. Compound API — custom trigger content ──────────────────────────────────\nexport function PrioritySelect({ value, onValueChange }) {\n return (\n <Select value={value} onValueChange={onValueChange}>\n <SelectTrigger size=\"sm\" id=\"priority\">\n <SelectValue placeholder=\"Priority\" />\n </SelectTrigger>\n <SelectContent>\n <SelectGroup>\n <SelectLabel>Urgency</SelectLabel>\n <SelectItem value=\"high\">High</SelectItem>\n <SelectItem value=\"medium\">Medium</SelectItem>\n </SelectGroup>\n <SelectSeparator />\n <SelectItem value=\"low\">Low</SelectItem>\n </SelectContent>\n </Select>\n );\n}",
|
|
11
|
+
"group": "data-entry",
|
|
12
|
+
"importPath": "@godxjp/ui/data-entry",
|
|
13
|
+
"name": "Select",
|
|
14
|
+
"props": [
|
|
15
|
+
{
|
|
16
|
+
"description": "Select several options; value/defaultValue become string arrays. `tags` additionally ACCEPTS what was typed (a value that is not in the list), which is antd's own split between the two. The popup list of either mode renders as `Command split` (rows ruled, list padding 0, gh#699); retune it with --command-item-divider-color / --command-item-divider-width.",
|
|
17
|
+
"name": "mode",
|
|
18
|
+
"type": "\"multiple\" | \"tags\""
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"description": "Maximum selected options; selected items remain removable.",
|
|
22
|
+
"name": "maxCount",
|
|
23
|
+
"type": "number"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"description": "Collapse extra selected labels.",
|
|
27
|
+
"name": "maxTagCount",
|
|
28
|
+
"type": "number"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"description": "Static option list. Passing this (or loadOptions) switches Select from the compound API to the data-driven API. An entry may be one of antd's nested GROUPS ({ label, options }) instead of a row; its heading becomes the same `group` a flat row carries. Rows in a foreign shape ({id,name}) go through fieldNames. Each option has { value, label, sublabel?, icon?, group?, disabled? }. `icon` (avatar / flag / lucide node) renders before the label in the rows AND on the trigger once selected. group buckets the option under an optgroup-style heading.",
|
|
32
|
+
"name": "options",
|
|
33
|
+
"type": "(SearchSelectOptionProp | { label: string; options: SearchSelectOptionProp[] })[]"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"description": "Async remote fetcher. Receives { query, page } (1-based). Must return { options, hasMore? }. Implies showSearch=true automatically.",
|
|
37
|
+
"name": "loadOptions",
|
|
38
|
+
"type": "(params: SearchSelectLoadParamsProp) => Promise<SearchSelectLoadResultProp>"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"defaultValue": "true when loadOptions is set or mode is multiple/tags, false otherwise",
|
|
42
|
+
"description": "Toggle the searchable combobox mode vs the plain listbox. The OBJECT form is antd's and configures the search in one place (passing it also turns search ON). NOTE the argument order: `showSearch.filterOption(input, option)` is antd's, while the long-standing top-level `filterOption(option, query)` keeps this library's; `filterOption: false` keeps every row, for a list the server already filtered.",
|
|
43
|
+
"name": "showSearch",
|
|
44
|
+
"type": "boolean | { filterOption?: boolean | ((input, option) => boolean); optionFilterProp?: \"label\" | \"value\" | \"sublabel\"; filterSort?; searchValue?: string; onSearch?: (value: string) => void; autoClearSearchValue?: boolean }"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"defaultValue": "\"\"",
|
|
48
|
+
"description": "Controlled selected value (data-driven API). Pass an empty string to represent no selection.",
|
|
49
|
+
"name": "value",
|
|
50
|
+
"type": "string"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"description": "Uncontrolled initial value (data-driven API). The trigger shows the matching option's label at rest — including in searchable (showSearch) mode — so an edit form pre-filled from server data renders the label, not the placeholder. Selected option is marked by a background tint (no check icon).",
|
|
54
|
+
"name": "defaultValue",
|
|
55
|
+
"type": "string"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"description": "Change handler for the data-driven API. Receives the new value string and the matching option object. The signature follows `mode` / `labelInValue`, and a bare `(value, option) => …` is inferred for each (no annotation needed under `strict`, gh#679): single `string` / `SelectOption | undefined`; `mode=\"multiple\" | \"tags\"` `string[]` / `SelectOption[] | undefined`; `labelInValue` the `{ value, label }` shapes.",
|
|
59
|
+
"name": "onValueChange",
|
|
60
|
+
"type": "(value: string, option?: SearchSelectOptionProp) => void"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"description": "Custom per-option renderer for the dropdown ROWS. Defaults to label + optional sublabel. Does not change the trigger — use `labelRender` for that.",
|
|
64
|
+
"name": "renderOption",
|
|
65
|
+
"type": "(option: SearchSelectOptionProp) => React.ReactNode"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"description": "Custom renderer for the SELECTED value shown on the TRIGGER (`labelRender`) — avatar + name + role badge, etc. `option` is undefined for an async preset whose page hasn't loaded. Only used while a value is selected; the placeholder still shows when empty.",
|
|
69
|
+
"name": "labelRender",
|
|
70
|
+
"type": "(selected: { value: string; label: React.ReactNode; option?: SearchSelectOptionProp }) => React.ReactNode"
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"description": "Display label for the current value when its option is not in the loaded page (async). Prevents a flash of the raw id.",
|
|
74
|
+
"name": "selectedLabel",
|
|
75
|
+
"type": "string"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"description": "Leading icon shown on the trigger for the current value when its option isn't loaded yet (async preset) — the trigger counterpart of `selectedLabel`, so an edit form pre-filled from the server shows the avatar/flag at rest.",
|
|
79
|
+
"name": "selectedIcon",
|
|
80
|
+
"type": "React.ReactNode"
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"description": "Placeholder shown in the trigger when no value is selected.",
|
|
84
|
+
"name": "placeholder",
|
|
85
|
+
"type": "string"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"description": "Placeholder inside the search input (combobox mode only).",
|
|
89
|
+
"name": "searchPlaceholder",
|
|
90
|
+
"type": "string"
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"description": "Message rendered when the filtered list is empty.",
|
|
94
|
+
"name": "emptyMessage",
|
|
95
|
+
"type": "string"
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"description": "Message rendered while loadOptions is resolving.",
|
|
99
|
+
"name": "loadingMessage",
|
|
100
|
+
"type": "string"
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"description": "Message rendered when an async loadOptions REJECTS — a distinct state from empty/loading. Defaults to a localized 'Couldn’t load options'. The panel shows this instead of a blank surface or a misleading 'no results'.",
|
|
104
|
+
"name": "errorMessage",
|
|
105
|
+
"type": "string"
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"defaultValue": "false",
|
|
109
|
+
"description": "Show the clear ✕ when a value is selected (data-driven API). Off by default, as antd `allowClear` — a required select is never one click from empty. Pass it (or `allowClear`) on an optional field whose empty state is a valid answer.",
|
|
110
|
+
"name": "clearable",
|
|
111
|
+
"type": "boolean"
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"description": "Label for the clear row (data-driven combobox mode).",
|
|
115
|
+
"name": "clearLabel",
|
|
116
|
+
"type": "string"
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"description": "Disables the entire select.",
|
|
120
|
+
"name": "disabled",
|
|
121
|
+
"type": "boolean"
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"defaultValue": "false",
|
|
125
|
+
"description": "Searchable mode only (showSearch/loadOptions). Value is shown (and the clear affordance hidden) but the popover cannot be opened — no new pick, no search. Mirrors the Input/NumberInput readOnly contract: stays focusable and still submits its value, unlike disabled.",
|
|
126
|
+
"name": "readOnly",
|
|
127
|
+
"type": "boolean"
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
"description": "Searchable mode only. Height tier forwarded to the SearchSelect trigger Button. For the compound API use SelectTrigger's own size prop instead (below).",
|
|
131
|
+
"name": "size",
|
|
132
|
+
"type": "\"xs\" | \"sm\" | \"md\" | \"lg\""
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
"defaultValue": "\"full\"",
|
|
136
|
+
"description": "Trigger width on the data-driven API (options / loadOptions), searchable or not — the same axis SelectTrigger carries on the compound API. `full` is what a form field wants; `auto` is what a filter bar wants, so two Selects share one row instead of stacking (CONSUMER-RULES rule 5); `bounded` holds one width from --control-bounded-width for a trigger whose value varies in length. Never wrap a Select in a fixed-width Flex to get this.",
|
|
137
|
+
"name": "width",
|
|
138
|
+
"type": "\"full\" | \"auto\" | \"bounded\""
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"description": "Searchable mode only. Controlled popover open state (uncontrolled by default). Pair with onOpenChange.",
|
|
142
|
+
"name": "open",
|
|
143
|
+
"type": "boolean"
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
"description": "Searchable mode only. Fires on every open/close attempt — including ones ignored because open is externally pinned — so a controlled consumer stays in sync.",
|
|
147
|
+
"name": "onOpenChange",
|
|
148
|
+
"type": "(open: boolean) => void"
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
"description": "Searchable mode only. Controlled search-box query (uncontrolled by default). Pair with onSearchChange.",
|
|
152
|
+
"name": "search",
|
|
153
|
+
"type": "string"
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
"description": "Searchable mode only. Fires on every keystroke in the search box.",
|
|
157
|
+
"name": "onSearchChange",
|
|
158
|
+
"type": "(query: string) => void"
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
"description": "Searchable mode only, static options (ignored with loadOptions, which owns its own server-side filtering). Overrides the default label/value substring filter. Only consulted while the query is non-empty.",
|
|
162
|
+
"name": "filterOption",
|
|
163
|
+
"type": "(option: SearchSelectOptionProp, query: string) => boolean"
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
"description": "Searchable mode only. Custom error slot, overriding the default errorMessage row. retry() reloads from the first page.",
|
|
167
|
+
"name": "renderError",
|
|
168
|
+
"type": "(params: { message: string; retry: () => void }) => React.ReactNode"
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
"description": "Searchable mode only. Custom 'load more' affordance appended below the list while another page is available — pairs with (does not replace) the built-in scroll-triggered pagination.",
|
|
172
|
+
"name": "renderLoadMore",
|
|
173
|
+
"type": "(params: { hasMore: boolean; loading: boolean; loadMore: () => void }) => React.ReactNode"
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
"description": "Form field name. Submits the selected value via a hidden input (data-driven API). Required for uncontrolled form submission.",
|
|
177
|
+
"name": "name",
|
|
178
|
+
"type": "string"
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
"description": "HTML id for the trigger element. Wire to a <label htmlFor> for a11y.",
|
|
182
|
+
"name": "id",
|
|
183
|
+
"type": "string"
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
"description": "Additional CSS classes applied to the trigger.",
|
|
187
|
+
"name": "className",
|
|
188
|
+
"type": "string"
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
"description": "Test id on the trigger. Option items get ${data-testid}-option-${value} automatically.",
|
|
192
|
+
"name": "data-testid",
|
|
193
|
+
"type": "string"
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
"defaultValue": "\"md\"",
|
|
197
|
+
"description": "Compound API only. Size variant on the SelectTrigger sub-component.",
|
|
198
|
+
"name": "SelectTrigger size",
|
|
199
|
+
"type": "\"sm\" | \"md\""
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
"defaultValue": "\"full\"",
|
|
203
|
+
"description": "Compound API only. `full` fills the field column (inside FormField). `auto` sizes the trigger to its label — use it in a PageContainer `extra` slot, a toolbar or a footer row, where a full-width trigger swallows the row and truncates its siblings.",
|
|
204
|
+
"name": "SelectTrigger width",
|
|
205
|
+
"type": "\"full\" | \"auto\""
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
"defaultValue": "true",
|
|
209
|
+
"description": "Compound API only. Set false to omit the built-in chevron disclosure indicator from the DOM entirely (not a CSS hide) — for specialized triggers (icon-only, etc.) that render their own affordance, so no consumer descendant CSS is needed.",
|
|
210
|
+
"name": "SelectTrigger showIndicator",
|
|
211
|
+
"type": "boolean"
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
"description": "antd `status`. `error` recolours the control AND sets aria-invalid (a colour-only error fails WCAG 2.2 SC 1.4.1); `warning` recolours only. An aria-invalid injected by FormField always wins.",
|
|
215
|
+
"name": "status",
|
|
216
|
+
"type": "\"error\" | \"warning\""
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
"description": "antd `variant` — the control surface. Default `outlined`. Drawn from --control-{surface,filled,borderless}-* tokens, so a theme retunes all three at once.",
|
|
220
|
+
"name": "variant",
|
|
221
|
+
"type": "\"outlined\" | \"filled\" | \"borderless\""
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
"description": "antd `loading` — the FIELD is in flight: the trailing chevron becomes a spinner and the trigger reports aria-busy. Distinct from the list-level spinner an async loadOptions shows inside the popup.",
|
|
225
|
+
"name": "loading",
|
|
226
|
+
"type": "boolean"
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
"description": "antd `defaultOpen` — uncontrolled initial popup state. The popup opens once every running ancestor animation has finished (a Popover sliding in), so inside a Popover the listbox is placed below its settled trigger exactly like a click-open (gh#708); with nothing animating it is open on the first render. onOpenChange is not called for this initial open.",
|
|
230
|
+
"name": "defaultOpen",
|
|
231
|
+
"type": "boolean"
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
"defaultValue": "false",
|
|
235
|
+
"description": "antd `allowClear` — the clear ✕ on the trigger while a value is selected. Default false, as in antd. The object form replaces the ✕ icon and/or its accessible label. Beats `clearable` when both are given.",
|
|
236
|
+
"name": "allowClear",
|
|
237
|
+
"type": "boolean | { clearIcon?: React.ReactNode; label?: string }"
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
"description": "antd `onClear` — fires after the value is cleared through the ✕.",
|
|
241
|
+
"name": "onClear",
|
|
242
|
+
"type": "() => void"
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
"description": "antd `notFoundContent` — the node shown when the popup has nothing to list. Outranks the string-only emptyMessage.",
|
|
246
|
+
"name": "notFoundContent",
|
|
247
|
+
"type": "React.ReactNode"
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
"description": "antd `autoClearSearchValue` (default true) — clear the search box after a pick / on close. Set false to resume the same filtered list on the next open.",
|
|
251
|
+
"name": "autoClearSearchValue",
|
|
252
|
+
"type": "boolean"
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
"description": "antd `filterSort` — orders what filterOption kept. Static options only; with loadOptions the server owns the order. Never mutates the caller's array.",
|
|
256
|
+
"name": "filterSort",
|
|
257
|
+
"type": "(a: SearchSelectOptionProp, b: SearchSelectOptionProp, info: { searchValue: string }) => number"
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
"description": "antd `optionRender` — per-option renderer in antd's own (option, { index }) shape. Outranks the older renderOption.",
|
|
261
|
+
"name": "optionRender",
|
|
262
|
+
"type": "(option: SearchSelectOptionProp, info: { index: number }) => React.ReactNode"
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
"description": "antd `menuItemSelectedIcon` — a decorative mark on the picked row. Off by default: the picked row is already marked by fill + weight, which costs no width.",
|
|
266
|
+
"name": "menuItemSelectedIcon",
|
|
267
|
+
"type": "React.ReactNode"
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
"description": "antd `popupMatchSelectWidth`. true (default) pins the popup to the trigger width, false lets it hug its rows, a number pins it to that many pixels.",
|
|
271
|
+
"name": "popupMatchSelectWidth",
|
|
272
|
+
"type": "boolean | number"
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
"description": "antd `fieldNames` — read rows in a FOREIGN shape ({id,name,children}) without copying them into a second array first. The same spelling Cascader and TreeSelect take. A foreign row needs a cast at the call site, exactly as theirs does.",
|
|
276
|
+
"name": "fieldNames",
|
|
277
|
+
"type": "{ label?: string; value?: string; options?: string; groupLabel?: string; disabled?: string }"
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
"description": "antd `labelInValue` — value and onValueChange speak {value,label} instead of a bare string (an array of them in multiple/tags). It earns its place on the async EDIT form: a record holding {value:'52',label:'東京本社'} renders the pick immediately, with no options page loaded and no flash of the raw id.",
|
|
281
|
+
"name": "labelInValue",
|
|
282
|
+
"type": "boolean"
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
"description": "antd `prefix` — a node pinned BEFORE the value on the trigger (a currency mark, an icon). Deliberately not aria-hidden: for role=combobox the trigger's text is the VALUE, and a prefix that is part of the value belongs in it. The NAME still comes from the label.",
|
|
286
|
+
"name": "prefix",
|
|
287
|
+
"type": "React.ReactNode"
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
"description": "antd `suffixIcon` — replaces the trailing chevron; `null` removes the indicator entirely (antd's own replacement for the deprecated showArrow). While a value is clearable the ✕ owns that seat, as in antd.",
|
|
291
|
+
"name": "suffixIcon",
|
|
292
|
+
"type": "React.ReactNode"
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
"description": "antd `placement`, spelled on the LOGICAL inline axis (antd's bottomLeft/topRight cannot mirror for an RTL layout). Absent = below, start-aligned, with collision flipping — what a picker wants.",
|
|
296
|
+
"name": "placement",
|
|
297
|
+
"type": "\"bottomStart\" | \"bottomEnd\" | \"topStart\" | \"topEnd\""
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
"description": "antd `popupRender` — wrap the popup's own node to add a footer, a 'create' action or a hint line. It must still render originNode: dropping it leaves a popup with no options.",
|
|
301
|
+
"name": "popupRender",
|
|
302
|
+
"type": "(originNode: React.ReactNode) => React.ReactNode"
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
"description": "antd `listHeight` — the option list's max height in px for THIS instance. It overrides the --select-content-max-height token rather than hard-coding a height, so the token stays the default everywhere else.",
|
|
306
|
+
"name": "listHeight",
|
|
307
|
+
"type": "number"
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
"description": "antd `onPopupScroll` — fires on the option list's own scroll. It runs BESIDE the built-in infinite scroll (loadOptions paging), never instead of it.",
|
|
311
|
+
"name": "onPopupScroll",
|
|
312
|
+
"type": "(event: React.UIEvent<HTMLElement>) => void"
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
"description": "antd `optionFilterProp` — which field the default filter matches while searching. Unset matches BOTH label and value (this library's long-standing behaviour); antd's own default is value alone.",
|
|
316
|
+
"name": "optionFilterProp",
|
|
317
|
+
"type": "\"label\" | \"value\" | \"sublabel\""
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
"description": "antd `tokenSeparators` (multiple/tags) — characters that commit what has been typed. Typing or PASTING 'a,b,c' commits three values in ONE onValueChange. A pasted run is read off the clipboard, so a '\\n' separator works even though a single-line input strips newlines.",
|
|
321
|
+
"name": "tokenSeparators",
|
|
322
|
+
"type": "string[]"
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
"description": "antd `maxTagTextLength` (multiple/tags) — cut each chip's TEXT to this many characters (an ellipsis marks the cut). The value keeps its whole label.",
|
|
326
|
+
"name": "maxTagTextLength",
|
|
327
|
+
"type": "number"
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
"description": "antd `tagRender` (multiple/tags) — render one chip yourself. Exactly the shape TagInput's tagRender takes. The onClose handed in is the same remover the built-in ✕ calls, so a custom chip cannot end up unremovable; supplying tagRender withdraws the built-in ✕.",
|
|
331
|
+
"name": "tagRender",
|
|
332
|
+
"type": "(props: { value: string; label: React.ReactNode; onClose: () => void; index: number; disabled: boolean }) => React.ReactNode"
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
"description": "antd `onSelect` / `onDeselect` — fires as one option JOINS or LEAVES the selection, beside onValueChange (which reports the whole value).",
|
|
336
|
+
"name": "onSelect / onDeselect",
|
|
337
|
+
"type": "(value: string, option: SearchSelectOptionProp) => void"
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
"description": "antd `maxTagPlaceholder` — the node standing in for the values maxTagCount hid. A function receives the omitted values, so '+3 件' or a tooltip listing them is possible.",
|
|
341
|
+
"name": "maxTagPlaceholder",
|
|
342
|
+
"type": "React.ReactNode | ((omitted: { value: string; label: React.ReactNode }[]) => React.ReactNode)"
|
|
343
|
+
}
|
|
344
|
+
],
|
|
345
|
+
"related": [
|
|
346
|
+
"Segmented — the same choice when the option set is small and worth showing at once. Select hides its options behind a trigger; Segmented lays them out, which reads better for 2-4 mutually exclusive options.",
|
|
347
|
+
"SearchSelect — the combobox engine Select delegates to when showSearch=true or loadOptions is set. Prefer Select with showSearch instead of reaching for SearchSelect directly (SearchSelect is now deprecated as a public API).",
|
|
348
|
+
"TreeSelect — use when options are hierarchical (parent/child tree). Not a drop-in for Select; has expand/collapse and a separate treeData prop.",
|
|
349
|
+
"Select with showSearch — use Select (with the `showSearch` prop) for typeahead/autocomplete lookup patterns instead of the removed Autocomplete component.",
|
|
350
|
+
"RadioGroup — use instead of Select when there are 2-4 mutually exclusive choices that must all be visible at once without opening a popover.",
|
|
351
|
+
"Combobox (if present) — compound cmdk-powered combobox for free-text + suggestion; Select is for strict value lists only."
|
|
352
|
+
],
|
|
353
|
+
"rules": [
|
|
354
|
+
3,
|
|
355
|
+
6,
|
|
356
|
+
23
|
|
357
|
+
],
|
|
358
|
+
"storyPath": "data-entry/Select.stories.tsx",
|
|
359
|
+
"subParts": [
|
|
360
|
+
"SelectContent",
|
|
361
|
+
"SelectGroup",
|
|
362
|
+
"SelectItem",
|
|
363
|
+
"SelectLabel",
|
|
364
|
+
"SelectScrollDownButton",
|
|
365
|
+
"SelectScrollUpButton",
|
|
366
|
+
"SelectSeparator",
|
|
367
|
+
"SelectTrigger",
|
|
368
|
+
"SelectValue"
|
|
369
|
+
],
|
|
370
|
+
"tagline": "Polymorphic single-select: pass options/loadOptions for the data-driven (Ant-style) API, or compose sub-parts manually — never use a raw <select>.",
|
|
371
|
+
"usage": [
|
|
372
|
+
"DO use the data-driven API (options/loadOptions) for straightforward selects — it handles grouping, search, async, and custom rendering automatically. Only reach for the compound API when you need to inject arbitrary content into the trigger or listbox.",
|
|
373
|
+
"DO pass name= on the data-driven Select so the value is submitted with a native form or Inertia useForm. Without name= the value is React-only and will not appear in form data.",
|
|
374
|
+
"READING THE SELECTED CODE FROM THE DOM: the trigger publishes `data-value` = the selected VALUE, alongside the `data-field` key it inherits from FormField. Use that in e2e tests and screen automation — the trigger's visible text is the option LABEL (東京本社), and the only other place the code lives is the aria-hidden, 1px-clipped native <select> react-aria renders so a native submit (and browser autofill) carries the value. `data-value` is absent while nothing is selected, and it tracks uncontrolled picks too.",
|
|
375
|
+
"DO use loadOptions + selectedLabel together for async selects: selectedLabel prevents a flash of the raw id string while the first page loads.",
|
|
376
|
+
"A Select is safe inside a draggable element (a Kanban card with draggable=true) and inside a `contain: paint` / `transform` app region: the aria-hidden native <select> fallback is held at its static position beside the trigger (position: absolute, 1px clipped), so the browser's drag image stays the card's own box instead of reaching to the region's corner (gh#708). No wrapper or consumer CSS is needed.",
|
|
377
|
+
"DO name the control with FormField, aria-label, or a <label htmlFor> pointing at the trigger id — all three work. (An earlier version of this entry said htmlFor does NOT name the trigger. That was wrong: the trigger is a <button>, which is a labelable element, so <label for> does name it — src/components/data-entry/__tests__/select-rac.test.tsx pins it on both the old Radix base and the react-aria one, because 17 godx-task files name their Selects exactly that way.) What role=combobox does NOT do is take a name from its own content, so the visible value is the VALUE, never the name — a Select with no label of any kind is anonymous. Wrapping in <FormField label=…> stays the route that also wires helper, error and required. This holds for BOTH APIs; anything set directly on SelectTrigger wins.",
|
|
378
|
+
"DO treat loading / no-options / error / disabled as DISTINCT states. A data-driven Select never opens a blank popover: a static options=[] list auto-disables the trigger (opening it would show nothing), while an async loadOptions shows a loading row, then either the options, a localized empty affordance (override with emptyMessage), or an error affordance if the fetch rejects (override with errorMessage). Disable the Select when there is nothing to pick AND no async loader; keep it enabled (it opens to load/search) whenever loadOptions is set.",
|
|
379
|
+
"DON'T mix the two APIs: once you pass options or loadOptions, Select is data-driven — all compound sub-parts (SelectTrigger, SelectContent, SelectItem) are rendered internally. Do not wrap them manually.",
|
|
380
|
+
"DON'T use a raw <select> element. Select is the one control for all single-select use cases. The only allowed raw <select> is a hidden aria-hidden sr-only element kept as an e2e hook paired with a visible Select.",
|
|
381
|
+
"COMPOUND API sub-parts (when NOT using options/loadOptions): Select → SelectTrigger (contains SelectValue) → SelectContent → SelectItem. Optionally wrap items in SelectGroup + SelectLabel for headings, or add SelectSeparator between sections.",
|
|
382
|
+
"DO reach for open/onOpenChange (searchable mode) to drive the popover from outside — e.g. opening it programmatically after a validation error — and search/onSearchChange to seed or read the query text. Both fall back to internal state when omitted; onOpenChange/onSearchChange still fire either way so a controlled consumer stays in sync.",
|
|
383
|
+
"DO use readOnly (searchable mode) for a value that must stay visible and submittable but not editable in this view — it differs from disabled: the control stays focusable and its value still submits. allowClear / clearable is ignored while readOnly.",
|
|
384
|
+
"DO use filterOption (searchable mode, static options) when the default label/value substring match isn't right — e.g. filtering by a hidden code field. It is NOT consulted when loadOptions is set (that fetcher owns its own filtering).",
|
|
385
|
+
"DO use renderError + renderLoadMore (searchable mode) to replace the default error row with a branded retry affordance, or to pair a manual 'load more' button with (not instead of) the built-in scroll-triggered pagination.",
|
|
386
|
+
"DO set SelectTrigger showIndicator={false} (compound API) on a specialized trigger — icon-only, or one with its own affordance — instead of hiding [data-slot=select-chevron] with consumer CSS."
|
|
387
|
+
],
|
|
388
|
+
"useCases": [
|
|
389
|
+
"Status filter on an invoice list — pass options=[{value:'draft',label:'Draft'},{value:'paid',label:'Paid'}] with onChange to drive a query param; no search needed so omit showSearch.",
|
|
390
|
+
"Legal-entity switcher — static options list with showSearch=true for client-side filtering when there are many entities; use selectedLabel to show the entity name before the full list loads.",
|
|
391
|
+
"Account category picker backed by an API — pass loadOptions to stream pages of accounts as the user types; use renderOption to show account code + name side by side; pass selectedLabel so the trigger shows the name on first render.",
|
|
392
|
+
"Grouped currency picker — set option.group='Asia' / 'Europe' on each option; the plain (non-search) data-driven mode renders SelectGroup headings automatically.",
|
|
393
|
+
"Form field in an accounting entry — use the compound API when the trigger must show a currency flag icon alongside the SelectValue; wire SelectTrigger size='sm' for dense table rows.",
|
|
394
|
+
"Required department select in a HR form — leave allowClear off (the default) so the user cannot clear the field once set; pair with name='department_id' for Inertia useForm submission. An OPTIONAL filter select passes allowClear so the user can return to 'no filter'.",
|
|
395
|
+
"Async account picker whose API can fail — pass loadOptions plus errorMessage so a rejected fetch shows a clear error affordance in the panel (not a blank surface or a false 'no results'); the loading and empty states are handled automatically."
|
|
396
|
+
]
|
|
397
|
+
}
|