feat(ui): add searchable and multiple selection support to Select component
Enhanced the Select component with search filtering and multi-selection capabilities through new searchable, selectionMode, and searchPlaceholder props. Breaking changes: - The SelectProps interface now accepts a generic type parameter for selection mode Implementation details: - Created SelectTrigger, SelectContent, and SelectListBox components for modular composition - Integrated react-aria's Autocomplete and SearchField for search functionality - Added support for multiple selection mode - Added "No results found" empty state when search returns no matches - Improved CSS organization and updated visual styling for better consistency - Exported Option type as public API for type safety - Updated API reports and added comprehensive Storybook stories - Added documentation with examples for searchable and multiple selection modes Migration guide: If using SelectProps type directly, update the type parameter: ```diff - SelectProps + SelectProps<'single' | 'multiple'> ``` Component usage remains backward compatible - existing Select implementations require no changes. Signed-off-by: Johan Persson <johanopersson@gmail.com>
This commit is contained in:
@@ -11,6 +11,9 @@ import {
|
||||
selectDisabledSnippet,
|
||||
selectResponsiveSnippet,
|
||||
selectIconSnippet,
|
||||
selectSearchableSnippet,
|
||||
selectMultipleSnippet,
|
||||
selectSearchableMultipleSnippet,
|
||||
} from './select.props';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
@@ -87,6 +90,42 @@ Here's a view when the select is disabled.
|
||||
code={selectDisabledSnippet}
|
||||
/>
|
||||
|
||||
### Searchable
|
||||
|
||||
Here's a view when the select has searchable filtering.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<SelectSnippet story="Searchable" />}
|
||||
code={selectSearchableSnippet}
|
||||
/>
|
||||
|
||||
### Multiple Selection
|
||||
|
||||
Here's a view when the select allows multiple selections.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<SelectSnippet story="MultipleSelection" />}
|
||||
code={selectMultipleSnippet}
|
||||
/>
|
||||
|
||||
### Searchable with Multiple Selection
|
||||
|
||||
Here's a view when the select combines search and multiple selection.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<SelectSnippet story="SearchableMultiple" />}
|
||||
code={selectSearchableMultipleSnippet}
|
||||
/>
|
||||
|
||||
### Responsive
|
||||
|
||||
Here's a view when the select is responsive.
|
||||
|
||||
@@ -23,6 +23,12 @@ export const selectPropDefs: Record<string, PropDef> = {
|
||||
values: ['Array<{ value: string, label: string }>'],
|
||||
required: true,
|
||||
},
|
||||
selectionMode: {
|
||||
type: 'enum',
|
||||
values: ['single', 'multiple'],
|
||||
default: 'single',
|
||||
responsive: false,
|
||||
},
|
||||
placeholder: {
|
||||
type: 'string',
|
||||
default: 'Select an item',
|
||||
@@ -34,17 +40,23 @@ export const selectPropDefs: Record<string, PropDef> = {
|
||||
responsive: false,
|
||||
},
|
||||
value: {
|
||||
type: 'string',
|
||||
type: 'enum',
|
||||
values: ['string', 'string[]'],
|
||||
responsive: false,
|
||||
description:
|
||||
'Selected value (controlled). String for single selection, array for multiple.',
|
||||
},
|
||||
defaultValue: {
|
||||
type: 'string',
|
||||
type: 'enum',
|
||||
values: ['string', 'string[]'],
|
||||
responsive: false,
|
||||
description:
|
||||
'Initial value (uncontrolled). String for single selection, array for multiple.',
|
||||
},
|
||||
size: {
|
||||
type: 'enum',
|
||||
values: ['small', 'medium'],
|
||||
default: 'medium',
|
||||
default: 'small',
|
||||
responsive: true,
|
||||
},
|
||||
isOpen: {
|
||||
@@ -57,7 +69,7 @@ export const selectPropDefs: Record<string, PropDef> = {
|
||||
},
|
||||
disabledKeys: {
|
||||
type: 'enum',
|
||||
values: ['string[]'],
|
||||
values: ['Iterable<Key>'],
|
||||
responsive: false,
|
||||
},
|
||||
isDisabled: {
|
||||
@@ -72,14 +84,6 @@ export const selectPropDefs: Record<string, PropDef> = {
|
||||
type: 'boolean',
|
||||
responsive: false,
|
||||
},
|
||||
selectedKey: {
|
||||
type: 'string',
|
||||
responsive: false,
|
||||
},
|
||||
defaultSelectedKey: {
|
||||
type: 'string',
|
||||
responsive: false,
|
||||
},
|
||||
onOpenChange: {
|
||||
type: 'enum',
|
||||
values: ['(isOpen: boolean) => void'],
|
||||
@@ -87,7 +91,19 @@ export const selectPropDefs: Record<string, PropDef> = {
|
||||
},
|
||||
onSelectionChange: {
|
||||
type: 'enum',
|
||||
values: ['(key: Key | null) => void'],
|
||||
values: ['(key: Key | null) => void', '(keys: Selection) => void'],
|
||||
responsive: false,
|
||||
description:
|
||||
'Handler called when selection changes. Single mode: receives Key | null. Multiple mode: receives Selection.',
|
||||
},
|
||||
searchable: {
|
||||
type: 'boolean',
|
||||
default: 'false',
|
||||
responsive: false,
|
||||
},
|
||||
searchPlaceholder: {
|
||||
type: 'string',
|
||||
default: 'Search...',
|
||||
responsive: false,
|
||||
},
|
||||
...classNamePropDefs,
|
||||
@@ -151,3 +167,45 @@ export const selectResponsiveSnippet = `<Select
|
||||
label="Font family"
|
||||
options={[ ... ]}
|
||||
/>`;
|
||||
|
||||
export const selectSearchableSnippet = `<Select
|
||||
name="country"
|
||||
label="Country"
|
||||
searchable
|
||||
searchPlaceholder="Search countries..."
|
||||
options={[
|
||||
{ value: 'us', label: 'United States' },
|
||||
{ value: 'ca', label: 'Canada' },
|
||||
{ value: 'uk', label: 'United Kingdom' },
|
||||
{ value: 'fr', label: 'France' },
|
||||
{ value: 'de', label: 'Germany' },
|
||||
// ... more options
|
||||
]}
|
||||
/>`;
|
||||
|
||||
export const selectMultipleSnippet = `<Select
|
||||
name="options"
|
||||
label="Select multiple options"
|
||||
selectionMode="multiple"
|
||||
options={[
|
||||
{ value: 'option1', label: 'Option 1' },
|
||||
{ value: 'option2', label: 'Option 2' },
|
||||
{ value: 'option3', label: 'Option 3' },
|
||||
{ value: 'option4', label: 'Option 4' },
|
||||
]}
|
||||
/>`;
|
||||
|
||||
export const selectSearchableMultipleSnippet = `<Select
|
||||
name="skills"
|
||||
label="Skills"
|
||||
searchable
|
||||
selectionMode="multiple"
|
||||
searchPlaceholder="Filter skills..."
|
||||
options={[
|
||||
{ value: 'react', label: 'React' },
|
||||
{ value: 'typescript', label: 'TypeScript' },
|
||||
{ value: 'javascript', label: 'JavaScript' },
|
||||
{ value: 'python', label: 'Python' },
|
||||
// ... more options
|
||||
]}
|
||||
/>`;
|
||||
|
||||
Reference in New Issue
Block a user