Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
38 changes: 36 additions & 2 deletions apps/www/src/content/docs/components/combobox/demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,20 +78,54 @@ export const groupDemo = {
<Combobox.Input placeholder="Search items" width={240} />
<Combobox.Content>
<Combobox.Group>
<Combobox.Label>Fruits</Combobox.Label>
<Combobox.GroupLabel>Fruits</Combobox.GroupLabel>
<Combobox.Item value="apple">Apple</Combobox.Item>
<Combobox.Item value="banana">Banana</Combobox.Item>
</Combobox.Group>
<Combobox.Separator />
<Combobox.Group>
<Combobox.Label>Vegetables</Combobox.Label>
<Combobox.GroupLabel>Vegetables</Combobox.GroupLabel>
<Combobox.Item value="carrot">Carrot</Combobox.Item>
<Combobox.Item value="broccoli">Broccoli</Combobox.Item>
</Combobox.Group>
</Combobox.Content>
</Combobox>`
};

export const labelDemo = {
type: 'code',
code: `
<Combobox multiple>
<Flex direction="column" gap={2}>
<Combobox.Label>Fruits</Combobox.Label>
<Flex align="center" gap={2}>
<Combobox.Input placeholder="Select fruits" width={300} />
<Combobox.Clear />
</Flex>
</Flex>
<Combobox.Content>
<Combobox.Item>Apple</Combobox.Item>
<Combobox.Item>Banana</Combobox.Item>
<Combobox.Item>Grape</Combobox.Item>
<Combobox.Item>Orange</Combobox.Item>
</Combobox.Content>
</Combobox>`
};

export const emptyDemo = {
type: 'code',
code: `
<Combobox>
<Combobox.Input placeholder="Type 'xyz'" width={240} />
<Combobox.Content>
<Combobox.Empty>No fruit matches your search</Combobox.Empty>
<Combobox.Item>Apple</Combobox.Item>
<Combobox.Item>Banana</Combobox.Item>
<Combobox.Item>Grape</Combobox.Item>
</Combobox.Content>
</Combobox>`
};

export const iconDemo = {
type: 'code',
code: `
Expand Down
76 changes: 69 additions & 7 deletions apps/www/src/content/docs/components/combobox/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import {
withFieldDemo,
controlledDemo,
createItemsDemo,
labelDemo,
emptyDemo,
} from "./demo.ts";

<Demo data={playground} />
Expand All @@ -25,8 +27,18 @@ Import and assemble the component:
import { Combobox } from "@raystack/apsara";

<Combobox>
<Combobox.Label />
<Combobox.Input />
<Combobox.Content />
<Combobox.Clear />
<Combobox.Content>
<Combobox.Empty />
<Combobox.Status />
<Combobox.Group>
<Combobox.GroupLabel />
<Combobox.Item />
</Combobox.Group>
<Combobox.Separator />
</Combobox.Content>
</Combobox>
```

Expand Down Expand Up @@ -54,7 +66,7 @@ Wrap the Combobox with [Field](/docs/components/field) to add a label, descripti

### Groups and separators

Organize items into groups with labels and visual separators. Groups and labels are automatically hidden when the user is searching.
Organize items into groups with `Combobox.GroupLabel` and separators. Groups, group labels, and separators are hidden while the user is searching.

<Demo data={groupDemo} />

Expand All @@ -64,8 +76,22 @@ Pass the `multiple` prop to enable multi-select. Selected values appear as chips

Each chip shows its value as text. To show a label instead, pass `itemToStringLabel` to the root. Values in the `{ value, label }` shape show their `label` without it.

The input shows the first two chips and a "+N" count for the rest. Press Backspace in an empty input to remove the last value. Press Left Arrow at the start of the input to move focus to the chips. On a focused chip, press Backspace or Delete to remove it.

<Demo data={multipleDemo} />

### Label and clear button

`Combobox.Label` renders a visible label linked to the input. `Combobox.Clear` renders a button that clears the value. It shows only when there is a value or input text. Place both anywhere inside `Combobox`.

<Demo data={labelDemo} />

### Empty state

`Combobox.Content` shows "No results" when no item matches the input. To change the text, add `Combobox.Empty` as a direct child of `Combobox.Content`.

<Demo data={emptyDemo} />

### Controlled

Use `value` and `onValueChange` for controlled behavior.
Expand Down Expand Up @@ -114,17 +140,41 @@ When no `value` prop is provided, the text content of `children` is used as the

<auto-type-table path="./props.ts" name="ComboboxItemProps" />

### Label Props

A visible label for the input. It renders a `<label>` whose `htmlFor` is the input `id`. Inside a [Field](/docs/components/field), use the Field label instead.

<auto-type-table path="./props.ts" name="ComboboxLabelProps" />

### Clear Props

A button that clears the value and the input text. It renders [IconButton](/docs/components/icon-button) and shows only when there is something to clear.

<auto-type-table path="./props.ts" name="ComboboxClearProps" />

### Empty Props

The text shown when no item matches the input. `Combobox.Content` renders one with "No results" by default. Add `Combobox.Empty` as a direct child of `Combobox.Content` to change the text. Pass `null` as children to show nothing.

<auto-type-table path="./props.ts" name="ComboboxEmptyProps" />

### Status Props

A visually hidden live region. Screen readers announce its text when it changes. `Combobox.Content` renders one by default that announces the number of matching items, for example "3 results". Add `Combobox.Status` as a direct child of `Combobox.Content` to change the text, for example to announce loading.

<auto-type-table path="./props.ts" name="ComboboxStatusProps" />

### Group Props

A way to group related combobox items together.

<auto-type-table path="./props.ts" name="ComboboxGroupProps" />

### Label Props
### GroupLabel Props

Renders a label in a combobox group. This component should be wrapped with `Combobox.Group`.
Renders a label in a combobox group. Wrap it in `Combobox.Group`.

<auto-type-table path="./props.ts" name="ComboboxLabelProps" />
<auto-type-table path="./props.ts" name="ComboboxGroupLabelProps" />

### Separator Props

Expand All @@ -138,12 +188,20 @@ Every rendered part carries a stable `data-slot` attribute for [styling and test

| Slot | Element |
|------|---------|
| `combobox-label` | The label for the input |
| `combobox-input` | The text input that opens the popup |
| `combobox-chips` | The container for chips and the input in multiple mode |
| `combobox-chip` | A selected value in multiple mode |
| `combobox-chip-remove` | The button that removes a chip |
| `combobox-chip-overflow` | The "+N" count of chips that are not shown |
| `combobox-clear` | The button that clears the value |
| `combobox-positioner` | Wrapper that positions the popup |
| `combobox-content` | The popup container |
| `combobox-status` | The hidden live region that announces the number of results |
| `combobox-empty` | The text shown when no item matches |
| `combobox-list` | The scrollable list of items |
| `combobox-group` | A group of related items |
| `combobox-label` | The label for a group |
| `combobox-group-label` | The label for a group |
| `combobox-separator` | The divider between items or groups |
| `combobox-item` | A selectable option row |
| `combobox-item-text` | The text label inside an item (when its children is a string) |
Expand All @@ -160,7 +218,11 @@ Every rendered part carries a stable `data-slot` attribute for [styling and test
the whole time, so typing to filter never loses the user's place.
- In multiple mode, each item renders a checkbox that mirrors its selected
state, and selected values appear in the input as removable chips.
- The chips use Base UI's `Combobox.Chips`, which has `role="toolbar"`. Arrow
keys move between chips, and Backspace or Delete removes the focused chip.
- `Combobox.Empty` and `Combobox.Status` are polite live regions. Screen
readers announce "No results" and the number of matching items.
- Group labels use Base UI's `Combobox.GroupLabel`, which links the label to
its group via `aria-labelledby`, so wrap `Combobox.Label` in `Combobox.Group`.
its group via `aria-labelledby`, so wrap `Combobox.GroupLabel` in `Combobox.Group`.
- Wrap the Combobox with [Field](/docs/components/field) to associate a
visible label, description, and error message with the input.
62 changes: 62 additions & 0 deletions apps/www/src/content/docs/components/combobox/props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ export interface ComboboxRootProps {

/** Converts a value to the text shown in the input and in multi-select chips. */
itemToStringLabel?: (value: string) => string;

/** The `id` of the input. `Combobox.Label` uses it. */
id?: string;
}

export interface ComboboxInputProps {
Expand Down Expand Up @@ -138,6 +141,65 @@ export interface ComboboxGroupProps {
}

export interface ComboboxLabelProps {
/** Shows `optionalText` after the label when `false`. */
required?: boolean;

/**
* Text shown after the label when `required` is `false`.
* @default "(optional)"
*/
optionalText?: string;

/** Additional CSS class names. */
className?: string;
}

export interface ComboboxClearProps {
/**
* Size of the button.
* @default 2
*/
size?: 1 | 2 | 3 | 4;

/**
* Accessible name of the button.
* @default "Clear"
*/
'aria-label'?: string;

/**
* The icon inside the button.
* @default <ClearIcon />
*/
children?: React.ReactNode;

/** Additional CSS class names. */
className?: string;
}

export interface ComboboxEmptyProps {
/**
* The text shown when no item matches.
* @default "No results"
*/
children?: React.ReactNode;

/** Additional CSS class names. */
className?: string;
}

export interface ComboboxStatusProps {
/**
* The text to announce.
* @defaultValue The number of matching items, for example "3 results"
*/
children?: React.ReactNode;

/** Additional CSS class names. */
className?: string;
}

export interface ComboboxGroupLabelProps {
/** Additional CSS class names. */
className?: string;
}
Expand Down
Loading
Loading