Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions src/components/fields/ComboBox/ComboBox.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { Meta, StoryFn } from '@storybook/react';
import { userEvent, within } from '@storybook/test';
import { IconCoin } from '@tabler/icons-react';

import { SELECTED_KEY_ARG } from '../../../stories/FormFieldArgs';
import { baseProps } from '../../../stories/lists/baseProps';
import { wait } from '../../../test/utils/wait';
import { Button } from '../../actions/index';
Expand All @@ -12,7 +11,7 @@ import { Flow } from '../../layout/Flow';
import { ComboBox, CubeComboBoxProps } from './ComboBox';

export default {
title: 'Pickers/ComboBox',
title: 'Forms/ComboBox',
component: ComboBox,
subcomponents: { Item: ComboBox.Item, Section: ComboBox.Section },
args: { id: 'name', width: '200px', label: 'Choose your favourite color' },
Expand Down
365 changes: 365 additions & 0 deletions src/components/fields/ListBox.docs.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,365 @@
import { Meta, Canvas, Story, Controls } from '@storybook/blocks';
import { ListBox } from './ListBox';
import * as ListBoxStories from './ListBox.stories.tsx';

<Meta of={ListBoxStories} />

# ListBox

A list box component that allows users to select one or more items from a list of options. It supports sections, descriptions, optional search functionality, and full keyboard navigation. Built with React Aria's `useListBox` for accessibility and the Cube `tasty` style system for theming.

## When to Use

- Present a list of selectable options in a contained area
- Enable single or multiple selection from a set of choices
- Provide searchable selection for large lists of options
- Display structured data with sections and descriptions
- Create custom selection interfaces that need to remain visible
- Build form controls that require persistent option visibility

## Component

<Story of={ListBoxStories.Default} />

---

### Properties

<Controls of={ListBoxStories.Default} />

### Base Properties

Supports [Base properties](/docs/tasty-base-properties--docs)

### Styling Properties

#### styles

Customizes the root wrapper element of the component.

**Sub-elements:**
- `ValidationState` - Container for validation and loading indicators

#### searchInputStyles

Customizes the search input when `isSearchable` is true.

#### listStyles

Customizes the list container element.

#### optionStyles

Customizes individual option elements.

**Sub-elements:**
- `Label` - The main text of each option
- `Description` - Secondary descriptive text for options

#### sectionStyles

Customizes section wrapper elements.

#### headingStyles

Customizes section heading elements.

### Style Properties

The ListBox component supports all standard style properties:

`display`, `font`, `preset`, `hide`, `opacity`, `whiteSpace`, `gridArea`, `order`, `gridColumn`, `gridRow`, `placeSelf`, `alignSelf`, `justifySelf`, `zIndex`, `margin`, `inset`, `position`, `width`, `height`, `flexBasis`, `flexGrow`, `flexShrink`, `flex`, `reset`, `padding`, `paddingInline`, `paddingBlock`, `shadow`, `border`, `radius`, `overflow`, `scrollbar`, `outline`, `textAlign`, `color`, `fill`, `fade`, `textTransform`, `fontWeight`, `fontStyle`, `flow`, `placeItems`, `placeContent`, `alignItems`, `alignContent`, `justifyItems`, `justifyContent`, `align`, `justify`, `gap`, `columnGap`, `rowGap`, `gridColumns`, `gridRows`, `gridTemplate`, `gridAreas`

### Modifiers

The `mods` property accepts the following modifiers you can override:

| Modifier | Type | Description |
|----------|------|-------------|
| invalid | `boolean` | Whether the ListBox has validation errors |
| valid | `boolean` | Whether the ListBox is valid |
| disabled | `boolean` | Whether the ListBox is disabled |
| focused | `boolean` | Whether the ListBox has focus |
| loading | `boolean` | Whether the ListBox is in loading state |
| searchable | `boolean` | Whether the ListBox includes search functionality |

## Variants

### Sizes

- `small` - Compact size for dense interfaces
- `default` - Standard size
- `large` - Emphasized size for important selections

## Examples

### Basic Usage

```jsx
<ListBox label="Select a fruit" selectionMode="single">
<ListBox.Item key="apple">Apple</ListBox.Item>
<ListBox.Item key="banana">Banana</ListBox.Item>
<ListBox.Item key="cherry">Cherry</ListBox.Item>
</ListBox>
```

### With Search

<Story of={ListBoxStories.WithSearch} />

```jsx
<ListBox
label="Search fruits"
isSearchable={true}
searchPlaceholder="Type to search fruits..."
selectionMode="single"
>
<ListBox.Item key="apple">Apple</ListBox.Item>
<ListBox.Item key="banana">Banana</ListBox.Item>
<ListBox.Item key="cherry">Cherry</ListBox.Item>
{/* More items... */}
</ListBox>
```

### With Descriptions

<Story of={ListBoxStories.WithDescriptions} />

```jsx
<ListBox label="Choose a framework" selectionMode="single">
<ListBox.Item key="react" description="A JavaScript library for building user interfaces">
React
</ListBox.Item>
<ListBox.Item key="vue" description="The Progressive JavaScript Framework">
Vue.js
</ListBox.Item>
<ListBox.Item key="angular" description="Platform for building mobile and desktop web applications">
Angular
</ListBox.Item>
</ListBox>
```

### With Sections

<Story of={ListBoxStories.WithSections} />

```jsx
<ListBox label="Select food items" selectionMode="single">
<ListBox.Section title="Fruits">
<ListBox.Item key="apple">Apple</ListBox.Item>
<ListBox.Item key="banana">Banana</ListBox.Item>
</ListBox.Section>
<ListBox.Section title="Vegetables">
<ListBox.Item key="carrot">Carrot</ListBox.Item>
<ListBox.Item key="broccoli">Broccoli</ListBox.Item>
</ListBox.Section>
</ListBox>
```

### Multiple Selection

<Story of={ListBoxStories.MultipleSelection} />

```jsx
<ListBox
label="Select skills (multiple)"
selectionMode="multiple"
isSearchable={true}
>
<ListBox.Item key="html">HTML</ListBox.Item>
<ListBox.Item key="css">CSS</ListBox.Item>
<ListBox.Item key="javascript">JavaScript</ListBox.Item>
<ListBox.Item key="react">React</ListBox.Item>
</ListBox>
```

### Controlled Selection

<Story of={ListBoxStories.ControlledExample} />

```jsx
const [selectedKey, setSelectedKey] = useState('apple');

<ListBox
label="Controlled ListBox"
selectedKey={selectedKey}
onSelectionChange={setSelectedKey}
selectionMode="single"
>
<ListBox.Item key="apple">Apple</ListBox.Item>
<ListBox.Item key="banana">Banana</ListBox.Item>
<ListBox.Item key="cherry">Cherry</ListBox.Item>
</ListBox>
```

### Different Sizes

```jsx
<ListBox size="small" label="Small ListBox" selectionMode="single">
<ListBox.Item key="option1">Option 1</ListBox.Item>
</ListBox>

<ListBox size="large" label="Large ListBox" selectionMode="single">
<ListBox.Item key="option1">Option 1</ListBox.Item>
</ListBox>
```

### Validation States

<Story of={ListBoxStories.ValidationStates} />

```jsx
<ListBox
label="Valid Selection"
validationState="valid"
selectionMode="single"
defaultSelectedKey="option1"
>
<ListBox.Item key="option1">Valid Option</ListBox.Item>
</ListBox>

<ListBox
label="Invalid Selection"
validationState="invalid"
selectionMode="single"
message="Please select a valid option"
>
<ListBox.Item key="option1">Option 1</ListBox.Item>
</ListBox>
```

### Disabled State

<Story of={ListBoxStories.DisabledState} />

```jsx
<ListBox
label="Disabled ListBox"
isDisabled={true}
selectionMode="single"
>
<ListBox.Item key="option1">Option 1</ListBox.Item>
<ListBox.Item key="option2">Option 2</ListBox.Item>
</ListBox>
```

### Loading State

<Story of={ListBoxStories.LoadingState} />

```jsx
<ListBox
label="Loading ListBox"
isLoading={true}
selectionMode="single"
>
<ListBox.Item key="option1">Option 1</ListBox.Item>
</ListBox>
```

## Accessibility

### Keyboard Navigation

- `Tab` - Moves focus to the ListBox
- `Arrow Down/Up` - Moves focus to the next/previous option
- `Home` - Moves focus to the first option
- `End` - Moves focus to the last option
- `Page Down/Up` - Moves focus by page in long lists
- `Space` - Selects the focused option (single selection) or toggles selection (multiple)
- `Enter` - Selects the focused option and can close associated popovers
- `A-Z` - Moves focus to the next option starting with the typed letter
- `Escape` - Clears search input when searchable

### Screen Reader Support

- Component announces as "listbox" to screen readers
- Current selection and total options are announced
- Section headings are properly associated with their options
- Option descriptions are read along with option labels
- Search functionality is announced when available
- Loading and validation states are communicated

### ARIA Properties

- `aria-label` - Provides accessible label when no visible label exists
- `aria-labelledby` - References external label elements
- `aria-describedby` - References additional descriptive text
- `aria-multiselectable` - Indicates if multiple selection is allowed
- `aria-activedescendant` - References the currently focused option
- `aria-expanded` - Used with search functionality
- `aria-required` - Indicates if selection is required
- `aria-invalid` - Indicates validation state

## Best Practices

1. **Do**: Provide clear, descriptive labels and option text
```jsx
<ListBox label="Select your preferred programming language">
<ListBox.Item key="js" description="Dynamic, interpreted language">
JavaScript
</ListBox.Item>
</ListBox>
```

2. **Don't**: Use ListBox for navigation or actions
```jsx
<ListBox> {/* Use Menu or navigation components instead */}
<ListBox.Item key="home">Go Home</ListBox.Item>
<ListBox.Item key="logout">Logout</ListBox.Item>
</ListBox>
```

3. **Performance**: Enable search for lists with more than 10-15 options
4. **Organization**: Use sections to group related options logically
5. **Descriptions**: Provide helpful descriptions for complex or technical options
6. **Validation**: Provide clear error messages for validation failures
7. **Selection**: Consider multiple selection for scenarios where users might need several options

## Integration with Forms

<Story of={ListBoxStories.InForm} />

This component supports all [Field properties](/docs/forms-field--docs) when used within a Form.

```jsx
<Form onSubmit={handleSubmit}>
<ListBox
name="technology"
label="Preferred Technology"
description="Select your preferred technology stack"
isRequired
selectionMode="single"
isSearchable
>
<ListBox.Section title="Frontend">
<ListBox.Item key="react">React</ListBox.Item>
<ListBox.Item key="vue">Vue.js</ListBox.Item>
</ListBox.Section>
<ListBox.Section title="Backend">
<ListBox.Item key="nodejs">Node.js</ListBox.Item>
<ListBox.Item key="python">Python</ListBox.Item>
</ListBox.Section>
</ListBox>

<Form.Submit>Submit</Form.Submit>
</Form>
```

## Suggested Improvements

- Add support for custom option rendering with more complex layouts
- Implement virtual scrolling for very large lists (1000+ items)
- Add support for option groups with different selection behaviors
- Consider adding drag-and-drop reordering functionality
- Implement async loading with pagination for dynamic data
- Add support for option icons and avatars
- Consider adding keyboard shortcuts for common actions (select all, clear all)

## Related Components

- [Select](/docs/pickers-select--docs) - For dropdown selection that saves space
- [ComboBox](/docs/pickers-combobox--docs) - For searchable selection with text input
- [RadioGroup](/docs/forms-radiogroup--docs) - For single selection with radio buttons
- [Checkbox](/docs/forms-checkbox--docs) - For multiple selection with checkboxes
- [Menu](/docs/pickers-menu--docs) - For action-oriented lists and navigation
Loading
Loading