Skip to content

Commit

Permalink
Merge pull request #1903 from adevinta/combobox-custom-value
Browse files Browse the repository at this point in the history
refactor(combobox): combobox blur behaviour and custom input value
  • Loading branch information
Powerplex authored Feb 20, 2024
2 parents 7f44df6 + 41d4dc8 commit 7ca285b
Show file tree
Hide file tree
Showing 10 changed files with 370 additions and 93 deletions.
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 13 additions & 15 deletions packages/components/combobox/src/Combobox.doc.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -99,33 +99,31 @@ AutoSuggest is used as the default behaviour. The user can type anything in the

<Canvas of={stories.ControlledOpenState} />

### Disabled
### Custom filtering

Use `disabled` on the root component to disable the combobox entirely.
Disable `autoFilter` to implement your own logic to filter out items depending on the inputValue or some external logic.

<Canvas of={stories.Disabled} />

### Disabled Item
This example showcases case-sensitive filtering.

Use `disabled` on individual `Combobox.Item` to disable them.
<Canvas of={stories.FilteringManual} />

<Canvas of={stories.DisabledItem} />
### Custom value entry

### Filtering - AutoFilter
Combo Box can be configured to allow entering custom values that aren’t included in the list of options.

Use `autoFilter` to filter out items that does not match the input value. This behaviour is not case-sensitive.
<Canvas of={stories.CustomValueEntry} />

For more custom filtering, logic should be done on call-site to render only desired items.
### Disabled

<Canvas of={stories.FilteringAutoFilter} />
Use `disabled` on the root component to disable the combobox entirely.

### Filtering - Manual
<Canvas of={stories.Disabled} />

Use your own logic to filter out items depending on the inputValue or some external logic.
### Disabled Item

This example showcases case-sensitive filtering.
Use `disabled` on individual `Combobox.Item` to disable them.

<Canvas of={stories.FilteringManual} />
<Canvas of={stories.DisabledItem} />

### Groups

