From d068c208aa2657a48656acc47381ee9a8920d555 Mon Sep 17 00:00:00 2001 From: Shadi Date: Thu, 29 Sep 2022 15:19:58 -0500 Subject: [PATCH] docs: add multiselect combobox docs (#2711) * docs: add multiselect combobox docs * fix(combobox): refine typings for multiselect * docs: fix storybook urls * docs: tiny fix to props table * docs: fix pr comments * chore: design fixes * fix: pr review * chore: small commit for CI * chore: small commit for ci --- .changeset/perfect-lobsters-beam.md | 6 + .../src/styles/ComboboxListboxGroup.tsx | 4 +- .../src/styles/ComboboxListboxOption.tsx | 2 +- .../components/combobox/src/types.ts | 16 +- .../MultiselectComboboxExamples.ts | 536 ++++++++++++++++++ .../sidebar/SidebarNavigation.tsx | 24 + .../src/pages/components/combobox/index.mdx | 2 +- .../components/multiselect-combobox/index.mdx | 392 +++++++++++++ 8 files changed, 975 insertions(+), 7 deletions(-) create mode 100644 .changeset/perfect-lobsters-beam.md create mode 100644 packages/paste-website/src/component-examples/MultiselectComboboxExamples.ts create mode 100644 packages/paste-website/src/pages/components/multiselect-combobox/index.mdx diff --git a/.changeset/perfect-lobsters-beam.md b/.changeset/perfect-lobsters-beam.md new file mode 100644 index 0000000000..31549a171b --- /dev/null +++ b/.changeset/perfect-lobsters-beam.md @@ -0,0 +1,6 @@ +--- +'@twilio-paste/combobox': patch +'@twilio-paste/core': patch +--- + +[Combobox] Refine typings and styling for group options for Multiselect Combobox. diff --git a/packages/paste-core/components/combobox/src/styles/ComboboxListboxGroup.tsx b/packages/paste-core/components/combobox/src/styles/ComboboxListboxGroup.tsx index 35b7f5ce03..234f9098ad 100644 --- a/packages/paste-core/components/combobox/src/styles/ComboboxListboxGroup.tsx +++ b/packages/paste-core/components/combobox/src/styles/ComboboxListboxGroup.tsx @@ -27,8 +27,8 @@ const ComboboxListboxGroup = React.forwardRef diff --git a/packages/paste-core/components/combobox/src/styles/ComboboxListboxOption.tsx b/packages/paste-core/components/combobox/src/styles/ComboboxListboxOption.tsx index 429339a542..b65c13e39a 100644 --- a/packages/paste-core/components/combobox/src/styles/ComboboxListboxOption.tsx +++ b/packages/paste-core/components/combobox/src/styles/ComboboxListboxOption.tsx @@ -17,7 +17,7 @@ export interface ComboboxListboxOptionProps extends Pick { const VariantStyles: {[key in ComboboxListboxOptionProps['variant']]: BoxStyleProps} = { groupOption: { - paddingLeft: 'space70', + paddingLeft: 'space90', paddingRight: 'space50', }, default: { diff --git a/packages/paste-core/components/combobox/src/types.ts b/packages/paste-core/components/combobox/src/types.ts index 1457c4717c..84dea9aecd 100644 --- a/packages/paste-core/components/combobox/src/types.ts +++ b/packages/paste-core/components/combobox/src/types.ts @@ -83,8 +83,15 @@ export interface ComboboxProps extends Omit } export interface MultiselectComboboxProps - extends Omit { - filterItems?: (items: any[], inputValue: string) => any[]; + extends Omit< + ComboboxProps, + | 'autocomplete' + | 'initialSelectedItem' + | 'selectedItem' + | 'onSelectedItemChange' + | 'getA11yStatusMessage' + | 'getA11ySelectionMessage' + > { initialSelectedItems?: any[]; onSelectedItemsChange?: (newSelectedItems: any[]) => void; selectedItemsLabelText: string; @@ -93,7 +100,10 @@ export interface MultiselectComboboxProps } export interface ComboboxItemsProps - extends Pick { + extends Pick< + ComboboxProps, + 'groupItemsBy' | 'optionTemplate' | 'groupLabelTemplate' | 'element' | 'emptyState' | 'state' + > { items: Item[]; selectedItems?: Item[]; disabledItems?: Item[]; diff --git a/packages/paste-website/src/component-examples/MultiselectComboboxExamples.ts b/packages/paste-website/src/component-examples/MultiselectComboboxExamples.ts new file mode 100644 index 0000000000..9c788474cd --- /dev/null +++ b/packages/paste-website/src/component-examples/MultiselectComboboxExamples.ts @@ -0,0 +1,536 @@ +export const basicExample = ` +const items = [ + 'Alert', + 'Anchor', + 'Button', + 'Card', + 'Heading', + 'List', + 'Modal', + 'Paragraph', + 'Popover', + 'Tooltip', +]; + +function getFilteredItems(inputValue) { + const lowerCasedInputValue = inputValue.toLowerCase(); + + return items.filter(function filterItems(item) { + return item.toLowerCase().includes(lowerCasedInputValue); + }); +} + +const SampleEmptyState = () => ( + + + No results found + + +); + +const MultiselectComboboxExample = () => { + const [inputValue, setInputValue] = React.useState(''); + const filteredItems = React.useMemo(() => getFilteredItems(inputValue), [inputValue]); + + return ( + { + setInputValue(newInputValue); + }} + onSelectedItemsChange={(selectedItems) => { + console.log(selectedItems); + }} + /> + ); +} + +render( + +) +`.trim(); + +export const insertBeforeAfterExample = ` +const items = [ + 'Alert', + 'Anchor', + 'Button', + 'Card', + 'Heading', + 'List', + 'Modal', + 'Paragraph', + 'Popover', + 'Tooltip', +]; + +function getFilteredItems(inputValue) { + const lowerCasedInputValue = inputValue.toLowerCase(); + + return items.filter(function filterItems(item) { + return item.toLowerCase().includes(lowerCasedInputValue); + }); +} + + +const MultiselectComboboxExample = () => { + const [inputValue, setInputValue] = React.useState(''); + const filteredItems = React.useMemo(() => getFilteredItems(inputValue), [inputValue]); + + return ( + { + setInputValue(newInputValue); + }} + onSelectedItemsChange={(selectedItems) => { + console.log(selectedItems); + }} + insertBefore={ + + $10.99 + + } + insertAfter={ + + + + } + /> + ); +} + +render( + +) +`.trim(); + +export const optionGroupExample = ` +const groupedItems = [ + {group: 'Components', label: 'Alert'}, + {group: 'Components', label: 'Anchor'}, + {group: 'Components', label: 'Button'}, + {group: 'Components', label: 'Card'}, + {group: 'Components', label: 'Heading'}, + {group: 'Components', label: 'List'}, + {group: 'Components', label: 'Modal'}, + {group: 'Components', label: 'Paragraph'}, + {group: 'Primitives', label: 'Box'}, + {group: 'Primitives', label: 'Text'}, + {group: 'Primitives', label: 'Non-modal dialog'}, + {group: 'Layout', label: 'Grid'}, + {label: 'Design Tokens'}, +]; + +function getFilteredGroupedItems(inputValue) { + const lowerCasedInputValue = inputValue.toLowerCase(); + return filter(groupedItems, (item) => item.label.toLowerCase().includes(lowerCasedInputValue)); +} + +const MultiselectComboboxExample = () => { + const [inputValue, setInputValue] = React.useState(''); + const filteredItems = React.useMemo(() => getFilteredGroupedItems(inputValue), [inputValue]); + + return ( + (item ? item.label : '')} + onInputValueChange={({inputValue: newInputValue = ''}) => { + setInputValue(newInputValue); + }} + onSelectedItemsChange={(selectedItems) => { + console.log(selectedItems); + }} + labelText="Choose a Paste Component" + selectedItemsLabelText="Selected Paste components" + helpText="Paste components are the building blocks of your product UI." + optionTemplate={(item) =>
{item.label}
} + /> + ); +} + +render( + +) +`.trim(); + +export const optionGroupLabelTemplateExample = ` +const groupedItems = [ + {group: 'Components', label: 'Alert'}, + {group: 'Components', label: 'Anchor'}, + {group: 'Components', label: 'Button'}, + {group: 'Components', label: 'Card'}, + {group: 'Components', label: 'Heading'}, + {group: 'Components', label: 'List'}, + {group: 'Components', label: 'Modal'}, + {group: 'Components', label: 'Paragraph'}, + {group: 'Primitives', label: 'Box'}, + {group: 'Primitives', label: 'Text'}, + {group: 'Primitives', label: 'Non-modal dialog'}, + {group: 'Layout', label: 'Grid'}, + {label: 'Design Tokens'}, +]; + +function getFilteredGroupedItems(inputValue) { + const lowerCasedInputValue = inputValue.toLowerCase(); + return filter(groupedItems, (item) => item.label.toLowerCase().includes(lowerCasedInputValue)); +} + +const MultiselectComboboxExample = () => { + const [inputValue, setInputValue] = React.useState(''); + const filteredItems = React.useMemo(() => getFilteredGroupedItems(inputValue), [inputValue]); + + return ( + (item ? item.label : '')} + onInputValueChange={({inputValue: newInputValue = ''}) => { + setInputValue(newInputValue); + }} + onSelectedItemsChange={(selectedItems) => { + console.log(selectedItems); + }} + labelText="Choose a Paste Component" + selectedItemsLabelText="Selected Paste components" + helpText="Paste components are the building blocks of your product UI." + optionTemplate={(item) =>
{item.label}
} + groupLabelTemplate={(groupName) => { + if (groupName === 'Components') { + return ( + + + + + {groupName} + + ); + } + return groupName; + }} + /> + ); +} + +render( + +) +`.trim(); + +export const optionTemplateExample = ` +const components = [ + {group: 'Components', label: 'Alert'}, + {group: 'Components', label: 'Anchor'}, + {group: 'Components', label: 'Button'}, + {group: 'Components', label: 'Card'}, + {group: 'Components', label: 'Heading'}, + {group: 'Components', label: 'List'}, + {group: 'Components', label: 'Modal'}, + {group: 'Components', label: 'Paragraph'}, + {group: 'Primitives', label: 'Box'}, + {group: 'Primitives', label: 'Text'}, + {group: 'Primitives', label: 'Non-modal dialog'}, + {group: 'Layout', label: 'Grid'}, +]; + +function getFilteredComponents(inputValue) { + const lowerCasedInputValue = inputValue.toLowerCase(); + + return components.filter(function filterComponents(component) { + return ( + component.group.toLowerCase().includes(lowerCasedInputValue) || + component.label.toLowerCase().includes(lowerCasedInputValue) + ); + }); +} + +const MultiselectComboboxExample = () => { + const [inputValue, setInputValue] = React.useState(''); + const filteredItems = React.useMemo(() => getFilteredComponents(inputValue), [inputValue]); + + return ( + (item ? item.label : '')} + optionTemplate={({label, group}) => ( + + {label} + {group} + + )} + onInputValueChange={({inputValue: newInputValue = ''}) => { + setInputValue(newInputValue); + }} + onSelectedItemsChange={(selectedItems) => { + console.log(selectedItems); + }} + /> + ); +}; + +render( + +) +`.trim(); + +export const disabledExample = ` +const items = [ + 'Alert', + 'Anchor', + 'Button', + 'Card', + 'Heading', + 'List', + 'Modal', + 'Paragraph', + 'Popover', + 'Tooltip', +]; + +function getFilteredItems(inputValue) { + const lowerCasedInputValue = inputValue.toLowerCase(); + + return items.filter(function filterItems(item) { + return item.toLowerCase().includes(lowerCasedInputValue); + }); +} + +const MultiselectComboboxExample = () => { + const [inputValue, setInputValue] = React.useState(''); + const filteredItems = React.useMemo(() => getFilteredItems(inputValue), [inputValue]); + + return ( + { + setInputValue(newInputValue); + }} + onSelectedItemsChange={(selectedItems) => { + console.log(selectedItems); + }} + /> + ); +} + +render( + +) +`.trim(); + +export const disabledOptionsExample = ` +const items = [ + 'Alert', + 'Anchor', + 'Button', + 'Card', + 'Heading', + 'List', + 'Modal', + 'Paragraph', + 'Popover', + 'Tooltip', +]; + +function getFilteredItems(inputValue) { + const lowerCasedInputValue = inputValue.toLowerCase(); + + return items.filter(function filterItems(item) { + return item.toLowerCase().includes(lowerCasedInputValue); + }); +} + +const MultiselectComboboxExample = () => { + const [inputValue, setInputValue] = React.useState(''); + const filteredItems = React.useMemo(() => getFilteredItems(inputValue), [inputValue]); + + return ( + { + setInputValue(newInputValue); + }} + onSelectedItemsChange={(selectedItems) => { + console.log(selectedItems); + }} + /> + ); +} + +render( + +) +`.trim(); + +export const errorExample = ` +const items = [ + 'Alert', + 'Anchor', + 'Button', + 'Card', + 'Heading', + 'List', + 'Modal', + 'Paragraph', + 'Popover', + 'Tooltip', +]; + +function getFilteredItems(inputValue) { + const lowerCasedInputValue = inputValue.toLowerCase(); + + return items.filter(function filterItems(item) { + return item.toLowerCase().includes(lowerCasedInputValue); + }); +} + +const MultiselectComboboxExample = () => { + const [inputValue, setInputValue] = React.useState(''); + const filteredItems = React.useMemo(() => getFilteredItems(inputValue), [inputValue]); + + return ( + { + setInputValue(newInputValue); + }} + onSelectedItemsChange={(selectedItems) => { + console.log(selectedItems); + }} + /> + ); +} + +render( + +) +`.trim(); + +export const emptyStateExample = ` +const items = [ + 'Alert', + 'Anchor', + 'Button', + 'Card', + 'Heading', + 'List', + 'Modal', + 'Paragraph', + 'Popover', + 'Tooltip', +]; + +function getFilteredItems(inputValue) { + const lowerCasedInputValue = inputValue.toLowerCase(); + + return items.filter(function filterItems(item) { + return item.toLowerCase().includes(lowerCasedInputValue); + }); +} + +const SampleEmptyState = () => ( + + + No results found + + +); + +const MultiselectComboboxExample = () => { + const [inputValue, setInputValue] = React.useState(''); + const filteredItems = React.useMemo(() => getFilteredItems(inputValue), [inputValue]); + + return ( + { + setInputValue(newInputValue); + }} + onSelectedItemsChange={(selectedItems) => { + console.log(selectedItems); + }} + /> + ); +} + +render( + +) +`.trim(); + +export const maxHeightExample = ` +const items = Array.from(new Array(100)).map((_,index) => \`Item \${index}\`) + +function getFilteredItems(inputValue) { + const lowerCasedInputValue = inputValue.toLowerCase(); + + return items.filter(function filterItems(item) { + return item.toLowerCase().includes(lowerCasedInputValue); + }); +} + +const SampleEmptyState = () => ( + + + No results found + + +); + +const MultiselectComboboxExample = () => { + const [inputValue, setInputValue] = React.useState(''); + const filteredItems = React.useMemo(() => getFilteredItems(inputValue), [inputValue]); + + return ( + { + setInputValue(newInputValue); + }} + onSelectedItemsChange={(selectedItems) => { + console.log(selectedItems); + }} + /> + ); +} + +render( + +) +`.trim(); diff --git a/packages/paste-website/src/components/site-wrapper/sidebar/SidebarNavigation.tsx b/packages/paste-website/src/components/site-wrapper/sidebar/SidebarNavigation.tsx index 3cdf1b7c36..68af8bd871 100644 --- a/packages/paste-website/src/components/site-wrapper/sidebar/SidebarNavigation.tsx +++ b/packages/paste-website/src/components/site-wrapper/sidebar/SidebarNavigation.tsx @@ -272,6 +272,30 @@ const SidebarNavigation: React.FC = () => { ); } + if (name === 'Combobox') { + return ( + + trackCustomEvent({ + category: 'Left Navigation', + action: `click-${name}`, + label: name, + }) + } + > + + Singleselect + + + Multiselect + + + ); + } return ( {name} diff --git a/packages/paste-website/src/pages/components/combobox/index.mdx b/packages/paste-website/src/pages/components/combobox/index.mdx index cfed187543..2e6979839d 100644 --- a/packages/paste-website/src/pages/components/combobox/index.mdx +++ b/packages/paste-website/src/pages/components/combobox/index.mdx @@ -81,7 +81,7 @@ export const pageQuery = graphql` diff --git a/packages/paste-website/src/pages/components/multiselect-combobox/index.mdx b/packages/paste-website/src/pages/components/multiselect-combobox/index.mdx new file mode 100644 index 0000000000..0fb775a8e4 --- /dev/null +++ b/packages/paste-website/src/pages/components/multiselect-combobox/index.mdx @@ -0,0 +1,392 @@ +--- +title: MultiselectCombobox +package: '@twilio-paste/combobox' +description: A multiselect combobox is a styled dropdown form element that allows users to select multiple values from a list. +slug: /components/multiselect-combobox/ +--- + +import {graphql} from 'gatsby'; +import {Anchor} from '@twilio-paste/anchor'; +import {Box} from '@twilio-paste/box'; +import {Text} from '@twilio-paste/text'; +import {MultiselectCombobox} from '@twilio-paste/combobox'; +import {Button} from '@twilio-paste/button'; +import {Table, THead, TBody, Td, Th, Tr} from '@twilio-paste/table'; +import {MediaObject, MediaFigure, MediaBody} from '@twilio-paste/media-object'; +import {InformationIcon} from '@twilio-paste/icons/esm/InformationIcon'; +import {LinkExternalIcon} from '@twilio-paste/icons/esm/LinkExternalIcon'; +import {ProductStudioIcon} from '@twilio-paste/icons/esm/ProductStudioIcon'; +import {ProductAutopilotIcon} from '@twilio-paste/icons/esm/ProductAutopilotIcon'; +import {ProductInsightsIcon} from '@twilio-paste/icons/esm/ProductInsightsIcon'; +import {SearchIcon} from '@twilio-paste/icons/esm/SearchIcon'; +import {CloseIcon} from '@twilio-paste/icons/esm/CloseIcon'; +import {AttachIcon} from '@twilio-paste/icons/esm/AttachIcon'; +import {UnorderedList, ListItem} from '@twilio-paste/list'; +import {Callout, CalloutHeading, CalloutText} from '@twilio-paste/callout'; +import {DoDont, Do, Dont} from '../../../components/DoDont'; +import filter from 'lodash/filter'; +import {SidebarCategoryRoutes} from '../../../constants'; +import Changelog from '@twilio-paste/combobox/CHANGELOG.md'; +import { + basicExample, + insertBeforeAfterExample, + optionGroupExample, + optionGroupLabelTemplateExample, + optionTemplateExample, + disabledExample, + disabledOptionsExample, + errorExample, + emptyStateExample, + maxHeightExample, +} from '../../../component-examples/MultiselectComboboxExamples'; + +export const pageQuery = graphql` + { + allPasteComponent(filter: {name: {eq: "@twilio-paste/combobox"}}) { + edges { + node { + name + description + status + version + } + } + } + mdx(frontmatter: {slug: {eq: "/components/multiselect-combobox/"}}) { + fileAbsolutePath + frontmatter { + slug + title + } + headings { + depth + value + } + } + allAirtable(filter: {data: {Feature: {eq: "MultiselectCombobox"}}}) { + edges { + node { + data { + Documentation + Figma + Design_committee_review + Engineer_committee_review + Code + status + } + } + } + } + } +`; + + + +--- + + + + + + + + + {basicExample} + + +## Guidelines + +### About Multiselect Combobox + +A Multiselect Combobox allows a user to either type or select multiple values from a styled listbox of +options. Each option can consist of more than just text, e.g. text paired with an icon. + +### What’s the difference between Select and Comboboxes? + +At its most basic, a Select has an options list that’s styled according to the browser default. +A Combobox has a Twilio-styled options list and can allow for additional functionality like +autocomplete and multiselect. + +Use a [Select](/components/select) when: + +- You need a native picker experience, especially on mobile devices. +- Users will be selecting from a list of 4-10 options, or a sorted list of highly familiar options (e.g., alphabetical list of states or countries). +- You need the component to work out-of-the-box across all operating systems and browsers. + +Use a (Multiselect) [Combobox](/components/combobox) when: + +- You need a Twilio-styled options list. +- You need to show more than text in an option (e.g., text paired with an icon). +- You need placeholder text in the field to show an example option, rather than a preselected option. +- Users would benefit from typeahead functionality (e.g., autocomplete, search). For example, typeahead may be useful when users need to select from a list of more than 10 options. +- You need to lazy load a much longer list of options to improve page load performance. + +### Accessibility + +Multiselect Combobox is built with consideration for the [ARIA combobox pattern](https://www.w3.org/WAI/ARIA/apg/patterns/combobox/). + +When a user is focused on a Combobox, the listbox opens. When a user makes a selection, the listbox closes so the selected option can be registered to screen readers. + +#### Keyboard interaction + +When the user is focused on a Combobox, the following keyboard interactions apply: + +- Up and down arrows move the user between the options +- Enter selects the currently active option + +When the user is focused within the Form Pill Group, they can use these keyboard interactions: + +- Left and right arrow keys move focus within the group. +- If a pill is selectable, spacebar and enter will toggle pill selection. +- If a pill is dismissible, the pill can be removed by pressing the delete or backspace key. + +## Examples + +### Basic Multiselect Combobox + +Use a basic Multiselect Combobox to allow users to select multiple values from a list of +predefined options. + +The height of the Combobox field will increase to fit the selection of [Form Pills](/components/form-pill-group). +Optionally, you may set a max height using the `maxHeight` prop and if there are more +pills than viewable at max height, users can vertically scroll to view all the selected options. + + + {basicExample} + + +### Multiselect Combobox with add-ons (prefix/suffix text or icons) + +Use add-ons to provide users with guidance on formatting their +input and to offer more context about the value a user is entering. + +- **Prefix/suffix text** — Text that can be used as a prefix and/or suffix to the value that is entered. Use prefix/suffix to help users format text. +- **Prefix/suffix icon** — Icons can be placed in the same area as the prefix and suffix text. Icons should trigger an action (e.g., clearing a field) or in rare cases, provide further context to what value should be entered to make a field's purpose more immediately visible (e.g., a search icon). + + + {insertBeforeAfterExample} + + +### Multiselect Combobox with option groups + +Use option groups to create labeled sections of options. + +Structure your data into an array of objects and use a key on each object as the grouping identifier. +Then, tell the Combobox what you would like to group the items by, by setting `groupItemsby` to match +the intended group identifier. + +In the example below, we have a list of components and we are grouping them based on their type. + + + {optionGroupExample} + + +### Multiselect Combobox with a custom group label + +Expanding on the previous example, it's also possible to customize the group label. + +The `groupLabelTemplate` prop accepts a method with a `groupName` argument. This method should return +valid JSX, which it will render in place of a group label string. + +In the example below, we are checking the `groupName` and rendering an icon next to +it based on the name. + + + {optionGroupLabelTemplateExample} + + +### Multiselect Combobox with option template + +Use the option template to display more complex options in a listbox. + +The `optionTemplate` prop allows you to pass `jsx` to customize the options. Note that we use native HTML input elements +to build Combobox and these HTML elements don't allow for images, icons, or svgs to be added even with the option template. + + + {optionTemplateExample} + + +### Multiselect Combobox with max height + +By default the Multiselect Combobox will grow to fit the content inside it. If you want to limit how much +it resizes vertically, you can provide a `maxHeight` prop. + + + {maxHeightExample} + + +## States + +### Disabled Multiselect Combobox + + + {disabledExample} + + +### Multiselect Combobox with disabled options + + + {disabledOptionsExample} + + +### Multiselect Combobox with error + + + {errorExample} + + +### Mutliselect Combobox with an empty state + +Use an empty state to indicate to a user that their input does not match any value in the list of options. + + + {emptyStateExample} + + +## Composition Notes + +A Multiselect Combobox is comprised of a label, an input and a listbox. + +### Positioning form fields + +Stack form fields vertically with `$space-80` spacing between each field. Avoid placing multiple form fields on the +same horizontal row to help make it easier to scan a page vertically. Use the Grid component if you need to place +form fields horizontally. + +### Options in a Combobox + +Keep option text concise and simple. Option text will truncate when it is longer than the width of the container. + +Use at least five options in a Combobox. If there are less than five options or if choosing options +requires more explanation, consider using a Checkbox instead and add help text for each option, +or give more explanation through help text. Sort options logically (e.g., alphabetically, by value) +or in an order that’s intuitive to your user. + +## When to use Multiselect Combobox + + + + + + + + + + +--- + +## Usage guide + +### API + +#### Installation + +```bash +yarn add @twilio-paste/combobox - or - yarn add @twilio-paste/core +``` + +#### Usage + +```jsx +import {MultiselectCombobox} from '@twilio-paste/core/combobox'; + +const items = [ + 'Alert', + 'Anchor', + 'Button', + 'Card', + 'Heading', + 'List', + 'Modal', + 'Paragraph', + 'Popover', + 'Tooltip', +]; + +function getFilteredItems(inputValue) { + const lowerCasedInputValue = inputValue.toLowerCase(); + + return items.filter(function filterItems(item) { + return item.toLowerCase().includes(lowerCasedInputValue); + }); +} + +const MyComponent = () => ( + const [inputValue, setInputValue] = React.useState(''); + const filteredItems = React.useMemo(() => getFilteredItems(inputValue), [inputValue]); + + return ( + { + setInputValue(newInputValue); + }} + /> + ); +); +``` + +#### Component props + +| Prop | Type | Description | Default | +| ------------------------ | ----------------------------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | +| `labelText` | `string \| NonNullable` | The contents of the label text. | | +| `selectedItemsLabelText` | `string` | Hidden helper text for screen readers | `''` | +| `groupItemsBy?` | `string` | The name of the key in your item objects that you would like Combobox to group the items by. | | +| `groupLabelTemplate?` | `(groupName: string) => React.ReactNode` | This function allows you to use your own `jsx` template for the group label in the drop-down list. | | +| `helpText?` | `string \| React.ReactNode` | The contents of the help and error text. | | +| `optionTemplate?` | `(item: string \| {}) => React.ReactNode` | This function allows you to use your own `jsx` template for the items in the drop-down list. | | +| `element?` | `string` | Overrides the default element name to apply unique styles with the Customization Provider | `'MULTISELECT_COMBOBOX'` | +| `variant?` | `'default' \| 'inverse'` | The variant of the Combobox. Available variants are `default` or `inverse`. | `'default'` | +| `disabled?` | `boolean` | Same as the HTML attribute | | +| `hasError?` | `boolean` | Whether or not the Combobox has an error. | `false` | +| `required?` | `boolean` | Same as the HTML attribute | `false` | +| `i18nKeyboardControls?` | `string` | Visually hidden string that has instructions for how to remove and select pills with a keyboard. | `"Press Delete or Backspace to remove. Press Enter to toggle selection."` | +| `maxHeight` | `string` | The maximum height of the Combobox listbox. | | + +#### State props + +| Prop | Type | Description | Default | +| --------------------------- | --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | -------------- | +| `items` | `any[]` | The items to be displayed in the Combobox listbox. | `[]` | +| `itemToString?` | `(item: any) => string` | A function that returns a string value that uniquely identifies each item in the items array. | `item => item` | +| `inputValue?` | `string` | Sets the value of the input inside the Combobox. | `''` | +| `initialIsOpen?` | `boolean` | Whether the Combobox is open on initial render. | `false` | +| `initialSelectedItems?` | `any[]` | Sets the initial items selected when initialized. | `[]` | +| `disabledItems?` | `any[]` | An array of items that should be disabled. | `[]` | +| `emptyState?` | `React.FC` | A custom empty state component to render when there are no items in the list. | `null` | +| `onSelectedItemsChange?` | `(newSelectedItems: any[]) => void` | Callback function for after an item is selected or deselected. | | +| `onHighlightedIndexChange?` | `(newHighlightedIndex: number) => void` | Callback function for after an item is highlighted. Items are highlighted if they are hovered over or with keyboard actions. | | +| `onInputValueChange?` | `(newInputValue: string) => void` | Callback function for after the input value is changed. | | +| `onIsOpenChange?` | `(newIsOpen: boolean) => void` | Callback function for after the Combobox is opened or closed. | | + + + + + + + +