Skip to content

Commit 0e85763

Browse files
feat: combobox component (#565)
* wip: combobox * feat: combobox * feat: combobox docs and tests
1 parent 629a2ec commit 0e85763

File tree

19 files changed

+1499
-243
lines changed

19 files changed

+1499
-243
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
'use client';
2+
import { Combobox, Flex } from '@raystack/apsara';
3+
4+
const Page = () => {
5+
return (
6+
<Flex
7+
style={{
8+
height: '100vh',
9+
width: '100%',
10+
backgroundColor: 'var(--rs-color-background-base-primary)',
11+
padding: '32px'
12+
}}
13+
direction='column'
14+
gap={8}
15+
>
16+
<Combobox>
17+
<Combobox.Input />
18+
<Combobox.Content>
19+
<Combobox.Item value='item-1'>Item 1</Combobox.Item>
20+
<Combobox.Item value='item-2'>Item 2</Combobox.Item>
21+
<Combobox.Item value='item-3'>Item 3</Combobox.Item>
22+
</Combobox.Content>
23+
</Combobox>
24+
</Flex>
25+
);
26+
};
27+
28+
export default Page;
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
'use client';
2+
3+
import { getPropsString } from '@/lib/utils';
4+
5+
export const getCode = (props: Record<string, unknown>) => {
6+
const { multiple, ...rest } = props;
7+
return `
8+
<Combobox${getPropsString({ ...(multiple ? { multiple } : {}) })}>
9+
<Combobox.Input placeholder="Enter a fruit"${getPropsString(rest)} />
10+
<Combobox.Content>
11+
<Combobox.Item>Apple</Combobox.Item>
12+
<Combobox.Item>Banana</Combobox.Item>
13+
<Combobox.Item>Blueberry</Combobox.Item>
14+
<Combobox.Item>Grapes</Combobox.Item>
15+
<Combobox.Item>Pineapple</Combobox.Item>
16+
</Combobox.Content>
17+
</Combobox>`;
18+
};
19+
20+
export const playground = {
21+
type: 'playground',
22+
controls: {
23+
label: { type: 'text', initialValue: 'Fruits' },
24+
size: {
25+
type: 'select',
26+
options: ['small', 'large'],
27+
defaultValue: 'large'
28+
},
29+
multiple: {
30+
type: 'checkbox',
31+
defaultValue: false
32+
}
33+
},
34+
getCode
35+
};
36+
37+
export const basicDemo = {
38+
type: 'code',
39+
code: `
40+
<Combobox>
41+
<Combobox.Input placeholder="Enter a fruit" />
42+
<Combobox.Content>
43+
<Combobox.Item>Apple</Combobox.Item>
44+
<Combobox.Item>Banana</Combobox.Item>
45+
<Combobox.Item>Grape</Combobox.Item>
46+
<Combobox.Item>Orange</Combobox.Item>
47+
</Combobox.Content>
48+
</Combobox>`
49+
};
50+
51+
export const iconDemo = {
52+
type: 'code',
53+
code: `
54+
<Combobox>
55+
<Combobox.Input placeholder="Enter a fruit" />
56+
<Combobox.Content>
57+
<Combobox.Item leadingIcon={<Info size={16} />}>Apple</Combobox.Item>
58+
<Combobox.Item leadingIcon={<X size={16} />}>Banana</Combobox.Item>
59+
<Combobox.Item leadingIcon={<Home size={16} />}>Grape</Combobox.Item>
60+
<Combobox.Item leadingIcon={<Laugh size={16} />}>Orange</Combobox.Item>
61+
</Combobox.Content>
62+
</Combobox>`
63+
};
64+
65+
export const sizeDemo = {
66+
type: 'code',
67+
code: `
68+
<Flex align="center" gap="large">
69+
<Combobox>
70+
<Combobox.Input size="small" placeholder="Small combobox" />
71+
<Combobox.Content>
72+
<Combobox.Item>Option 1</Combobox.Item>
73+
<Combobox.Item>Option 2</Combobox.Item>
74+
</Combobox.Content>
75+
</Combobox>
76+
<Combobox>
77+
<Combobox.Input placeholder="Large combobox" />
78+
<Combobox.Content>
79+
<Combobox.Item>Option 1</Combobox.Item>
80+
<Combobox.Item>Option 2</Combobox.Item>
81+
</Combobox.Content>
82+
</Combobox>
83+
</Flex>`
84+
};
85+
86+
export const multipleDemo = {
87+
type: 'code',
88+
code: `
89+
<Combobox multiple>
90+
<Combobox.Input placeholder="Select fruits..." />
91+
<Combobox.Content>
92+
<Combobox.Item>Apple</Combobox.Item>
93+
<Combobox.Item>Banana</Combobox.Item>
94+
<Combobox.Item>Grape</Combobox.Item>
95+
<Combobox.Item>Orange</Combobox.Item>
96+
<Combobox.Item>Pineapple</Combobox.Item>
97+
<Combobox.Item>Mango</Combobox.Item>
98+
</Combobox.Content>
99+
</Combobox>`
100+
};
101+
102+
export const groupDemo = {
103+
type: 'code',
104+
code: `
105+
<Combobox>
106+
<Combobox.Input placeholder="Enter a fruit" />
107+
<Combobox.Content>
108+
<Combobox.Group>
109+
<Combobox.Label>Fruits</Combobox.Label>
110+
<Combobox.Item>Apple</Combobox.Item>
111+
<Combobox.Item>Banana</Combobox.Item>
112+
</Combobox.Group>
113+
<Combobox.Separator />
114+
<Combobox.Group>
115+
<Combobox.Label>Vegetables</Combobox.Label>
116+
<Combobox.Item>Carrot</Combobox.Item>
117+
<Combobox.Item>Broccoli</Combobox.Item>
118+
</Combobox.Group>
119+
</Combobox.Content>
120+
</Combobox>`
121+
};
122+
123+
export const controlledDemo = {
124+
type: 'code',
125+
code: `
126+
function ControlledDemo() {
127+
const [value, setValue] = React.useState("");
128+
const [inputValue, setInputValue] = React.useState("");
129+
130+
return (
131+
<Flex direction="column" gap="medium">
132+
<Text>Selected: {value || "None"}</Text>
133+
<Combobox
134+
value={value}
135+
onValueChange={setValue}
136+
inputValue={inputValue}
137+
onInputValueChange={setInputValue}
138+
>
139+
<Combobox.Input placeholder="Enter a fruit" />
140+
<Combobox.Content>
141+
<Combobox.Item>Apple</Combobox.Item>
142+
<Combobox.Item>Banana</Combobox.Item>
143+
<Combobox.Item>Grape</Combobox.Item>
144+
</Combobox.Content>
145+
</Combobox>
146+
</Flex>
147+
);
148+
}`
149+
};
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
---
2+
title: Combobox
3+
description: An input field with an attached dropdown that allows users to search and select from a list of options.
4+
tag: new
5+
---
6+
7+
import {
8+
playground,
9+
basicDemo,
10+
sizeDemo,
11+
iconDemo,
12+
multipleDemo,
13+
groupDemo,
14+
controlledDemo
15+
} from "./demo.ts";
16+
17+
<Demo data={playground} />
18+
19+
## Usage
20+
21+
```tsx
22+
import { Combobox } from "@raystack/apsara";
23+
```
24+
25+
## Combobox Props
26+
27+
The Combobox component is composed of several parts, each with their own props.
28+
29+
The root element is the parent component that manages the combobox state including open/close, input value, and selection. It is built using [Ariakit ComboboxProvider](https://ariakit.org/reference/combobox-provider) and [Radix Popover](https://www.radix-ui.com/primitives/docs/components/popover).
30+
31+
<auto-type-table path="./props.ts" name="ComboboxRootProps" />
32+
33+
### Combobox.Input Props
34+
35+
The input field that triggers the combobox dropdown and allows users to type and filter options.
36+
37+
<auto-type-table path="./props.ts" name="ComboboxInputProps" />
38+
39+
### Combobox.Content Props
40+
41+
The dropdown container that holds the combobox items.
42+
43+
<auto-type-table path="./props.ts" name="ComboboxContentProps" />
44+
45+
### Combobox.Item Props
46+
47+
Individual selectable options within the combobox.
48+
49+
<auto-type-table path="./props.ts" name="ComboboxItemProps" />
50+
51+
### Combobox.Group Props
52+
53+
A way to group related combobox items together.
54+
55+
<auto-type-table path="./props.ts" name="ComboboxGroupProps" />
56+
57+
### Combobox.Label Props
58+
59+
Renders a label in a combobox group. This component should be used inside Combobox.Group.
60+
61+
<auto-type-table path="./props.ts" name="ComboboxLabelProps" />
62+
63+
### Combobox.Separator Props
64+
65+
Visual divider between combobox items or groups.
66+
67+
<auto-type-table path="./props.ts" name="ComboboxSeparatorProps" />
68+
69+
## Examples
70+
71+
### Basic Combobox
72+
73+
A simple combobox with search functionality.
74+
75+
<Demo data={basicDemo} />
76+
77+
### Size
78+
79+
The combobox input supports different sizes.
80+
81+
<Demo data={sizeDemo} />
82+
83+
### Multiple Selection
84+
85+
To enable multiple selection, pass the `multiple` prop to the Combobox root element.
86+
87+
When multiple selection is enabled, the value, onValueChange, and defaultValue will be an array of strings. Selected items are displayed as chips in the input field.
88+
89+
<Demo data={multipleDemo} />
90+
91+
### Groups and Separators
92+
93+
Use Combobox.Group, Combobox.Label, and Combobox.Separator to organize items into logical groups.
94+
95+
<Demo data={groupDemo} />
96+
97+
### Controlled
98+
99+
You can control the combobox value and input value using the `value`, `onValueChange`, `inputValue`, and `onInputValueChange` props.
100+
101+
<Demo data={controlledDemo} />
102+
103+
## Accessibility
104+
105+
The Combobox component follows WAI-ARIA guidelines:
106+
107+
- Input has role `combobox`
108+
- Content has role `listbox`
109+
- Items have role `option`
110+
- Supports keyboard navigation (Arrow keys, Enter, Escape)
111+
- ARIA labels and descriptions for screen readers
112+
- Focus management between input and listbox
113+

0 commit comments

Comments
 (0)