@arbor-education/design-system.components 0.15.0 → 0.16.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/.gather/skills/write-stories/SKILL.md +207 -271
- package/.storybook/preview.ts +5 -0
- package/CHANGELOG.md +17 -0
- package/README.md +8 -0
- package/component-library.md +144 -13
- package/dist/components/articleCard/ArticleCard.stories.d.ts +137 -11
- package/dist/components/articleCard/ArticleCard.stories.d.ts.map +1 -1
- package/dist/components/articleCard/ArticleCard.stories.js +358 -91
- package/dist/components/articleCard/ArticleCard.stories.js.map +1 -1
- package/dist/components/avatar/Avatar.stories.d.ts +6 -6
- package/dist/components/avatar/Avatar.stories.d.ts.map +1 -1
- package/dist/components/avatar/Avatar.stories.js +393 -49
- package/dist/components/avatar/Avatar.stories.js.map +1 -1
- package/dist/components/avatarGroup/AvatarGroup.stories.d.ts +9 -7
- package/dist/components/avatarGroup/AvatarGroup.stories.d.ts.map +1 -1
- package/dist/components/avatarGroup/AvatarGroup.stories.js +688 -65
- package/dist/components/avatarGroup/AvatarGroup.stories.js.map +1 -1
- package/dist/components/banner/Banner.stories.d.ts.map +1 -1
- package/dist/components/banner/Banner.stories.js +7 -3
- package/dist/components/banner/Banner.stories.js.map +1 -1
- package/dist/components/card/Card.stories.d.ts +105 -4
- package/dist/components/card/Card.stories.d.ts.map +1 -1
- package/dist/components/card/Card.stories.js +336 -18
- package/dist/components/card/Card.stories.js.map +1 -1
- package/dist/components/combobox/Combobox.stories.d.ts +134 -21
- package/dist/components/combobox/Combobox.stories.d.ts.map +1 -1
- package/dist/components/combobox/Combobox.stories.js +676 -175
- package/dist/components/combobox/Combobox.stories.js.map +1 -1
- package/dist/components/datePicker/DatePicker.stories.d.ts +119 -27
- package/dist/components/datePicker/DatePicker.stories.d.ts.map +1 -1
- package/dist/components/datePicker/DatePicker.stories.js +575 -47
- package/dist/components/datePicker/DatePicker.stories.js.map +1 -1
- package/dist/components/dateTimePicker/DateTimePicker.stories.d.ts +155 -39
- package/dist/components/dateTimePicker/DateTimePicker.stories.d.ts.map +1 -1
- package/dist/components/dateTimePicker/DateTimePicker.stories.js +674 -103
- package/dist/components/dateTimePicker/DateTimePicker.stories.js.map +1 -1
- package/dist/components/editableText/EditableText.stories.d.ts +53 -12
- package/dist/components/editableText/EditableText.stories.d.ts.map +1 -1
- package/dist/components/editableText/EditableText.stories.js +401 -64
- package/dist/components/editableText/EditableText.stories.js.map +1 -1
- package/dist/components/formField/FormField.d.ts +4 -0
- package/dist/components/formField/FormField.d.ts.map +1 -1
- package/dist/components/formField/FormField.js +2 -1
- package/dist/components/formField/FormField.js.map +1 -1
- package/dist/components/formField/FormField.test.js +5 -0
- package/dist/components/formField/FormField.test.js.map +1 -1
- package/dist/components/formField/fieldset/Fieldset.stories.d.ts +56 -4
- package/dist/components/formField/fieldset/Fieldset.stories.d.ts.map +1 -1
- package/dist/components/formField/fieldset/Fieldset.stories.js +534 -28
- package/dist/components/formField/fieldset/Fieldset.stories.js.map +1 -1
- package/dist/components/formField/inputs/checkbox/CheckboxGroup.d.ts +3 -1
- package/dist/components/formField/inputs/checkbox/CheckboxGroup.d.ts.map +1 -1
- package/dist/components/formField/inputs/checkbox/CheckboxInput.js +1 -1
- package/dist/components/formField/inputs/checkbox/CheckboxInput.js.map +1 -1
- package/dist/components/formField/inputs/colourPickerDropdown/ColourPickerDropdown.stories.d.ts +95 -1
- package/dist/components/formField/inputs/colourPickerDropdown/ColourPickerDropdown.stories.d.ts.map +1 -1
- package/dist/components/formField/inputs/colourPickerDropdown/ColourPickerDropdown.stories.js +386 -9
- package/dist/components/formField/inputs/colourPickerDropdown/ColourPickerDropdown.stories.js.map +1 -1
- package/dist/components/formField/inputs/radio/RadioButtonGroup.d.ts +6 -2
- package/dist/components/formField/inputs/radio/RadioButtonGroup.d.ts.map +1 -1
- package/dist/components/formField/inputs/radio/RadioButtonGroup.js.map +1 -1
- package/dist/components/formField/inputs/radio/RadioButtonInput.stories.d.ts.map +1 -1
- package/dist/components/formField/inputs/radio/RadioButtonInput.stories.js +61 -49
- package/dist/components/formField/inputs/radio/RadioButtonInput.stories.js.map +1 -1
- package/dist/components/formField/inputs/selectDropdown/SelectDropdown.stories.d.ts +188 -166
- package/dist/components/formField/inputs/selectDropdown/SelectDropdown.stories.d.ts.map +1 -1
- package/dist/components/formField/inputs/selectDropdown/SelectDropdown.stories.js +821 -160
- package/dist/components/formField/inputs/selectDropdown/SelectDropdown.stories.js.map +1 -1
- package/dist/components/formField/inputs/time/TimeInput.stories.d.ts +176 -22
- package/dist/components/formField/inputs/time/TimeInput.stories.d.ts.map +1 -1
- package/dist/components/formField/inputs/time/TimeInput.stories.js +851 -92
- package/dist/components/formField/inputs/time/TimeInput.stories.js.map +1 -1
- package/dist/components/formField/label/Label.stories.d.ts +54 -5
- package/dist/components/formField/label/Label.stories.d.ts.map +1 -1
- package/dist/components/formField/label/Label.stories.js +238 -4
- package/dist/components/formField/label/Label.stories.js.map +1 -1
- package/dist/components/icoText/IcoText.stories.d.ts +32 -6
- package/dist/components/icoText/IcoText.stories.d.ts.map +1 -1
- package/dist/components/icoText/IcoText.stories.js +309 -14
- package/dist/components/icoText/IcoText.stories.js.map +1 -1
- package/dist/components/kpiCard/KPICard.stories.d.ts +100 -2
- package/dist/components/kpiCard/KPICard.stories.d.ts.map +1 -1
- package/dist/components/kpiCard/KPICard.stories.js +354 -10
- package/dist/components/kpiCard/KPICard.stories.js.map +1 -1
- package/dist/components/kvpList/KVPList.stories.d.ts +57 -4
- package/dist/components/kvpList/KVPList.stories.d.ts.map +1 -1
- package/dist/components/kvpList/KVPList.stories.js +403 -10
- package/dist/components/kvpList/KVPList.stories.js.map +1 -1
- package/dist/components/modal/Modal.stories.d.ts +113 -9
- package/dist/components/modal/Modal.stories.d.ts.map +1 -1
- package/dist/components/modal/Modal.stories.js +633 -13
- package/dist/components/modal/Modal.stories.js.map +1 -1
- package/dist/components/modal/modalManager/ModalManager.stories.d.ts +34 -10
- package/dist/components/modal/modalManager/ModalManager.stories.d.ts.map +1 -1
- package/dist/components/modal/modalManager/ModalManager.stories.js +463 -85
- package/dist/components/modal/modalManager/ModalManager.stories.js.map +1 -1
- package/dist/components/pill/Pill.d.ts.map +1 -1
- package/dist/components/pill/Pill.js +1 -1
- package/dist/components/pill/Pill.js.map +1 -1
- package/dist/components/pill/Pill.stories.d.ts.map +1 -1
- package/dist/components/pill/Pill.stories.js +11 -13
- package/dist/components/pill/Pill.stories.js.map +1 -1
- package/dist/components/row/Row.stories.d.ts +1 -2
- package/dist/components/row/Row.stories.d.ts.map +1 -1
- package/dist/components/row/Row.stories.js +360 -50
- package/dist/components/row/Row.stories.js.map +1 -1
- package/dist/components/searchBar/SearchBar.stories.d.ts +52 -4
- package/dist/components/searchBar/SearchBar.stories.d.ts.map +1 -1
- package/dist/components/searchBar/SearchBar.stories.js +428 -36
- package/dist/components/searchBar/SearchBar.stories.js.map +1 -1
- package/dist/components/section/Section.stories.d.ts +11 -41
- package/dist/components/section/Section.stories.d.ts.map +1 -1
- package/dist/components/section/Section.stories.js +494 -56
- package/dist/components/section/Section.stories.js.map +1 -1
- package/dist/components/singleUser/SingleUser.stories.d.ts +5 -4
- package/dist/components/singleUser/SingleUser.stories.d.ts.map +1 -1
- package/dist/components/singleUser/SingleUser.stories.js +303 -31
- package/dist/components/singleUser/SingleUser.stories.js.map +1 -1
- package/dist/components/slideoverManager/SlideoverManager.stories.d.ts +32 -11
- package/dist/components/slideoverManager/SlideoverManager.stories.d.ts.map +1 -1
- package/dist/components/slideoverManager/SlideoverManager.stories.js +380 -84
- package/dist/components/slideoverManager/SlideoverManager.stories.js.map +1 -1
- package/dist/components/table/DSDefaultColDef.d.ts.map +1 -1
- package/dist/components/table/DSDefaultColDef.js +4 -3
- package/dist/components/table/DSDefaultColDef.js.map +1 -1
- package/dist/components/table/Table.d.ts +6 -1
- package/dist/components/table/Table.d.ts.map +1 -1
- package/dist/components/table/Table.js +8 -3
- package/dist/components/table/Table.js.map +1 -1
- package/dist/components/table/Table.stories.d.ts +2 -0
- package/dist/components/table/Table.stories.d.ts.map +1 -1
- package/dist/components/table/Table.stories.js +357 -3
- package/dist/components/table/Table.stories.js.map +1 -1
- package/dist/components/table/TableFooter.stories.d.ts +49 -0
- package/dist/components/table/TableFooter.stories.d.ts.map +1 -0
- package/dist/components/table/TableFooter.stories.js +137 -0
- package/dist/components/table/TableFooter.stories.js.map +1 -0
- package/dist/components/table/TableHeader.stories.d.ts +93 -0
- package/dist/components/table/TableHeader.stories.d.ts.map +1 -0
- package/dist/components/table/TableHeader.stories.js +176 -0
- package/dist/components/table/TableHeader.stories.js.map +1 -0
- package/dist/components/table/cellEditors/DateCellEditor.stories.d.ts +44 -0
- package/dist/components/table/cellEditors/DateCellEditor.stories.d.ts.map +1 -0
- package/dist/components/table/cellEditors/DateCellEditor.stories.js +186 -0
- package/dist/components/table/cellEditors/DateCellEditor.stories.js.map +1 -0
- package/dist/components/table/cellRenderers/BooleanCellRenderer.stories.d.ts +40 -0
- package/dist/components/table/cellRenderers/BooleanCellRenderer.stories.d.ts.map +1 -0
- package/dist/components/table/cellRenderers/BooleanCellRenderer.stories.js +209 -0
- package/dist/components/table/cellRenderers/BooleanCellRenderer.stories.js.map +1 -0
- package/dist/components/table/cellRenderers/ButtonCellRenderer.stories.d.ts +48 -0
- package/dist/components/table/cellRenderers/ButtonCellRenderer.stories.d.ts.map +1 -0
- package/dist/components/table/cellRenderers/ButtonCellRenderer.stories.js +244 -0
- package/dist/components/table/cellRenderers/ButtonCellRenderer.stories.js.map +1 -0
- package/dist/components/table/cellRenderers/CheckboxCellRenderer.d.ts.map +1 -1
- package/dist/components/table/cellRenderers/CheckboxCellRenderer.js +3 -1
- package/dist/components/table/cellRenderers/CheckboxCellRenderer.js.map +1 -1
- package/dist/components/table/cellRenderers/CheckboxCellRenderer.stories.d.ts +64 -0
- package/dist/components/table/cellRenderers/CheckboxCellRenderer.stories.d.ts.map +1 -0
- package/dist/components/table/cellRenderers/CheckboxCellRenderer.stories.js +241 -0
- package/dist/components/table/cellRenderers/CheckboxCellRenderer.stories.js.map +1 -0
- package/dist/components/table/cellRenderers/DefaultCellRenderer.stories.d.ts +55 -0
- package/dist/components/table/cellRenderers/DefaultCellRenderer.stories.d.ts.map +1 -0
- package/dist/components/table/cellRenderers/DefaultCellRenderer.stories.js +245 -0
- package/dist/components/table/cellRenderers/DefaultCellRenderer.stories.js.map +1 -0
- package/dist/components/table/cellRenderers/InlineTextCellRenderer.stories.d.ts +67 -0
- package/dist/components/table/cellRenderers/InlineTextCellRenderer.stories.d.ts.map +1 -0
- package/dist/components/table/cellRenderers/InlineTextCellRenderer.stories.js +221 -0
- package/dist/components/table/cellRenderers/InlineTextCellRenderer.stories.js.map +1 -0
- package/dist/components/table/cellRenderers/SelectDropdownCellRenderer.stories.d.ts +75 -0
- package/dist/components/table/cellRenderers/SelectDropdownCellRenderer.stories.d.ts.map +1 -0
- package/dist/components/table/cellRenderers/SelectDropdownCellRenderer.stories.js +270 -0
- package/dist/components/table/cellRenderers/SelectDropdownCellRenderer.stories.js.map +1 -0
- package/dist/components/table/columnFilters/BooleanFilter.stories.d.ts +57 -0
- package/dist/components/table/columnFilters/BooleanFilter.stories.d.ts.map +1 -0
- package/dist/components/table/columnFilters/BooleanFilter.stories.js +198 -0
- package/dist/components/table/columnFilters/BooleanFilter.stories.js.map +1 -0
- package/dist/components/table/columnFilters/TimeFilter.stories.d.ts +58 -0
- package/dist/components/table/columnFilters/TimeFilter.stories.d.ts.map +1 -0
- package/dist/components/table/columnFilters/TimeFilter.stories.js +207 -0
- package/dist/components/table/columnFilters/TimeFilter.stories.js.map +1 -0
- package/dist/components/table/pagination/PaginationPanel.stories.d.ts +113 -0
- package/dist/components/table/pagination/PaginationPanel.stories.d.ts.map +1 -0
- package/dist/components/table/pagination/PaginationPanel.stories.js +272 -0
- package/dist/components/table/pagination/PaginationPanel.stories.js.map +1 -0
- package/dist/components/table/tableControls/TableControls.stories.d.ts +151 -0
- package/dist/components/table/tableControls/TableControls.stories.d.ts.map +1 -0
- package/dist/components/table/tableControls/TableControls.stories.js +356 -0
- package/dist/components/table/tableControls/TableControls.stories.js.map +1 -0
- package/dist/components/table/tableControls/TableSettingsDropdown.d.ts +27 -1
- package/dist/components/table/tableControls/TableSettingsDropdown.d.ts.map +1 -1
- package/dist/components/table/tableControls/TableSettingsDropdown.js +53 -26
- package/dist/components/table/tableControls/TableSettingsDropdown.js.map +1 -1
- package/dist/components/table/tableControls/TableSettingsDropdown.test.d.ts +2 -0
- package/dist/components/table/tableControls/TableSettingsDropdown.test.d.ts.map +1 -0
- package/dist/components/table/tableControls/TableSettingsDropdown.test.js +178 -0
- package/dist/components/table/tableControls/TableSettingsDropdown.test.js.map +1 -0
- package/dist/components/tabs/Tabs.stories.d.ts +22 -4
- package/dist/components/tabs/Tabs.stories.d.ts.map +1 -1
- package/dist/components/tabs/Tabs.stories.js +398 -22
- package/dist/components/tabs/Tabs.stories.js.map +1 -1
- package/dist/components/tabs/TabsItem.stories.d.ts +54 -1
- package/dist/components/tabs/TabsItem.stories.d.ts.map +1 -1
- package/dist/components/tabs/TabsItem.stories.js +61 -9
- package/dist/components/tabs/TabsItem.stories.js.map +1 -1
- package/dist/components/toast/Toast.stories.d.ts +103 -10
- package/dist/components/toast/Toast.stories.d.ts.map +1 -1
- package/dist/components/toast/Toast.stories.js +409 -47
- package/dist/components/toast/Toast.stories.js.map +1 -1
- package/dist/components/toggle/Toggle.stories.d.ts +61 -46
- package/dist/components/toggle/Toggle.stories.d.ts.map +1 -1
- package/dist/components/toggle/Toggle.stories.js +311 -122
- package/dist/components/toggle/Toggle.stories.js.map +1 -1
- package/dist/components/tooltip/Tooltip.stories.d.ts +78 -6
- package/dist/components/tooltip/Tooltip.stories.d.ts.map +1 -1
- package/dist/components/tooltip/Tooltip.stories.js +413 -7
- package/dist/components/tooltip/Tooltip.stories.js.map +1 -1
- package/dist/components/tooltip/TooltipWrapper.stories.d.ts +71 -7
- package/dist/components/tooltip/TooltipWrapper.stories.d.ts.map +1 -1
- package/dist/components/tooltip/TooltipWrapper.stories.js +238 -10
- package/dist/components/tooltip/TooltipWrapper.stories.js.map +1 -1
- package/dist/index.css +8 -0
- package/dist/index.css.map +1 -1
- package/dist/utils/PopupParentContext.stories.d.ts +17 -0
- package/dist/utils/PopupParentContext.stories.d.ts.map +1 -0
- package/dist/utils/PopupParentContext.stories.js +266 -0
- package/dist/utils/PopupParentContext.stories.js.map +1 -0
- package/dist/utils/getDefaultPopupParent.d.ts.map +1 -1
- package/dist/utils/getDefaultPopupParent.js +6 -0
- package/dist/utils/getDefaultPopupParent.js.map +1 -1
- package/package.json +1 -1
- package/src/components/articleCard/ArticleCard.stories.tsx +524 -111
- package/src/components/avatar/Avatar.stories.tsx +504 -59
- package/src/components/avatarGroup/AvatarGroup.stories.tsx +977 -175
- package/src/components/banner/Banner.stories.tsx +7 -3
- package/src/components/card/Card.stories.tsx +466 -36
- package/src/components/combobox/Combobox.stories.tsx +867 -260
- package/src/components/datePicker/DatePicker.stories.tsx +777 -60
- package/src/components/dateTimePicker/DateTimePicker.stories.tsx +910 -132
- package/src/components/editableText/EditableText.stories.tsx +567 -91
- package/src/components/formField/FormField.test.tsx +6 -0
- package/src/components/formField/FormField.tsx +5 -0
- package/src/components/formField/fieldset/Fieldset.stories.tsx +761 -51
- package/src/components/formField/inputs/checkbox/CheckboxGroup.tsx +1 -1
- package/src/components/formField/inputs/checkbox/CheckboxInput.tsx +1 -1
- package/src/components/formField/inputs/colourPickerDropdown/ColourPickerDropdown.stories.tsx +504 -11
- package/src/components/formField/inputs/radio/RadioButtonGroup.tsx +17 -4
- package/src/components/formField/inputs/radio/RadioButtonInput.stories.tsx +71 -59
- package/src/components/formField/inputs/selectDropdown/SelectDropdown.stories.tsx +1079 -168
- package/src/components/formField/inputs/time/TimeInput.stories.tsx +1140 -104
- package/src/components/formField/label/Label.stories.tsx +317 -8
- package/src/components/icoText/IcoText.stories.tsx +442 -31
- package/src/components/kpiCard/KPICard.stories.tsx +475 -30
- package/src/components/kvpList/KVPList.stories.tsx +593 -26
- package/src/components/modal/Modal.stories.tsx +963 -26
- package/src/components/modal/modalManager/ModalManager.stories.tsx +612 -454
- package/src/components/pill/Pill.stories.tsx +11 -13
- package/src/components/pill/Pill.tsx +1 -0
- package/src/components/row/Row.stories.tsx +474 -58
- package/src/components/searchBar/SearchBar.stories.tsx +570 -38
- package/src/components/section/Section.stories.tsx +723 -70
- package/src/components/singleUser/SingleUser.stories.tsx +393 -34
- package/src/components/slideoverManager/SlideoverManager.stories.tsx +572 -342
- package/src/components/table/DSDefaultColDef.ts +25 -5
- package/src/components/table/Table.stories.tsx +411 -3
- package/src/components/table/Table.tsx +9 -2
- package/src/components/table/TableFooter.stories.tsx +196 -0
- package/src/components/table/TableHeader.stories.tsx +251 -0
- package/src/components/table/cellEditors/DateCellEditor.stories.tsx +245 -0
- package/src/components/table/cellRenderers/BooleanCellRenderer.stories.tsx +278 -0
- package/src/components/table/cellRenderers/ButtonCellRenderer.stories.tsx +333 -0
- package/src/components/table/cellRenderers/CheckboxCellRenderer.stories.tsx +337 -0
- package/src/components/table/cellRenderers/CheckboxCellRenderer.tsx +5 -1
- package/src/components/table/cellRenderers/DefaultCellRenderer.stories.tsx +342 -0
- package/src/components/table/cellRenderers/InlineTextCellRenderer.stories.tsx +292 -0
- package/src/components/table/cellRenderers/SelectDropdownCellRenderer.stories.tsx +369 -0
- package/src/components/table/columnFilters/BooleanFilter.stories.tsx +268 -0
- package/src/components/table/columnFilters/TimeFilter.stories.tsx +281 -0
- package/src/components/table/pagination/PaginationPanel.stories.tsx +327 -0
- package/src/components/table/tableControls/TableControls.stories.tsx +415 -0
- package/src/components/table/tableControls/TableSettingsDropdown.test.tsx +207 -0
- package/src/components/table/tableControls/TableSettingsDropdown.tsx +103 -39
- package/src/components/tabs/Tabs.stories.tsx +540 -60
- package/src/components/tabs/TabsItem.stories.tsx +82 -8
- package/src/components/toast/Toast.stories.tsx +539 -77
- package/src/components/toggle/Toggle.stories.tsx +371 -135
- package/src/components/tooltip/Tooltip.stories.tsx +606 -15
- package/src/components/tooltip/TooltipWrapper.stories.tsx +348 -12
- package/src/docs/Contributing.mdx +241 -0
- package/src/docs/UsingComponents.mdx +93 -0
- package/src/docs/Welcome.mdx +68 -0
- package/src/global.scss +7 -0
- package/src/utils/PopupParentContext.stories.tsx +367 -0
- package/src/utils/getDefaultPopupParent.ts +6 -0
- package/.ralph/storybook-upgrade/knowledge.md +0 -308
- package/.ralph/storybook-upgrade/prd.json +0 -777
- package/.ralph/storybook-upgrade/progress.md +0 -342
- package/src/components/table/TableWIP.mdx +0 -3
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
import {
|
|
3
|
+
Controls,
|
|
4
|
+
Heading as DocHeading,
|
|
5
|
+
Markdown,
|
|
6
|
+
Primary as DocPrimary,
|
|
7
|
+
Stories,
|
|
8
|
+
Subtitle,
|
|
9
|
+
Title,
|
|
10
|
+
} from '@storybook/addon-docs/blocks';
|
|
2
11
|
import { useRef, useState } from 'react';
|
|
3
12
|
import { Icon } from 'Components/icon/Icon';
|
|
4
13
|
import {
|
|
@@ -7,352 +16,950 @@ import {
|
|
|
7
16
|
comboboxPeopleOptions,
|
|
8
17
|
} from '../../mocks/comboboxStoryOptions';
|
|
9
18
|
import { Combobox } from './Combobox';
|
|
10
|
-
import type { ComboboxOption
|
|
19
|
+
import type { ComboboxOption } from './types';
|
|
20
|
+
|
|
21
|
+
// ─── Docs-page content ────────────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
const DESCRIPTION_INTRO = `
|
|
24
|
+
The \`Combobox\` component is a searchable, accessible select field that supports
|
|
25
|
+
single-select, multi-select, grouped options, async search, and item creation —
|
|
26
|
+
built on Radix UI Popover for reliable portal-based positioning.
|
|
27
|
+
`.trim();
|
|
28
|
+
|
|
29
|
+
const USAGE_GUIDANCE = `
|
|
30
|
+
### When to use
|
|
31
|
+
|
|
32
|
+
- Selecting one or more items from a searchable list (teachers, pupils, subjects, year groups).
|
|
33
|
+
- Filtering a dataset where the user needs to type to narrow down many options.
|
|
34
|
+
- Allowing users to create new values on the fly (e.g. adding a new tag or subject name).
|
|
35
|
+
- Async-loaded lists where options are fetched from an API as the user types.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
### When NOT to use
|
|
40
|
+
|
|
41
|
+
| Situation | Use instead |
|
|
42
|
+
|-----------|-------------|
|
|
43
|
+
| A short list (≤ 6 items) with no need to search | \`SelectDropdown\` |
|
|
44
|
+
| Free-text search that triggers a navigation or query action | \`SearchBar\` |
|
|
45
|
+
| A simple true/false toggle | \`Checkbox\` or \`Toggle\` |
|
|
46
|
+
| Picking a date or time | \`DatePicker\` / \`TimePicker\` |
|
|
47
|
+
`.trim();
|
|
48
|
+
|
|
49
|
+
const DEVELOPER_NOTES = `
|
|
50
|
+
> **Built on Radix UI Popover.** The dropdown renders into a portal — do NOT place
|
|
51
|
+
> the Combobox inside a container with \`overflow: hidden\` or \`position: relative\`
|
|
52
|
+
> that clips its stacking context. This will cause the dropdown to be cut off.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
### \`value\` is always \`string[]\`
|
|
57
|
+
|
|
58
|
+
> **Critical:** Even in single-select mode, \`value\` and \`defaultValue\` are
|
|
59
|
+
> **always arrays**. This is the most common integration bug.
|
|
60
|
+
|
|
61
|
+
\`\`\`tsx
|
|
62
|
+
// ✅ Correct — value is string[]
|
|
63
|
+
<Combobox value={['alice-johnson']} onValueChange={(vals) => setVal(vals)} />
|
|
64
|
+
|
|
65
|
+
// ❌ Wrong — value must not be a plain string
|
|
66
|
+
<Combobox value="alice-johnson" />
|
|
67
|
+
\`\`\`
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
### Controlled vs uncontrolled
|
|
72
|
+
|
|
73
|
+
Use \`value\` + \`onValueChange\` for controlled mode. Use \`defaultValue\` for
|
|
74
|
+
uncontrolled initial selection. Do not mix both.
|
|
75
|
+
|
|
76
|
+
\`\`\`tsx
|
|
77
|
+
// Controlled
|
|
78
|
+
const [selected, setSelected] = useState<string[]>([]);
|
|
79
|
+
<Combobox value={selected} onValueChange={setSelected} options={options} />
|
|
80
|
+
|
|
81
|
+
// Uncontrolled with initial value
|
|
82
|
+
<Combobox defaultValue={['alice-johnson']} options={options} />
|
|
83
|
+
\`\`\`
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
### Async search
|
|
88
|
+
|
|
89
|
+
When you pass \`onSearch\`, **all client-side filtering is disabled** — you are
|
|
90
|
+
responsible for updating the \`options\` array yourself (e.g. from an API response).
|
|
91
|
+
The component does NOT debounce; add your own debounce inside \`onSearch\`.
|
|
92
|
+
|
|
93
|
+
\`\`\`tsx
|
|
94
|
+
<Combobox
|
|
95
|
+
options={results}
|
|
96
|
+
onSearch={async (query) => {
|
|
97
|
+
const data = await fetchStaff(query);
|
|
98
|
+
setResults(data);
|
|
99
|
+
}}
|
|
100
|
+
/>
|
|
101
|
+
\`\`\`
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
### Keyboard navigation
|
|
106
|
+
|
|
107
|
+
| Key | Action |
|
|
108
|
+
|-----|--------|
|
|
109
|
+
| \`↓\` / \`↑\` | Move focus through options |
|
|
110
|
+
| \`Enter\` | Select the focused option |
|
|
111
|
+
| \`Escape\` | Close the dropdown |
|
|
112
|
+
| \`Backspace\` | Remove the last selected tag (multi-select) |
|
|
113
|
+
| \`Tab\` | Close the dropdown and advance focus |
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
### Grouped options
|
|
118
|
+
|
|
119
|
+
Options with the same \`group\` string are rendered under a shared group heading.
|
|
120
|
+
The \`group\` string is **case-sensitive** and must match exactly across all options
|
|
121
|
+
in the same group.
|
|
122
|
+
|
|
123
|
+
\`\`\`tsx
|
|
124
|
+
const options = [
|
|
125
|
+
{ value: 'alice', label: 'Alice Johnson', group: 'Teachers' },
|
|
126
|
+
{ value: 'bob', label: 'Bob Smith', group: 'Teachers' }, // same group ✅
|
|
127
|
+
{ value: 'carol', label: 'Carol White', group: 'teachers' }, // different group ❌
|
|
128
|
+
];
|
|
129
|
+
\`\`\`
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
### Accessibility
|
|
11
134
|
|
|
12
|
-
|
|
135
|
+
- The trigger renders a combobox role with \`aria-expanded\`, \`aria-haspopup\`, and
|
|
136
|
+
\`aria-controls\` wired to the listbox.
|
|
137
|
+
- Options use \`aria-selected\` and \`aria-disabled\` where appropriate.
|
|
138
|
+
- Pass \`aria-label\` when there is no visible \`<label>\` element nearby.
|
|
139
|
+
- Pass \`aria-describedby\` to link to a \`<FormField>\` hint or error message.
|
|
140
|
+
- Pass \`aria-invalid\` alongside \`hasError\` to signal validation failure to
|
|
141
|
+
assistive technology.
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
### Sub-components (escape hatches)
|
|
146
|
+
|
|
147
|
+
The compound sub-components (\`Combobox.Trigger\`, \`Combobox.ButtonTrigger\`,
|
|
148
|
+
\`Combobox.Listbox\`) exist for advanced layout needs where the trigger and listbox
|
|
149
|
+
must be separated in the DOM. For 99% of use cases, use \`<Combobox />\` directly.
|
|
150
|
+
`.trim();
|
|
151
|
+
|
|
152
|
+
const RELATED_COMPONENTS = [
|
|
153
|
+
'## Related components',
|
|
154
|
+
'',
|
|
155
|
+
'[SelectDropdown](?path=/docs/components-formfield-inputs-selectdropdown--docs) · [SearchBar](?path=/docs/components-searchbar--docs) · [FormField](?path=/docs/components-formfield--docs) · [CheckboxInput](?path=/docs/components-formfield-inputs-checkbox--docs)',
|
|
156
|
+
].join('\n');
|
|
157
|
+
|
|
158
|
+
const PROPS_INTRO = 'The preview below is wired to the **Controls** panel — tweak any prop to see the story update in real time.';
|
|
159
|
+
|
|
160
|
+
// ─── Docs page ────────────────────────────────────────────────────────────────
|
|
161
|
+
|
|
162
|
+
function ComboboxDocsPage() {
|
|
163
|
+
return (
|
|
164
|
+
<>
|
|
165
|
+
<Title />
|
|
166
|
+
<Subtitle />
|
|
167
|
+
<Markdown>{DESCRIPTION_INTRO}</Markdown>
|
|
168
|
+
<DocHeading>Interactive example</DocHeading>
|
|
169
|
+
<Markdown>{PROPS_INTRO}</Markdown>
|
|
170
|
+
<DocPrimary />
|
|
171
|
+
<Controls />
|
|
172
|
+
<DocHeading>Usage guidance</DocHeading>
|
|
173
|
+
<Markdown>{USAGE_GUIDANCE}</Markdown>
|
|
174
|
+
<DocHeading>Developer notes</DocHeading>
|
|
175
|
+
<Markdown>{DEVELOPER_NOTES}</Markdown>
|
|
176
|
+
<DocHeading>Examples</DocHeading>
|
|
177
|
+
<Stories title="" />
|
|
178
|
+
<Markdown>{RELATED_COMPONENTS}</Markdown>
|
|
179
|
+
</>
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// ─── Meta ─────────────────────────────────────────────────────────────────────
|
|
184
|
+
|
|
185
|
+
const meta = {
|
|
13
186
|
title: 'Components/Combobox',
|
|
14
187
|
component: Combobox,
|
|
15
188
|
tags: ['autodocs'],
|
|
16
189
|
parameters: {
|
|
190
|
+
layout: 'padded',
|
|
17
191
|
docs: {
|
|
18
|
-
|
|
19
|
-
component:
|
|
20
|
-
'Default usage is `<Combobox … />` with props such as `triggerVariant` and `multiple`. `Combobox.Trigger`, `Combobox.ButtonTrigger`, and `Combobox.Listbox` are composition escape hatches.',
|
|
21
|
-
},
|
|
192
|
+
page: ComboboxDocsPage,
|
|
22
193
|
},
|
|
23
194
|
},
|
|
195
|
+
decorators: [
|
|
196
|
+
(Story: React.ComponentType) => (
|
|
197
|
+
<div
|
|
198
|
+
data-surface="base"
|
|
199
|
+
data-colour-mode="light"
|
|
200
|
+
style={{ padding: 'var(--size-space-400)', maxWidth: 480, minHeight: 300 }}
|
|
201
|
+
>
|
|
202
|
+
<Story />
|
|
203
|
+
</div>
|
|
204
|
+
),
|
|
205
|
+
],
|
|
24
206
|
argTypes: {
|
|
25
|
-
|
|
207
|
+
options: {
|
|
208
|
+
control: 'object',
|
|
209
|
+
description:
|
|
210
|
+
'Array of `ComboboxOption` objects `{ value: string; label: string; tagLabel?: string; iconName?: IconName; disabled?: boolean; group?: string }`.',
|
|
211
|
+
},
|
|
212
|
+
multiple: {
|
|
26
213
|
control: 'boolean',
|
|
27
|
-
description: '
|
|
214
|
+
description: 'Allow multiple selections. Each selected value is shown as a tag chip.',
|
|
28
215
|
},
|
|
29
|
-
|
|
216
|
+
placeholder: {
|
|
217
|
+
control: 'text',
|
|
218
|
+
description: 'Placeholder text shown in the trigger when no value is selected.',
|
|
219
|
+
},
|
|
220
|
+
disabled: {
|
|
221
|
+
control: 'boolean',
|
|
222
|
+
description: 'Disables the entire combobox — trigger and dropdown become non-interactive.',
|
|
223
|
+
},
|
|
224
|
+
loading: {
|
|
225
|
+
control: 'boolean',
|
|
226
|
+
description: 'Shows a loading indicator inside the dropdown while options are being fetched.',
|
|
227
|
+
},
|
|
228
|
+
hasError: {
|
|
229
|
+
control: 'boolean',
|
|
230
|
+
description:
|
|
231
|
+
'Applies error styling to the trigger. Pair with `aria-invalid` for full accessibility.',
|
|
232
|
+
},
|
|
233
|
+
searchType: {
|
|
234
|
+
control: 'inline-radio',
|
|
235
|
+
options: ['prefix', 'substring'],
|
|
236
|
+
description:
|
|
237
|
+
'`prefix` (default) matches from the start of the label. `substring` matches anywhere. You can also pass a custom function `(query, options) => ComboboxOption[]`.',
|
|
238
|
+
},
|
|
239
|
+
highlightStringMatches: {
|
|
240
|
+
control: 'boolean',
|
|
241
|
+
description:
|
|
242
|
+
'Highlight the matching portion of each option label while the user is typing.',
|
|
243
|
+
},
|
|
244
|
+
selectedValueDisplay: {
|
|
245
|
+
control: 'inline-radio',
|
|
246
|
+
options: ['tags', 'text'],
|
|
247
|
+
description:
|
|
248
|
+
'`tags` (default) renders each selected value as a chip. `text` shows a comma-separated string — useful in space-constrained button triggers.',
|
|
249
|
+
},
|
|
250
|
+
showClearAll: {
|
|
30
251
|
control: 'boolean',
|
|
31
|
-
description: '
|
|
252
|
+
description: 'Show a "Clear all" button inside the trigger when one or more values are selected.',
|
|
253
|
+
},
|
|
254
|
+
clearAllLabel: {
|
|
255
|
+
control: 'text',
|
|
256
|
+
description: 'Label for the clear-all button. Defaults to `"Clear all"`.',
|
|
32
257
|
},
|
|
33
258
|
triggerVariant: {
|
|
34
259
|
control: 'inline-radio',
|
|
35
260
|
options: ['input', 'button'],
|
|
36
|
-
description:
|
|
261
|
+
description:
|
|
262
|
+
'`input` (default) renders an inline text field. `button` renders a clickable button; the search field moves into the dropdown.',
|
|
263
|
+
},
|
|
264
|
+
allowCreate: {
|
|
265
|
+
control: 'boolean',
|
|
266
|
+
description:
|
|
267
|
+
'Show a "Create X" row when the typed query does not match any existing option.',
|
|
268
|
+
},
|
|
269
|
+
showDropdownTrigger: {
|
|
270
|
+
control: 'boolean',
|
|
271
|
+
description: 'Show or hide the chevron button at the end of the trigger. Defaults to `true`.',
|
|
272
|
+
},
|
|
273
|
+
dropdownOnFocus: {
|
|
274
|
+
control: 'boolean',
|
|
275
|
+
description:
|
|
276
|
+
'Open the dropdown automatically when the trigger receives focus. Defaults to `true`.',
|
|
37
277
|
},
|
|
278
|
+
showSelectionCountBadge: {
|
|
279
|
+
control: 'boolean',
|
|
280
|
+
description:
|
|
281
|
+
'Show a badge with the number of selected items (useful with `triggerVariant="button"`).',
|
|
282
|
+
},
|
|
283
|
+
// Callbacks — not controllable via the controls panel
|
|
284
|
+
onValueChange: { control: false },
|
|
285
|
+
onSearch: { control: false },
|
|
286
|
+
onCreateNew: { control: false },
|
|
287
|
+
onDeleteCreated: { control: false },
|
|
288
|
+
renderOption: { control: false },
|
|
289
|
+
getTagLabel: { control: false },
|
|
290
|
+
triggerEndContent: { control: false },
|
|
38
291
|
},
|
|
39
|
-
|
|
40
|
-
Story => (
|
|
41
|
-
<div data-surface="base" data-colour-mode="light" className="bg-surface text-on-surface-default" style={{ padding: 32, maxWidth: 420 }}>
|
|
42
|
-
<Story />
|
|
43
|
-
</div>
|
|
44
|
-
),
|
|
45
|
-
],
|
|
46
|
-
};
|
|
292
|
+
} satisfies Meta<typeof Combobox>;
|
|
47
293
|
|
|
48
294
|
export default meta;
|
|
49
|
-
|
|
50
295
|
type Story = StoryObj<typeof Combobox>;
|
|
51
296
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
297
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
298
|
+
|
|
299
|
+
function withDescription(description: string, story: Story): Story {
|
|
300
|
+
return {
|
|
301
|
+
...story,
|
|
302
|
+
parameters: {
|
|
303
|
+
...story.parameters,
|
|
304
|
+
docs: {
|
|
305
|
+
...story.parameters?.docs,
|
|
306
|
+
description: { story: description },
|
|
60
307
|
},
|
|
61
308
|
},
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/** Enough rows to exceed `.ds-combobox__listbox` max-height so keyboard nav scrolls. */
|
|
66
|
-
const manyScrollableOptions: ComboboxOption[] = Array.from({ length: 45 }, (_, i) => ({
|
|
67
|
-
value: `scroll-demo-${i + 1}`,
|
|
68
|
-
label: `Option ${i + 1}`,
|
|
69
|
-
iconName: 'user',
|
|
70
|
-
}));
|
|
309
|
+
};
|
|
310
|
+
}
|
|
71
311
|
|
|
72
|
-
|
|
73
|
-
value: time,
|
|
74
|
-
label: time,
|
|
75
|
-
}));
|
|
312
|
+
// ─── Stories ──────────────────────────────────────────────────────────────────
|
|
76
313
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
options: manyScrollableOptions,
|
|
80
|
-
placeholder: 'Open the list and move with arrow keys…',
|
|
81
|
-
},
|
|
82
|
-
}, 'Exercises a scrollable listbox with enough items to validate keyboard navigation and active-row scrolling.');
|
|
83
|
-
|
|
84
|
-
export const ButtonTriggerSingleSelect: Story = withDescription({
|
|
314
|
+
// 1. Default — wired to Controls
|
|
315
|
+
export const Default: Story = {
|
|
85
316
|
args: {
|
|
86
317
|
options: comboboxPeopleOptions,
|
|
87
|
-
placeholder: '
|
|
88
|
-
|
|
318
|
+
placeholder: 'Select a teacher...',
|
|
319
|
+
multiple: false,
|
|
320
|
+
disabled: false,
|
|
321
|
+
loading: false,
|
|
322
|
+
hasError: false,
|
|
323
|
+
searchType: 'prefix',
|
|
324
|
+
highlightStringMatches: false,
|
|
325
|
+
selectedValueDisplay: 'tags',
|
|
326
|
+
showClearAll: false,
|
|
89
327
|
},
|
|
90
|
-
|
|
328
|
+
render: args => <Combobox {...args} />,
|
|
329
|
+
};
|
|
91
330
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
placeholder: 'Select time',
|
|
96
|
-
triggerVariant: 'button',
|
|
97
|
-
selectedValueDisplay: 'text',
|
|
98
|
-
showDropdownTrigger: false,
|
|
99
|
-
triggerEndContent: <Icon name="clock-3" size={16} />,
|
|
100
|
-
defaultValue: ['14:30'],
|
|
101
|
-
},
|
|
102
|
-
}, 'Uses plain-text selected-value rendering instead of tags, which suits a single selected time or similarly compact values.');
|
|
331
|
+
// 2. ControlledSingleSelect
|
|
332
|
+
function ControlledSingleSelectTemplate() {
|
|
333
|
+
const [value, setValue] = useState<string[]>([]);
|
|
103
334
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
335
|
+
return (
|
|
336
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--size-space-300)' }}>
|
|
337
|
+
<Combobox
|
|
338
|
+
id="controlled-combobox"
|
|
339
|
+
options={comboboxPeopleOptions}
|
|
340
|
+
value={value}
|
|
341
|
+
onValueChange={setValue}
|
|
342
|
+
placeholder="Select a teacher..."
|
|
343
|
+
/>
|
|
344
|
+
<p className="ds-text" style={{ margin: 0 }}>
|
|
345
|
+
Selected:
|
|
346
|
+
{' '}
|
|
347
|
+
{value.length > 0 ? value.join(', ') : '(none)'}
|
|
348
|
+
</p>
|
|
349
|
+
</div>
|
|
350
|
+
);
|
|
351
|
+
}
|
|
113
352
|
|
|
114
|
-
export const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
353
|
+
export const ControlledSingleSelect = withDescription(
|
|
354
|
+
'Controlled mode: `value` and `onValueChange` are managed by the parent component. **`value` is always `string[]`** — even for single-select. This is the most common integration mistake.',
|
|
355
|
+
{
|
|
356
|
+
render: () => <ControlledSingleSelectTemplate />,
|
|
357
|
+
parameters: {
|
|
358
|
+
docs: {
|
|
359
|
+
source: {
|
|
360
|
+
code: `
|
|
361
|
+
function ControlledExample() {
|
|
362
|
+
// value is ALWAYS string[] — even for single-select
|
|
363
|
+
const [value, setValue] = useState<string[]>([]);
|
|
120
364
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
365
|
+
return (
|
|
366
|
+
<FormField label="Class teacher" htmlFor="teacher-select">
|
|
367
|
+
<Combobox
|
|
368
|
+
id="teacher-select"
|
|
369
|
+
options={teacherOptions}
|
|
370
|
+
value={value}
|
|
371
|
+
onValueChange={setValue}
|
|
372
|
+
placeholder="Select a teacher..."
|
|
373
|
+
/>
|
|
374
|
+
</FormField>
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
`.trim(),
|
|
378
|
+
},
|
|
379
|
+
},
|
|
380
|
+
},
|
|
126
381
|
},
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
382
|
+
);
|
|
383
|
+
|
|
384
|
+
// 3. SingleSelect
|
|
385
|
+
export const SingleSelect = withDescription(
|
|
386
|
+
'Single-select mode (default). Type to filter the list by prefix. Press Enter or click to select.',
|
|
387
|
+
{
|
|
388
|
+
args: {
|
|
389
|
+
options: comboboxPeopleOptions,
|
|
390
|
+
placeholder: 'Select a teacher...',
|
|
391
|
+
},
|
|
135
392
|
},
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
393
|
+
);
|
|
394
|
+
|
|
395
|
+
// 4. MultiSelect
|
|
396
|
+
export const MultiSelect = withDescription(
|
|
397
|
+
'Set `multiple` to allow multiple selections — each selected value is shown as a removable tag chip. Press Backspace to remove the last tag.',
|
|
398
|
+
{
|
|
399
|
+
args: {
|
|
400
|
+
options: comboboxPeopleOptions,
|
|
401
|
+
multiple: true,
|
|
402
|
+
placeholder: 'Select teachers...',
|
|
403
|
+
},
|
|
404
|
+
parameters: {
|
|
405
|
+
docs: {
|
|
406
|
+
source: {
|
|
407
|
+
code: '<Combobox options={teacherOptions} multiple placeholder="Select teachers..." />',
|
|
408
|
+
},
|
|
409
|
+
},
|
|
410
|
+
},
|
|
144
411
|
},
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
412
|
+
);
|
|
413
|
+
|
|
414
|
+
// 5. WithDefaultValue
|
|
415
|
+
export const WithDefaultValue = withDescription(
|
|
416
|
+
'`defaultValue` sets the initial selection in uncontrolled mode. Note that `defaultValue` must be a `string[]` even for single-select.',
|
|
417
|
+
{
|
|
418
|
+
args: {
|
|
419
|
+
options: comboboxPeopleOptions,
|
|
420
|
+
defaultValue: ['alice-johnson'],
|
|
421
|
+
},
|
|
422
|
+
parameters: {
|
|
423
|
+
docs: {
|
|
424
|
+
source: {
|
|
425
|
+
code: '<Combobox options={teacherOptions} defaultValue={[\'alice-johnson\']} />',
|
|
426
|
+
},
|
|
427
|
+
},
|
|
428
|
+
},
|
|
154
429
|
},
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
430
|
+
);
|
|
431
|
+
|
|
432
|
+
// 6. SubstringSearch
|
|
433
|
+
export const SubstringSearch = withDescription(
|
|
434
|
+
'Pass `searchType="substring"` to match anywhere in the label — not just the start. Type "son" to see it match "Alice Johnson" and "Alice Williamson".',
|
|
435
|
+
{
|
|
436
|
+
args: {
|
|
437
|
+
options: comboboxPeopleOptions,
|
|
438
|
+
searchType: 'substring',
|
|
439
|
+
placeholder: 'Type part of a name...',
|
|
440
|
+
},
|
|
441
|
+
parameters: {
|
|
442
|
+
docs: {
|
|
443
|
+
source: {
|
|
444
|
+
code: '<Combobox options={teacherOptions} searchType="substring" placeholder="Type part of a name..." />',
|
|
445
|
+
},
|
|
446
|
+
},
|
|
447
|
+
},
|
|
162
448
|
},
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
449
|
+
);
|
|
450
|
+
|
|
451
|
+
// 7. HighlightedMatches
|
|
452
|
+
export const HighlightedMatches = withDescription(
|
|
453
|
+
'Add `highlightStringMatches` to highlight the matching portion of each option label as the user types. Works best with `searchType="substring"`.',
|
|
454
|
+
{
|
|
455
|
+
args: {
|
|
456
|
+
options: comboboxPeopleOptions,
|
|
457
|
+
searchType: 'substring',
|
|
458
|
+
highlightStringMatches: true,
|
|
459
|
+
placeholder: 'Type to highlight matches...',
|
|
460
|
+
},
|
|
461
|
+
parameters: {
|
|
462
|
+
docs: {
|
|
463
|
+
source: {
|
|
464
|
+
code: '<Combobox options={teacherOptions} searchType="substring" highlightStringMatches placeholder="Type to highlight matches..." />',
|
|
465
|
+
},
|
|
466
|
+
},
|
|
467
|
+
},
|
|
171
468
|
},
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
469
|
+
);
|
|
470
|
+
|
|
471
|
+
// 8. WithGroups
|
|
472
|
+
export const WithGroups = withDescription(
|
|
473
|
+
'Options with the same `group` string are visually grouped under a shared heading. The `group` value must match exactly (case-sensitive) across all options in the same group.',
|
|
474
|
+
{
|
|
475
|
+
args: {
|
|
476
|
+
options: comboboxGroupedPeopleOptions,
|
|
477
|
+
multiple: true,
|
|
478
|
+
placeholder: 'Select staff...',
|
|
479
|
+
},
|
|
480
|
+
parameters: {
|
|
481
|
+
docs: {
|
|
482
|
+
source: {
|
|
483
|
+
code: `
|
|
484
|
+
const options = [
|
|
485
|
+
{ value: 'alice', label: 'Alice Johnson', group: 'Teachers' },
|
|
486
|
+
{ value: 'bob', label: 'Bob Smith', group: 'Teachers' },
|
|
487
|
+
{ value: 'carol', label: 'Carol White', group: 'Support Staff' },
|
|
488
|
+
{ value: 'rachel', label: 'Rachel Green', group: 'Admin' },
|
|
489
|
+
];
|
|
490
|
+
|
|
491
|
+
<Combobox options={options} multiple placeholder="Select staff..." />
|
|
492
|
+
`.trim(),
|
|
493
|
+
},
|
|
494
|
+
},
|
|
495
|
+
},
|
|
179
496
|
},
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
497
|
+
);
|
|
498
|
+
|
|
499
|
+
// 9. Disabled
|
|
500
|
+
export const Disabled = withDescription(
|
|
501
|
+
'The entire combobox is non-interactive when `disabled` is set. The trigger becomes visually muted and cannot be clicked or focused.',
|
|
502
|
+
{
|
|
503
|
+
args: {
|
|
504
|
+
options: comboboxPeopleOptions,
|
|
505
|
+
disabled: true,
|
|
506
|
+
placeholder: 'Not available',
|
|
507
|
+
},
|
|
508
|
+
parameters: {
|
|
509
|
+
docs: {
|
|
510
|
+
source: {
|
|
511
|
+
code: '<Combobox options={teacherOptions} disabled placeholder="Not available" />',
|
|
512
|
+
},
|
|
513
|
+
},
|
|
514
|
+
},
|
|
187
515
|
},
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
516
|
+
);
|
|
517
|
+
|
|
518
|
+
// 10. ErrorState
|
|
519
|
+
export const ErrorState = withDescription(
|
|
520
|
+
'Set `hasError` to apply error styling to the trigger. Always pair with `aria-invalid` so assistive technology can identify the invalid field. Wrap in `FormField` to display the error message.',
|
|
521
|
+
{
|
|
522
|
+
args: {
|
|
523
|
+
'options': comboboxPeopleOptions,
|
|
524
|
+
'hasError': true,
|
|
525
|
+
'aria-invalid': true,
|
|
526
|
+
'placeholder': 'Select a teacher...',
|
|
527
|
+
},
|
|
528
|
+
render: args => <Combobox {...args} />,
|
|
529
|
+
parameters: {
|
|
530
|
+
docs: {
|
|
531
|
+
source: {
|
|
532
|
+
code: `
|
|
533
|
+
<Combobox
|
|
534
|
+
options={teacherOptions}
|
|
535
|
+
hasError
|
|
536
|
+
aria-invalid
|
|
537
|
+
placeholder="Select a teacher..."
|
|
538
|
+
/>
|
|
539
|
+
`.trim(),
|
|
540
|
+
},
|
|
541
|
+
},
|
|
542
|
+
},
|
|
543
|
+
},
|
|
544
|
+
);
|
|
545
|
+
|
|
546
|
+
// 11. LoadingState
|
|
547
|
+
export const LoadingState = withDescription(
|
|
548
|
+
'Set `loading` to show a loading indicator inside the dropdown while options are being fetched. Use this during async search operations before the results arrive.',
|
|
549
|
+
{
|
|
550
|
+
args: {
|
|
551
|
+
options: [],
|
|
552
|
+
loading: true,
|
|
553
|
+
placeholder: 'Searching...',
|
|
554
|
+
},
|
|
555
|
+
parameters: {
|
|
556
|
+
docs: {
|
|
557
|
+
source: {
|
|
558
|
+
code: `
|
|
559
|
+
// Typically used while an async fetch is in-flight:
|
|
560
|
+
<Combobox
|
|
561
|
+
options={results}
|
|
562
|
+
loading={isFetching}
|
|
563
|
+
onSearch={handleSearch}
|
|
564
|
+
placeholder="Search staff..."
|
|
565
|
+
/>
|
|
566
|
+
`.trim(),
|
|
567
|
+
},
|
|
568
|
+
},
|
|
569
|
+
},
|
|
570
|
+
},
|
|
571
|
+
);
|
|
572
|
+
|
|
573
|
+
// 12. ButtonTriggerSingleSelect
|
|
574
|
+
export const ButtonTriggerSingleSelect = withDescription(
|
|
575
|
+
'`triggerVariant="button"` renders a clickable button as the trigger instead of an inline input. The search field moves inside the dropdown. Ideal for toolbar filters and compact layouts.',
|
|
576
|
+
{
|
|
577
|
+
args: {
|
|
578
|
+
options: comboboxPeopleOptions,
|
|
579
|
+
triggerVariant: 'button',
|
|
580
|
+
placeholder: 'Filter by teacher',
|
|
581
|
+
},
|
|
582
|
+
parameters: {
|
|
583
|
+
docs: {
|
|
584
|
+
source: {
|
|
585
|
+
code: '<Combobox options={teacherOptions} triggerVariant="button" placeholder="Filter by teacher" />',
|
|
586
|
+
},
|
|
587
|
+
},
|
|
588
|
+
},
|
|
589
|
+
},
|
|
590
|
+
);
|
|
591
|
+
|
|
592
|
+
// 13. ButtonTriggerMultiSelect
|
|
593
|
+
export const ButtonTriggerMultiSelect = withDescription(
|
|
594
|
+
'Button trigger with `multiple` enabled. Use `showSelectionCountBadge` to show a badge with the number of selected items — useful when the trigger label should not grow in size.',
|
|
595
|
+
{
|
|
596
|
+
args: {
|
|
597
|
+
options: comboboxPeopleOptions,
|
|
598
|
+
triggerVariant: 'button',
|
|
599
|
+
multiple: true,
|
|
600
|
+
placeholder: 'Filter by teachers',
|
|
601
|
+
showSelectionCountBadge: true,
|
|
602
|
+
},
|
|
603
|
+
parameters: {
|
|
604
|
+
docs: {
|
|
605
|
+
source: {
|
|
606
|
+
code: '<Combobox options={teacherOptions} triggerVariant="button" multiple placeholder="Filter by teachers" showSelectionCountBadge />',
|
|
607
|
+
},
|
|
608
|
+
},
|
|
609
|
+
},
|
|
203
610
|
},
|
|
204
|
-
|
|
611
|
+
);
|
|
612
|
+
|
|
613
|
+
// 14. ButtonTriggerPlainTextValue
|
|
614
|
+
export const ButtonTriggerPlainTextValue = withDescription(
|
|
615
|
+
'`selectedValueDisplay="text"` shows the selected value(s) as a comma-separated text string instead of tag chips — useful when space is limited inside a button trigger.',
|
|
616
|
+
{
|
|
617
|
+
args: {
|
|
618
|
+
options: comboboxPeopleOptions,
|
|
619
|
+
triggerVariant: 'button',
|
|
620
|
+
selectedValueDisplay: 'text',
|
|
621
|
+
multiple: true,
|
|
622
|
+
placeholder: 'Filter by teacher',
|
|
623
|
+
},
|
|
624
|
+
parameters: {
|
|
625
|
+
docs: {
|
|
626
|
+
source: {
|
|
627
|
+
code: '<Combobox options={teacherOptions} triggerVariant="button" selectedValueDisplay="text" multiple placeholder="Filter by teacher" />',
|
|
628
|
+
},
|
|
629
|
+
},
|
|
630
|
+
},
|
|
631
|
+
},
|
|
632
|
+
);
|
|
633
|
+
|
|
634
|
+
// 15. HiddenTrigger
|
|
635
|
+
export const HiddenTrigger = withDescription(
|
|
636
|
+
'Set `showDropdownTrigger={false}` to hide the chevron button at the end of the input. The dropdown can still be opened by typing or focusing the field.',
|
|
637
|
+
{
|
|
638
|
+
args: {
|
|
639
|
+
options: comboboxPeopleOptions,
|
|
640
|
+
showDropdownTrigger: false,
|
|
641
|
+
placeholder: 'No chevron shown...',
|
|
642
|
+
},
|
|
643
|
+
parameters: {
|
|
644
|
+
docs: {
|
|
645
|
+
source: {
|
|
646
|
+
code: '<Combobox options={teacherOptions} showDropdownTrigger={false} placeholder="No chevron shown..." />',
|
|
647
|
+
},
|
|
648
|
+
},
|
|
649
|
+
},
|
|
650
|
+
},
|
|
651
|
+
);
|
|
652
|
+
|
|
653
|
+
// 16. ManualOpenOnFocus
|
|
654
|
+
export const ManualOpenOnFocus = withDescription(
|
|
655
|
+
'Set `dropdownOnFocus={false}` to prevent the dropdown from opening automatically on focus — the user must start typing or click the chevron to open it.',
|
|
656
|
+
{
|
|
657
|
+
args: {
|
|
658
|
+
options: comboboxPeopleOptions,
|
|
659
|
+
dropdownOnFocus: false,
|
|
660
|
+
placeholder: 'Focus me — dropdown stays closed',
|
|
661
|
+
},
|
|
662
|
+
parameters: {
|
|
663
|
+
docs: {
|
|
664
|
+
source: {
|
|
665
|
+
code: '<Combobox options={teacherOptions} dropdownOnFocus={false} placeholder="Focus me — dropdown stays closed" />',
|
|
666
|
+
},
|
|
667
|
+
},
|
|
668
|
+
},
|
|
669
|
+
},
|
|
670
|
+
);
|
|
205
671
|
|
|
206
|
-
|
|
207
|
-
setTimeout(resolve, ms);
|
|
208
|
-
});
|
|
672
|
+
// ─── Async search template ─────────────────────────────────────────────────────
|
|
209
673
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
674
|
+
function AsyncSearchTemplate() {
|
|
675
|
+
const [options, setOptions] = useState(comboboxPeopleOptions);
|
|
676
|
+
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
677
|
+
|
|
678
|
+
const handleSearch = (query: string) => {
|
|
679
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
680
|
+
timerRef.current = setTimeout(() => {
|
|
681
|
+
setOptions(
|
|
682
|
+
comboboxAsyncSearchOptions.filter(o =>
|
|
683
|
+
o.label.toLowerCase().startsWith(query.toLowerCase()),
|
|
684
|
+
),
|
|
685
|
+
);
|
|
686
|
+
}, 300);
|
|
687
|
+
};
|
|
216
688
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
'allowCreate' | 'searchType' | 'highlightStringMatches'
|
|
220
|
-
>;
|
|
689
|
+
return <Combobox options={options} onSearch={handleSearch} placeholder="Search staff..." />;
|
|
690
|
+
}
|
|
221
691
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
692
|
+
// 17. AsyncSearch
|
|
693
|
+
export const AsyncSearch = withDescription(
|
|
694
|
+
'Pass `onSearch` to take control of filtering. The component disables all client-side matching — you update the `options` array yourself (e.g. via an API call). This example simulates a 300 ms network delay.',
|
|
695
|
+
{
|
|
696
|
+
render: () => <AsyncSearchTemplate />,
|
|
697
|
+
parameters: {
|
|
698
|
+
docs: {
|
|
699
|
+
source: {
|
|
700
|
+
code: `
|
|
701
|
+
function AsyncSearchExample() {
|
|
227
702
|
const [options, setOptions] = useState<ComboboxOption[]>([]);
|
|
228
|
-
const
|
|
229
|
-
const requestIdRef = useRef(0);
|
|
703
|
+
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
230
704
|
|
|
231
705
|
const handleSearch = (query: string) => {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
setLoading(false);
|
|
238
|
-
});
|
|
706
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
707
|
+
timerRef.current = setTimeout(async () => {
|
|
708
|
+
const results = await fetchStaff(query);
|
|
709
|
+
setOptions(results);
|
|
710
|
+
}, 300);
|
|
239
711
|
};
|
|
240
712
|
|
|
241
713
|
return (
|
|
242
714
|
<Combobox
|
|
243
715
|
options={options}
|
|
244
|
-
multiple
|
|
245
|
-
placeholder="Type to search..."
|
|
246
|
-
loading={loading}
|
|
247
|
-
allowCreate={allowCreate}
|
|
248
|
-
searchType={searchType}
|
|
249
|
-
highlightStringMatches={highlightStringMatches}
|
|
250
716
|
onSearch={handleSearch}
|
|
717
|
+
placeholder="Search staff..."
|
|
251
718
|
/>
|
|
252
719
|
);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
searchType: 'substring',
|
|
259
|
-
highlightStringMatches: true,
|
|
720
|
+
}
|
|
721
|
+
`.trim(),
|
|
722
|
+
},
|
|
723
|
+
},
|
|
724
|
+
},
|
|
260
725
|
},
|
|
261
|
-
|
|
262
|
-
<AsyncSearchTemplate
|
|
263
|
-
allowCreate={args.allowCreate}
|
|
264
|
-
searchType={args.searchType}
|
|
265
|
-
highlightStringMatches={args.highlightStringMatches}
|
|
266
|
-
/>
|
|
267
|
-
),
|
|
268
|
-
}, 'Simulates async search results with local data so loading, filtering, and optional create flows can be reviewed.');
|
|
726
|
+
);
|
|
269
727
|
|
|
270
|
-
|
|
271
|
-
const [options, setOptions] = useState<ComboboxOption[]>(comboboxPeopleOptions);
|
|
728
|
+
// ─── Create new template ───────────────────────────────────────────────────────
|
|
272
729
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
value: name.toLowerCase().replace(/\s/g, '-'),
|
|
276
|
-
label: name,
|
|
277
|
-
iconName: 'user',
|
|
278
|
-
};
|
|
279
|
-
setOptions(prev => [...prev, newOpt]);
|
|
280
|
-
return newOpt;
|
|
281
|
-
};
|
|
730
|
+
function CreateNewTemplate() {
|
|
731
|
+
const [options, setOptions] = useState(comboboxPeopleOptions);
|
|
282
732
|
|
|
283
|
-
const
|
|
284
|
-
|
|
733
|
+
const handleCreate = (input: string) => {
|
|
734
|
+
const newOption = { value: input.toLowerCase().replace(/\s+/g, '-'), label: input };
|
|
735
|
+
setOptions(prev => [...prev, newOption]);
|
|
736
|
+
return newOption;
|
|
285
737
|
};
|
|
286
738
|
|
|
287
739
|
return (
|
|
288
740
|
<Combobox
|
|
289
741
|
options={options}
|
|
290
742
|
multiple
|
|
291
|
-
placeholder="Search or create..."
|
|
292
743
|
allowCreate
|
|
293
744
|
onCreateNew={handleCreate}
|
|
294
|
-
|
|
745
|
+
placeholder="Select or add a teacher..."
|
|
295
746
|
/>
|
|
296
747
|
);
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
export const CreateNew: Story = withDescription({
|
|
300
|
-
render: () => <CreateNewTemplate />,
|
|
301
|
-
}, 'Demonstrates multi-select creation and deletion of user-added options.');
|
|
302
|
-
|
|
303
|
-
const SingleSelectCreateTemplate = () => {
|
|
304
|
-
const [options, setOptions] = useState<ComboboxOption[]>(comboboxPeopleOptions);
|
|
748
|
+
}
|
|
305
749
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
750
|
+
// 18. CreateNew
|
|
751
|
+
export const CreateNew = withDescription(
|
|
752
|
+
'Set `allowCreate` to show a "Create X" option when the typed query does not match any existing option. The `onCreateNew` callback receives the typed string and must return a new `ComboboxOption`.',
|
|
753
|
+
{
|
|
754
|
+
render: () => <CreateNewTemplate />,
|
|
755
|
+
parameters: {
|
|
756
|
+
docs: {
|
|
757
|
+
source: {
|
|
758
|
+
code: `
|
|
759
|
+
function CreateNewExample() {
|
|
760
|
+
const [options, setOptions] = useState(initialOptions);
|
|
761
|
+
|
|
762
|
+
const handleCreate = (input: string) => {
|
|
763
|
+
const newOption = {
|
|
764
|
+
value: input.toLowerCase().replace(/\\s+/g, '-'),
|
|
765
|
+
label: input,
|
|
311
766
|
};
|
|
312
|
-
setOptions(prev => [...prev,
|
|
313
|
-
return
|
|
767
|
+
setOptions((prev) => [...prev, newOption]);
|
|
768
|
+
return newOption;
|
|
314
769
|
};
|
|
315
770
|
|
|
316
|
-
|
|
317
|
-
|
|
771
|
+
return (
|
|
772
|
+
<Combobox
|
|
773
|
+
options={options}
|
|
774
|
+
multiple
|
|
775
|
+
allowCreate
|
|
776
|
+
onCreateNew={handleCreate}
|
|
777
|
+
placeholder="Select or add a teacher..."
|
|
778
|
+
/>
|
|
779
|
+
);
|
|
780
|
+
}
|
|
781
|
+
`.trim(),
|
|
782
|
+
},
|
|
783
|
+
},
|
|
784
|
+
},
|
|
785
|
+
},
|
|
786
|
+
);
|
|
787
|
+
|
|
788
|
+
// ─── Single select create template ────────────────────────────────────────────
|
|
789
|
+
|
|
790
|
+
function SingleSelectCreateTemplate() {
|
|
791
|
+
const [options, setOptions] = useState(comboboxPeopleOptions);
|
|
792
|
+
|
|
793
|
+
const handleCreate = (input: string) => {
|
|
794
|
+
const newOption = { value: input.toLowerCase().replace(/\s+/g, '-'), label: input };
|
|
795
|
+
setOptions(prev => [...prev, newOption]);
|
|
796
|
+
return newOption;
|
|
318
797
|
};
|
|
319
798
|
|
|
320
799
|
return (
|
|
321
800
|
<Combobox
|
|
322
801
|
options={options}
|
|
323
|
-
placeholder="Search or create..."
|
|
324
802
|
allowCreate
|
|
325
803
|
onCreateNew={handleCreate}
|
|
326
|
-
|
|
804
|
+
placeholder="Select or add a teacher..."
|
|
327
805
|
/>
|
|
328
806
|
);
|
|
329
|
-
}
|
|
807
|
+
}
|
|
330
808
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
809
|
+
// 19. SingleSelectCreate
|
|
810
|
+
export const SingleSelectCreate = withDescription(
|
|
811
|
+
'`allowCreate` works in single-select mode too — useful for fields like "Add a subject" where the user can either pick from existing values or type a new one.',
|
|
812
|
+
{
|
|
813
|
+
render: () => <SingleSelectCreateTemplate />,
|
|
814
|
+
parameters: {
|
|
815
|
+
docs: {
|
|
816
|
+
source: {
|
|
817
|
+
code: `
|
|
818
|
+
function SingleSelectCreateExample() {
|
|
819
|
+
const [options, setOptions] = useState(initialOptions);
|
|
820
|
+
|
|
821
|
+
const handleCreate = (input: string) => {
|
|
822
|
+
const newOption = {
|
|
823
|
+
value: input.toLowerCase().replace(/\\s+/g, '-'),
|
|
824
|
+
label: input,
|
|
825
|
+
};
|
|
826
|
+
setOptions((prev) => [...prev, newOption]);
|
|
827
|
+
return newOption;
|
|
828
|
+
};
|
|
334
829
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
{
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
830
|
+
return (
|
|
831
|
+
<Combobox
|
|
832
|
+
options={options}
|
|
833
|
+
allowCreate
|
|
834
|
+
onCreateNew={handleCreate}
|
|
835
|
+
placeholder="Select or add a teacher..."
|
|
836
|
+
/>
|
|
837
|
+
);
|
|
838
|
+
}
|
|
839
|
+
`.trim(),
|
|
840
|
+
},
|
|
841
|
+
},
|
|
842
|
+
},
|
|
345
843
|
},
|
|
346
|
-
|
|
844
|
+
);
|
|
845
|
+
|
|
846
|
+
// 20. CustomOptionLayout
|
|
847
|
+
export const CustomOptionLayout = withDescription(
|
|
848
|
+
'Use `renderOption` to fully customise how each option is rendered inside the dropdown — useful for adding avatars, icons, or additional metadata alongside the label.',
|
|
849
|
+
{
|
|
850
|
+
args: {
|
|
851
|
+
options: comboboxPeopleOptions,
|
|
852
|
+
multiple: true,
|
|
853
|
+
placeholder: 'Select teachers...',
|
|
854
|
+
renderOption: (option: ComboboxOption) => (
|
|
855
|
+
<span style={{ display: 'flex', alignItems: 'center', gap: 'var(--size-space-200)' }}>
|
|
856
|
+
<Icon name="user" size={16} />
|
|
857
|
+
<span>{option.label}</span>
|
|
858
|
+
</span>
|
|
859
|
+
),
|
|
860
|
+
},
|
|
861
|
+
parameters: {
|
|
862
|
+
docs: {
|
|
863
|
+
source: {
|
|
864
|
+
code: `
|
|
865
|
+
<Combobox
|
|
866
|
+
options={teacherOptions}
|
|
867
|
+
multiple
|
|
868
|
+
placeholder="Select teachers..."
|
|
869
|
+
renderOption={(option) => (
|
|
870
|
+
<span style={{ display: 'flex', alignItems: 'center', gap: 'var(--size-space-200)' }}>
|
|
871
|
+
<Icon name="user" size={16} />
|
|
872
|
+
<span>{option.label}</span>
|
|
873
|
+
</span>
|
|
874
|
+
)}
|
|
875
|
+
/>
|
|
876
|
+
`.trim(),
|
|
877
|
+
},
|
|
878
|
+
},
|
|
879
|
+
},
|
|
880
|
+
},
|
|
881
|
+
);
|
|
882
|
+
|
|
883
|
+
// 21. CustomTagLabel
|
|
884
|
+
export const CustomTagLabel = withDescription(
|
|
885
|
+
'Use `getTagLabel` to customise the label shown inside a selected tag chip — useful when the full option label is too long to display comfortably in the trigger.',
|
|
886
|
+
{
|
|
887
|
+
args: {
|
|
888
|
+
options: comboboxPeopleOptions,
|
|
889
|
+
multiple: true,
|
|
890
|
+
placeholder: 'Select teachers...',
|
|
891
|
+
getTagLabel: (option: ComboboxOption) => option.label.split(' ')[0] ?? option.label,
|
|
892
|
+
},
|
|
893
|
+
parameters: {
|
|
894
|
+
docs: {
|
|
895
|
+
source: {
|
|
896
|
+
code: `
|
|
897
|
+
<Combobox
|
|
898
|
+
options={teacherOptions}
|
|
899
|
+
multiple
|
|
900
|
+
placeholder="Select teachers..."
|
|
901
|
+
getTagLabel={(option) => option.label.split(' ')[0]}
|
|
902
|
+
/>
|
|
903
|
+
`.trim(),
|
|
904
|
+
},
|
|
905
|
+
},
|
|
906
|
+
},
|
|
907
|
+
},
|
|
908
|
+
);
|
|
909
|
+
|
|
910
|
+
// 22. WithDisabledOptions
|
|
911
|
+
export const WithDisabledOptions = withDescription(
|
|
912
|
+
'Individual options can be disabled by setting `disabled: true` on the option object. Disabled options are visible but cannot be selected.',
|
|
913
|
+
{
|
|
914
|
+
args: {
|
|
915
|
+
options: comboboxPeopleOptions.map((o, i) => ({ ...o, disabled: i % 3 === 1 })),
|
|
916
|
+
multiple: true,
|
|
917
|
+
placeholder: 'Select teachers...',
|
|
918
|
+
},
|
|
919
|
+
parameters: {
|
|
920
|
+
docs: {
|
|
921
|
+
source: {
|
|
922
|
+
code: `
|
|
923
|
+
const options = [
|
|
924
|
+
{ value: 'alice', label: 'Alice Johnson' },
|
|
925
|
+
{ value: 'bob', label: 'Bob Smith', disabled: true },
|
|
926
|
+
{ value: 'carol', label: 'Carol White' },
|
|
927
|
+
{ value: 'dan', label: 'Dan Brown', disabled: true },
|
|
928
|
+
];
|
|
929
|
+
|
|
930
|
+
<Combobox options={options} multiple placeholder="Select teachers..." />
|
|
931
|
+
`.trim(),
|
|
932
|
+
},
|
|
933
|
+
},
|
|
934
|
+
},
|
|
935
|
+
},
|
|
936
|
+
);
|
|
937
|
+
|
|
938
|
+
// 23. ScrollableLongList
|
|
939
|
+
export const ScrollableLongList = withDescription(
|
|
940
|
+
'The dropdown scrolls automatically when there are many options. Here we render 50 generated options to demonstrate the scrollable list and confirm search still works at scale.',
|
|
941
|
+
{
|
|
942
|
+
args: {
|
|
943
|
+
options: Array.from({ length: 50 }, (_, i) => ({
|
|
944
|
+
value: `option-${i}`,
|
|
945
|
+
label: `Option ${i + 1}`,
|
|
946
|
+
})),
|
|
947
|
+
multiple: true,
|
|
948
|
+
placeholder: 'Search 50 options...',
|
|
949
|
+
},
|
|
950
|
+
parameters: {
|
|
951
|
+
docs: {
|
|
952
|
+
source: {
|
|
953
|
+
code: `
|
|
954
|
+
const options = Array.from({ length: 50 }, (_, i) => ({
|
|
955
|
+
value: \`option-\${i}\`,
|
|
956
|
+
label: \`Option \${i + 1}\`,
|
|
957
|
+
}));
|
|
347
958
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
{ value: 'charlie', label: 'Charlie Brown', iconName: 'user' },
|
|
354
|
-
],
|
|
355
|
-
multiple: true,
|
|
356
|
-
placeholder: 'Search people...',
|
|
959
|
+
<Combobox options={options} multiple placeholder="Search 50 options..." />
|
|
960
|
+
`.trim(),
|
|
961
|
+
},
|
|
962
|
+
},
|
|
963
|
+
},
|
|
357
964
|
},
|
|
358
|
-
|
|
965
|
+
);
|