Expand Down
56 changes: 33 additions & 23 deletions packages/components/combobox/src/Combobox.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,15 @@ export default meta
export const Default: StoryFn = _args => {
return (
<div className="pb-[300px]">
<Combobox>
<Combobox autoFilter={false}>
<Combobox.Trigger>
<Combobox.Input aria-label="Book" placeholder="Pick a book" />
<Combobox.Disclosure openedLabel="Close popup" closedLabel="Open popup" />
</Combobox.Trigger>

<Combobox.Popover>
<Combobox.Items>
<Combobox.Empty>No results found</Combobox.Empty>
<Combobox.Item value="book-1">To Kill a Mockingbird</Combobox.Item>
<Combobox.Item value="book-2">War and Peace</Combobox.Item>
<Combobox.Item value="book-3">The Idiot</Combobox.Item>
Expand Down Expand Up @@ -102,6 +103,7 @@ export const Controlled: StoryFn = () => {

<Combobox.Popover>
<Combobox.Items>
<Combobox.Empty>No results found</Combobox.Empty>
<Combobox.Item value="book-1">To Kill a Mockingbird</Combobox.Item>
<Combobox.Item value="book-2">War and Peace</Combobox.Item>
<Combobox.Item value="book-3">The Idiot</Combobox.Item>
Expand Down Expand Up @@ -137,6 +139,7 @@ export const ControlledOpenState: StoryFn = () => {

<Combobox.Popover>
<Combobox.Items>
<Combobox.Empty>No results found</Combobox.Empty>
<Combobox.Item value="book-1">To Kill a Mockingbird</Combobox.Item>
<Combobox.Item value="book-2">War and Peace</Combobox.Item>
<Combobox.Item value="book-3">The Idiot</Combobox.Item>
Expand All @@ -161,6 +164,7 @@ export const CustomItem: StoryFn = _args => {

<Combobox.Popover>
<Combobox.Items>
<Combobox.Empty>No results found</Combobox.Empty>
<Combobox.Item value="book-1" className="flex items-center gap-md">
<Combobox.ItemText>To Kill a Mockingbird</Combobox.ItemText>
<Tag>New</Tag>
Expand Down Expand Up @@ -192,16 +196,18 @@ export const CustomItem: StoryFn = _args => {
)
}

export const Disabled: StoryFn = _args => {
export const CustomValueEntry: StoryFn = _args => {
return (
<div className="pb-[300px]">
<Combobox disabled>
<Combobox allowCustomValue>
<Combobox.Trigger>
<Combobox.Input aria-label="Book" placeholder="Pick a book" />
<Combobox.Disclosure openedLabel="Close popup" closedLabel="Open popup" />
</Combobox.Trigger>

<Combobox.Popover>
<Combobox.Items>
<Combobox.Empty>No results found</Combobox.Empty>
<Combobox.Item value="book-1">To Kill a Mockingbird</Combobox.Item>
<Combobox.Item value="book-2">War and Peace</Combobox.Item>
<Combobox.Item value="book-3">The Idiot</Combobox.Item>
Expand All @@ -215,31 +221,23 @@ export const Disabled: StoryFn = _args => {
)
}

export const FilteringAutoFilter: StoryFn = _args => {
const items = {
'book-1': 'To Kill a Mockingbird',
'book-2': 'War and Peace',
'book-3': 'The Idiot',
'book-4': 'A Picture of Dorian Gray',
'book-5': '1984',
'book-6': 'Pride and Prejudice',
}

export const Disabled: StoryFn = _args => {
return (
<div className="pb-[300px]">
<Combobox autoFilter>
<Combobox disabled>
<Combobox.Trigger>
<Combobox.Input aria-label="Book" placeholder="Pick a book" />
</Combobox.Trigger>

<Combobox.Popover>
<Combobox.Items>
<Combobox.Empty>No results found</Combobox.Empty>
{Object.entries(items).map(([value, text]) => (
<Combobox.Item value={value} key={value}>
{text}
</Combobox.Item>
))}
<Combobox.Item value="book-1">To Kill a Mockingbird</Combobox.Item>
<Combobox.Item value="book-2">War and Peace</Combobox.Item>
<Combobox.Item value="book-3">The Idiot</Combobox.Item>
<Combobox.Item value="book-4">A Picture of Dorian Gray</Combobox.Item>
<Combobox.Item value="book-5">1984</Combobox.Item>
<Combobox.Item value="book-6">Pride and Prejudice</Combobox.Item>
</Combobox.Items>
</Combobox.Popover>
</Combobox>
Expand All @@ -260,7 +258,7 @@ export const FilteringManual: StoryFn = () => {

return (
<div className="pb-[300px]">
<Combobox>
<Combobox autoFilter={false}>
<Combobox.Trigger>
<Combobox.Input
aria-label="Book"
Expand Down Expand Up @@ -299,6 +297,7 @@ export const ReadOnly: StoryFn = _args => {

<Combobox.Popover>
<Combobox.Items>
<Combobox.Empty>No results found</Combobox.Empty>
<Combobox.Item value="book-1">To Kill a Mockingbird</Combobox.Item>
<Combobox.Item value="book-2">War and Peace</Combobox.Item>
<Combobox.Item value="book-3">The Idiot</Combobox.Item>
Expand All @@ -322,6 +321,7 @@ export const DisabledItem: StoryFn = _args => {

<Combobox.Popover>
<Combobox.Items>
<Combobox.Empty>No results found</Combobox.Empty>
<Combobox.Item value="book-1">To Kill a Mockingbird</Combobox.Item>
<Combobox.Item value="book-2">War and Peace</Combobox.Item>
<Combobox.Item value="book-3" disabled>
Expand All @@ -340,12 +340,13 @@ export const DisabledItem: StoryFn = _args => {
export const Grouped: StoryFn = _args => {
return (
<div className="pb-[300px]">
<Combobox autoFilter>
<Combobox>
<Combobox.Trigger>
<Combobox.Input aria-label="Book" placeholder="Pick a book" />
</Combobox.Trigger>
<Combobox.Popover>
<Combobox.Items>
<Combobox.Empty>No results found</Combobox.Empty>
<Combobox.Group>
<Combobox.Label>Best-sellers</Combobox.Label>
<Combobox.Item value="book-1">To Kill a Mockingbird</Combobox.Item>
Expand Down Expand Up @@ -378,6 +379,7 @@ export const LeadingIcon: StoryFn = _args => {

<Combobox.Popover>
<Combobox.Items>
<Combobox.Empty>No results found</Combobox.Empty>
<Combobox.Item value="book-1">To Kill a Mockingbird</Combobox.Item>
<Combobox.Item value="book-2">War and Peace</Combobox.Item>
<Combobox.Item value="book-3">The Idiot</Combobox.Item>
Expand All @@ -403,6 +405,7 @@ export const ItemIndicator: StoryFn = _args => {

<Combobox.Popover>
<Combobox.Items>
<Combobox.Empty>No results found</Combobox.Empty>
<Combobox.Item value="book-1" className="flex items-center gap-md">
<Combobox.ItemIndicator />
<Combobox.ItemText>To Kill a Mockingbird</Combobox.ItemText>
Expand Down Expand Up @@ -450,6 +453,7 @@ export const Statuses: StoryFn = () => {

<Combobox.Popover>
<Combobox.Items>
<Combobox.Empty>No results found</Combobox.Empty>
<Combobox.Item value="book-1">To Kill a Mockingbird</Combobox.Item>
<Combobox.Item value="book-2">War and Peace</Combobox.Item>
<Combobox.Item value="book-3">The Idiot</Combobox.Item>
Expand All @@ -468,7 +472,7 @@ export const Statuses: StoryFn = () => {
export const MultipleSelection: StoryFn = _args => {
return (
<div className="pb-[300px]">
<Combobox multiple autoFilter defaultValue={['book-1', 'book-2']}>
<Combobox multiple defaultValue={['book-1', 'book-2']}>
<Combobox.Trigger>
<Combobox.Input aria-label="Book" placeholder="Pick a book" />
</Combobox.Trigger>
Expand Down Expand Up @@ -503,7 +507,7 @@ export const MultipleSelectionControlled: StoryFn = () => {

return (
<div className="flex flex-col gap-md pb-[300px]">
<Combobox autoFilter multiple value={selectedValues} onValueChange={setSelectedValues}>
<Combobox multiple value={selectedValues} onValueChange={setSelectedValues}>
<Combobox.Trigger>
<Combobox.Input
aria-label="Book"
Expand Down Expand Up @@ -541,6 +545,7 @@ export const FormFieldLabel: StoryFn = _args => {
</Combobox.Trigger>
<Combobox.Popover>
<Combobox.Items>
<Combobox.Empty>No results found</Combobox.Empty>
<Combobox.Item value="book-1">To Kill a Mockingbird</Combobox.Item>
<Combobox.Item value="book-2">War and Peace</Combobox.Item>
<Combobox.Item value="book-3">The Idiot</Combobox.Item>
Expand Down Expand Up @@ -568,6 +573,7 @@ export const FormFieldHiddenLabel: StoryFn = _args => {
</Combobox.Trigger>
<Combobox.Popover>
<Combobox.Items>
<Combobox.Empty>No results found</Combobox.Empty>
<Combobox.Item value="book-1">To Kill a Mockingbird</Combobox.Item>
<Combobox.Item value="book-2">War and Peace</Combobox.Item>
<Combobox.Item value="book-3">The Idiot</Combobox.Item>
Expand All @@ -594,6 +600,7 @@ export const FormFieldReadOnly: StoryFn = _args => {

<Combobox.Popover>
<Combobox.Items>
<Combobox.Empty>No results found</Combobox.Empty>
<Combobox.Item value="book-1">To Kill a Mockingbird</Combobox.Item>
<Combobox.Item value="book-2">War and Peace</Combobox.Item>
<Combobox.Item value="book-3">The Idiot</Combobox.Item>
Expand All @@ -619,6 +626,7 @@ export const FormFieldDisabled: StoryFn = _args => {
</Combobox.Trigger>
<Combobox.Popover>
<Combobox.Items>
<Combobox.Empty>No results found</Combobox.Empty>
<Combobox.Item value="book-1">To Kill a Mockingbird</Combobox.Item>
<Combobox.Item value="book-2">War and Peace</Combobox.Item>
<Combobox.Item value="book-3">The Idiot</Combobox.Item>
Expand All @@ -644,6 +652,7 @@ export const FormFieldRequired: StoryFn = _args => {
</Combobox.Trigger>
<Combobox.Popover>
<Combobox.Items>
<Combobox.Empty>No results found</Combobox.Empty>
<Combobox.Item value="book-1">To Kill a Mockingbird</Combobox.Item>
<Combobox.Item value="book-2">War and Peace</Combobox.Item>
<Combobox.Item value="book-3">The Idiot</Combobox.Item>
Expand Down Expand Up @@ -675,6 +684,7 @@ export const FormFieldValidation: StoryFn = () => {
</Combobox.Trigger>
<Combobox.Popover>
<Combobox.Items>
<Combobox.Empty>No results found</Combobox.Empty>
<Combobox.Item value="default">default</Combobox.Item>
<Combobox.Item value="success">success</Combobox.Item>
<Combobox.Item value="alert">alert</Combobox.Item>
Expand Down
14 changes: 2 additions & 12 deletions packages/components/combobox/src/Combobox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,8 @@ import { type ComboboxContextProps, ComboboxProvider } from './ComboboxContext'

export type ComboboxProps = ComboboxContextProps

export const Combobox = ({
children,
autoFilter = false,
disabled = false,
readOnly = false,
...props
}: ComboboxProps) => {
return (
<ComboboxProvider autoFilter={autoFilter} disabled={disabled} readOnly={readOnly} {...props}>
{children}
</ComboboxProvider>
)
export const Combobox = ({ children, ...props }: ComboboxProps) => {
return <ComboboxProvider {...props}>{children}</ComboboxProvider>
}

Combobox.displayName = 'Combobox'
6 changes: 6 additions & 0 deletions packages/components/combobox/src/ComboboxContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ export type ComboboxContextCommonProps = PropsWithChildren<{
* When true, the items will be filtered depending on the value of the input (not case-sensitive).
*/
autoFilter?: boolean
/**
* By default, the combobox will clear or restore the input value to the selected item value on blur.
*/
allowCustomValue?: boolean
}>

interface ComboboxPropsSingle {
Expand Down Expand Up @@ -126,6 +130,7 @@ export const ComboboxProvider = ({
multiple = false,
disabled: disabledProp = false,
readOnly: readOnlyProp = false,
allowCustomValue = false,
state: stateProp,
}: ComboboxContextProps) => {
// Input state
Expand Down Expand Up @@ -172,6 +177,7 @@ export const ComboboxProvider = ({
id,
labelId,
inputValue,
allowCustomValue,
setInputValue: handleDownshiftInputChange,
onInputValueChange,
filteredItems: filteredItemsMap,
Expand Down
Loading

0 comments on commit 7ca285b

Please sign in to comment.