Skip to content

Commit

Permalink
Merge pull request #1819 from adevinta/poc-combobox
Browse files Browse the repository at this point in the history
feat(combobox): combobox v1 poc
  • Loading branch information
Powerplex authored Feb 14, 2024
2 parents 89c768c + 91d7107 commit fa1035b
Show file tree
Hide file tree
Showing 33 changed files with 2,678 additions and 26 deletions.
11 changes: 11 additions & 0 deletions package-lock.json

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

10 changes: 10 additions & 0 deletions packages/components/combobox/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,16 @@
"react-dom": "^16.8 || ^17.0 || ^18.0",
"tailwindcss": "^3.0.0"
},
"dependencies": {
"@radix-ui/react-id": "1.0.1",
"@spark-ui/form-field": "^1.4.1",
"@spark-ui/icon": "^2.1.1",
"@spark-ui/icons": "^1.21.6",
"@spark-ui/popover": "^1.5.2",
"@spark-ui/visually-hidden": "^1.2.0",
"class-variance-authority": "0.7.0",
"downshift": "^8.2.3"
},
"repository": {
"type": "git",
"url": "https://github.com/adevinta/spark.git",
Expand Down
198 changes: 194 additions & 4 deletions packages/components/combobox/src/Combobox.doc.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Meta, Canvas } from '@storybook/addon-docs'
import { ArgTypes } from '@storybook/blocks'
import { ArgTypes as ExtendedArgTypes } from '@docs/helpers/ArgTypes'
import { Callout } from '@docs/helpers/Callout'

import { Combobox } from '.'

Expand All @@ -9,7 +10,7 @@ import * as stories from './Combobox.stories'

# Combobox

An input that behaves similarly to a select, with the addition of a free text input to filter options.
TODO

## Install

Expand All @@ -25,8 +26,197 @@ import { Combobox } from '@spark-ui/combobox'

## Props

<ArgTypes of={Combobox} />
<ExtendedArgTypes
of={Combobox}
description="A form input used for selecting a value: when collapsed it shows the currently selected option and when expanded, it shows a scrollable list of predefined options for the user to choose from."
subcomponents={{
'Combobox.Input': {
of: Combobox.Input,
description:
'The button that toggles the select. The Select.Popover will position itself by aligning over the trigger.',
},
'Combobox.LeadingIcon': {
of: Combobox.LeadingIcon,
description: 'Prepend a decorative icon inside the input (to the left).',
},
'Combobox.Popover': {
of: Combobox.Popover,
description: 'The part that is toggled and portaled when the trigger element is clicked.',
},
'Combobox.Items': {
of: Combobox.Items,
description: 'The wrapper which contains all the options.',
},
'Combobox.Group': {
of: Combobox.Group,
description:
'A wrapper for grouping a subset of options (Combobox.Item) when needed. Use in conjunction with Combobox.Label to ensure good accessibility via automatic labelling.',
},
'Combobox.Item': { of: Combobox.Item, description: 'Each option of the element field' },
'Combobox.ItemText': {
of: Combobox.ItemText,
description:
'The textual part of the item. It should only contain the text you want to see in the trigger when that item is selected. It should not be styled to ensure correct positioning.',
},
'Combobox.ItemIndicator': {
of: Combobox.ItemIndicator,
description:
'Renders when the item is selected. You can style this element directly, or you can use it as a wrapper to put an icon into, or both.',
},
'Combobox.Divider': {
of: Combobox.Divider,
description: 'Used to visually separate items in the select.',
},
'Combobox.Label': {
of: Combobox.Label,
description: "Used to render the label of a group. It won't be focusable using arrow keys.",
},
}}
/>

## Variants
## Usage

### Default

AutoSuggest is used as the default behaviour. The user can type anything in the input, the list is showing optional suggestions.

<Canvas of={stories.Default} />

### Controlled

<Canvas of={stories.Controlled} />

### Controlled open state

<Canvas of={stories.ControlledOpenState} />

### Disabled

Use `disabled` on the root component to disable the combobox entirely.

<Canvas of={stories.Disabled} />

### Disabled Item

Use `disabled` on individual `Combobox.Item` to disable them.

<Canvas of={stories.DisabledItem} />

### Filtering - AutoFilter

Use `autoFilter` to filter out items that does not match the input value. This behaviour is not case-sensitive.

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

<Canvas of={stories.FilteringAutoFilter} />

### Filtering - Manual

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

This example showcases case-sensitive filtering.

<Canvas of={stories.FilteringManual} />

### Groups

Similar to `optgroup` HTML tag, you can gather your items in groups.

It is important to use `Combobox.Label` inside each `Combobox.Group` to give it an accessible name.

<Canvas of={stories.Grouped} />

### Item indicator

Renders when the parent `ComboboxMenu.Item` is selected.

You can style this element directly, or you can use it as a wrapper to put an icon into, or both.

<Canvas of={stories.ItemIndicator} />

### Leading icon

Use `Combobox.LeadingIcon` inside `Combobox.Input` to prefix your trigger with an icon.

<Canvas of={stories.LeadingIcon} />

### Read only

Use `readOnly` prop to indicate the combobox is only readable.

<Canvas of={stories.ReadOnly} />

### Status

Use `state` prop to assign a specific state to the combobox, choosing from: `error`, `alert` and `success`. By doing so, the outline styles will be updated, and a status indicator will be displayed accordingly.

You could also wrap `Combobox` with a `FormField` and pass the prop to `Formfield` instead.

<Canvas of={stories.Statuses} />

## Multiple selection

### Default

When using `multiple` mode, the component manages an array of values and no longer a single value.

It means you must adapt `value`, `onValueChange` and `defaultValue` accordingly.

In `multiple` mode, the combobox won't close when the user selects an item, and it is possible to unselect every item.

In multiple selection mode, the input will go back to empty state after each selection in the list.
This is up to the developer to make it clear to the user which items are selected, by using other components such as chips, for example.

<Canvas of={stories.MultipleSelection} />

### Controlled

<Canvas of={stories.MultipleSelectionControlled} />

## Advanced usage

### Custom item

If your `Combobox.Item` contains anything else than raw text, you may use any JSX markup to customize it.

**If you do so, you MUST use `Combobox.ItemText` inside of your item to give it a proper accessible name.**

<Canvas of={stories.CustomItem} />

## Form field

### Label

Use `FormField.Label` to add a label to the input.

<Canvas of={stories.FormFieldLabel} />

### Hidden label

In certain cases, a visible label may not be necessary. To achieve this behavior, use the `VisuallyHidden` component.

<Canvas of={stories.FormFieldHiddenLabel} />

### Required

Use the `isRequired` prop of the `FormField` to indicate that the combobox is required.

<Canvas of={stories.FormFieldRequired} />

### Disabled

The combobox `disabled` field status can be managed by the FormField `disabled` flag.

<Canvas of={stories.FormFieldDisabled} />

### ReadOnly

Apply `readOnly` to the wrapping `FormField` to indicate the combobox is only readable.

<Canvas of={stories.FormFieldReadOnly} />

### Validation

Set the `state` prop of the `FormField` to `error` to indicate that the combobox is invalid. Optionally use the `FormField.ErrorMessage` to describe why the combobox is invalid.

<Canvas of={stories.FormFieldValidation} />
Loading

0 comments on commit fa1035b

Please sign in to comment.