Skip to content

Commit

Permalink
feat(combobox): combobox v1 poc
Browse files Browse the repository at this point in the history
  • Loading branch information
Powerplex committed Jan 17, 2024
1 parent ffd3d22 commit 9ec4f0d
Show file tree
Hide file tree
Showing 28 changed files with 2,568 additions and 11 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.

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
22 changes: 22 additions & 0 deletions packages/components/combobox/src/ComboBoxEmpty.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { forwardRef, type ReactNode, type Ref } from 'react'

import { useComboboxContext } from './ComboboxContext'

interface EmptyProps {
className?: string
children: ReactNode
}

export const Empty = forwardRef(
({ className, children }: EmptyProps, forwardedRef: Ref<HTMLDivElement>) => {
const { filteredItemsMap } = useComboboxContext()

return filteredItemsMap.size === 0 ? (
<div ref={forwardedRef} className={className}>
{children}
</div>
) : null
}
)

Empty.displayName = 'Combobox.Empty'
189 changes: 185 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,188 @@ 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.Value': {
of: Combobox.Value,
description:
"The part that reflects the selected value. By default the selected item's text will be rendered. if you require more control, you can instead control the select and pass your own children. It should not be styled to ensure correct positioning. An optional placeholder prop is also available for when the select has no value.",
},
'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} />

### 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 - AutoSelect

Use `autoSelect` 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.FilteringAutoSelect} />

### 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

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} />

## 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 9ec4f0d

Please sign in to comment.