react-search-select-dropdown
Version:
A customizable React autocomplete/search select dropdown component with TypeScript support
569 lines (496 loc) • 16.2 kB
Markdown
A highly customizable React autocomplete/search select dropdown component with TypeScript support and inline styles.
- 🔍 **Search functionality** - Filter options as you type
- ⌨️ **Keyboard navigation** - Navigate with arrow keys, select with Enter
- 🎨 **Fully customizable styling** - Override default inline styles with `customStyles` prop
- 🎭 **Custom input support** - Use any UI library (Material-UI, Ant Design, etc.) via `renderInput` prop
- 🔤 **TypeScript support** - Full type definitions included
- 🎯 **Generic types** - Support for any value type (string, number, objects)
- 🌐 **i18n ready** - Customizable text for localization
- 📱 **Responsive design** - Works on all screen sizes
- ♿ **Accessible** - ARIA labels and keyboard support
- 💅 **Inline styles** - No CSS conflicts, minimal external CSS (only scrollbar)
## Installation
```bash
npm install react-search-select-dropdown
```
or
```bash
yarn add react-search-select-dropdown
```
or
```bash
pnpm add react-search-select-dropdown
```
## Quick Start
```tsx
import { Autocomplete } from 'react-search-select-dropdown';
import type { AutocompleteOption } from 'react-search-select-dropdown';
import 'react-search-select-dropdown/styles.css';
const options: AutocompleteOption[] = [
{ value: 'us', label: 'United States' },
{ value: 'ca', label: 'Canada' },
{ value: 'mx', label: 'Mexico' },
];
function App() {
return (
<Autocomplete
options={options}
placeholder="Search country..."
onSelect={(option) => console.log('Selected:', option)}
/>
);
}
```
```tsx
import { useState } from 'react';
import { Autocomplete } from 'react-search-select-dropdown';
import type { AutocompleteOption } from 'react-search-select-dropdown';
import 'react-search-select-dropdown/styles.css';
const countryOptions: AutocompleteOption[] = [
{ value: 'us', label: 'United States' },
{ value: 'ca', label: 'Canada' },
{ value: 'mx', label: 'Mexico' },
{ value: 'gb', label: 'United Kingdom' },
{ value: 'fr', label: 'France' },
];
function App() {
const [selectedOption, setSelectedOption] = useState<AutocompleteOption | null>(null);
return (
<div>
<Autocomplete
options={countryOptions}
placeholder="Search country..."
onSelect={(option) => {
setSelectedOption(option);
console.log('Selected country:', option);
}}
/>
{selectedOption && (
<div style={{
marginTop: '12px',
padding: '12px',
backgroundColor: '#f0fdf4',
border: '1px solid #86efac',
borderRadius: '6px',
color: '#166534',
fontSize: '14px',
lineHeight: '1.6'
}}>
Selected: {selectedOption.label} (code: {selectedOption.value})
</div>
)}
</div>
);
}
```
```tsx
import { useState } from 'react';
import { Autocomplete } from 'react-search-select-dropdown';
import type { AutocompleteOption } from 'react-search-select-dropdown';
import 'react-search-select-dropdown/styles.css';
const numberOptions: AutocompleteOption<number>[] = [
{ value: 1, label: 'Option One' },
{ value: 2, label: 'Option Two' },
{ value: 3, label: 'Option Three' },
{ value: 4, label: 'Option Four' },
{ value: 5, label: 'Option Five' },
];
function App() {
const [selectedNumber, setSelectedNumber] = useState<AutocompleteOption<number> | null>(null);
return (
<div>
<Autocomplete
options={numberOptions}
placeholder="Search option..."
onSelect={(option) => {
setSelectedNumber(option);
console.log('Selected number:', option);
}}
/>
{selectedNumber && (
<div style={{
marginTop: '12px',
padding: '12px',
backgroundColor: '#f0fdf4',
border: '1px solid #86efac',
borderRadius: '6px',
color: '#166534',
fontSize: '14px',
lineHeight: '1.6'
}}>
Selected: {selectedNumber.label} (ID: {selectedNumber.value})
</div>
)}
</div>
);
}
```
```tsx
import { useState } from 'react';
import { Autocomplete } from 'react-search-select-dropdown';
import type { AutocompleteOption } from 'react-search-select-dropdown';
import 'react-search-select-dropdown/styles.css';
interface User {
id: number;
email: string;
role: string;
}
const userOptions: AutocompleteOption<User>[] = [
{
value: { id: 1, email: 'john@example.com', role: 'admin' },
label: 'John Doe (Admin)'
},
{
value: { id: 2, email: 'jane@example.com', role: 'user' },
label: 'Jane Smith (User)'
},
{
value: { id: 3, email: 'bob@example.com', role: 'moderator' },
label: 'Bob Johnson (Moderator)'
},
];
function App() {
const [selectedUser, setSelectedUser] = useState<AutocompleteOption<User> | null>(null);
return (
<div>
<Autocomplete
options={userOptions}
placeholder="Search user..."
getOptionKey={(option) => option.value.id}
onSelect={(option) => {
setSelectedUser(option);
console.log('Selected user:', option);
}}
/>
{selectedUser && (
<div style={{
marginTop: '12px',
padding: '12px',
backgroundColor: '#f0fdf4',
border: '1px solid #86efac',
borderRadius: '6px',
color: '#166534',
fontSize: '14px',
lineHeight: '1.6'
}}>
<strong>Selected User:</strong><br />
Name: {selectedUser.label}<br />
ID: {selectedUser.value.id}<br />
Email: {selectedUser.value.email}<br />
Role: {selectedUser.value.role}
</div>
)}
</div>
);
}
```
```tsx
import { useState } from 'react';
import { Autocomplete } from 'react-search-select-dropdown';
import type { AutocompleteOption } from 'react-search-select-dropdown';
import 'react-search-select-dropdown/styles.css';
interface User {
id: number;
email: string;
role: string;
}
const countryOptions: AutocompleteOption[] = [
{ value: 'us', label: 'United States' },
{ value: 'ca', label: 'Canada' },
{ value: 'mx', label: 'Mexico' },
];
const numberOptions: AutocompleteOption<number>[] = [
{ value: 1, label: 'Option One' },
{ value: 2, label: 'Option Two' },
{ value: 3, label: 'Option Three' },
];
const userOptions: AutocompleteOption<User>[] = [
{ value: { id: 1, email: 'john@example.com', role: 'admin' }, label: 'John Doe (Admin)' },
{ value: { id: 2, email: 'jane@example.com', role: 'user' }, label: 'Jane Smith (User)' },
];
function App() {
const [selectedCountry, setSelectedCountry] = useState<AutocompleteOption | null>(null);
const [selectedNumber, setSelectedNumber] = useState<AutocompleteOption<number> | null>(null);
const [selectedUser, setSelectedUser] = useState<AutocompleteOption<User> | null>(null);
return (
<div style={{ padding: '40px', maxWidth: '800px', margin: '0 auto' }}>
<h1>Dropdown Autocomplete - Examples</h1>
{/* Example 1: String Values */}
<div style={{ marginBottom: '30px' }}>
<label style={{ display: 'block', marginBottom: '8px', fontWeight: 500 }}>
Example 1: String Values (Countries)
</label>
<Autocomplete
options={countryOptions}
placeholder="Search country..."
onSelect={setSelectedCountry}
/>
{selectedCountry && (
<div style={{
marginTop: '12px',
padding: '12px',
backgroundColor: '#f0fdf4',
border: '1px solid #86efac',
borderRadius: '6px',
color: '#166534',
fontSize: '14px',
lineHeight: '1.6'
}}>
<strong>Selected:</strong> {selectedCountry.label} (code: {selectedCountry.value})
</div>
)}
</div>
{/* Example 2: Number Values */}
<div style={{ marginBottom: '30px' }}>
<label style={{ display: 'block', marginBottom: '8px', fontWeight: 500 }}>
Example 2: Number Values
</label>
<Autocomplete
options={numberOptions}
placeholder="Search option..."
onSelect={setSelectedNumber}
/>
{selectedNumber && (
<div style={{
marginTop: '12px',
padding: '12px',
backgroundColor: '#f0fdf4',
border: '1px solid #86efac',
borderRadius: '6px',
color: '#166534',
fontSize: '14px',
lineHeight: '1.6'
}}>
<strong>Selected:</strong> {selectedNumber.label} (ID: {selectedNumber.value})
</div>
)}
</div>
{/* Example 3: Custom Object Values */}
<div style={{ marginBottom: '30px' }}>
<label style={{ display: 'block', marginBottom: '8px', fontWeight: 500 }}>
Example 3: Custom Object Values (Users)
</label>
<Autocomplete
options={userOptions}
placeholder="Search user..."
getOptionKey={(option) => option.value.id}
onSelect={setSelectedUser}
/>
{selectedUser && (
<div style={{
marginTop: '12px',
padding: '12px',
backgroundColor: '#f0fdf4',
border: '1px solid #86efac',
borderRadius: '6px',
color: '#166534',
fontSize: '14px',
lineHeight: '1.6'
}}>
<strong>Selected User:</strong><br />
Name: {selectedUser.label}<br />
ID: {selectedUser.value.id}<br />
Email: {selectedUser.value.email}<br />
Role: {selectedUser.value.role}
</div>
)}
</div>
</div>
);
}
export default App;
```
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `options` | `AutocompleteOption<T>[]` | **required** | Array of options to display |
| `placeholder` | `string` | `'Search...'` | Placeholder text for the input |
| `onSelect` | `(option: AutocompleteOption<T> \| null) => void` | `undefined` | Callback when an option is selected |
| `disabled` | `boolean` | `false` | Disable the autocomplete |
| `getOptionKey` | `(option: AutocompleteOption<T>) => string \| number` | Uses `value` or `label` | Function to generate unique keys for options |
| `customStyles` | `AutocompleteStyles` | `{}` | Override default styles with custom inline styles |
| `noResultsText` | `string` | `'No results found'` | Text to display when no options match the search |
| `value` | `AutocompleteOption<T> \| null` | `undefined` | Controlled value for the autocomplete |
| `renderInput` | `(props: AutocompleteInputProps) => ReactNode` | `undefined` | Custom input component renderer for maximum flexibility |
```typescript
interface AutocompleteOption<T = string> {
value: T;
label: string;
}
interface AutocompleteInputProps {
ref: React.RefObject<HTMLInputElement>;
type: string;
style: CSSProperties;
placeholder: string;
value: string;
onChange: (e: ChangeEvent<HTMLInputElement>) => void;
onFocus: () => void;
onBlur: () => void;
onKeyDown: (e: KeyboardEvent<HTMLInputElement>) => void;
disabled: boolean;
autoComplete: string;
}
interface AutocompleteStyles {
wrapper?: CSSProperties;
inputWrapper?: CSSProperties;
input?: CSSProperties;
inputFocus?: CSSProperties;
inputDisabled?: CSSProperties;
clearButton?: CSSProperties;
clearButtonHover?: CSSProperties;
arrow?: CSSProperties;
arrowOpen?: CSSProperties;
dropdown?: CSSProperties;
option?: CSSProperties;
optionHighlighted?: CSSProperties;
optionSelected?: CSSProperties;
optionHighlightedSelected?: CSSProperties;
noOptions?: CSSProperties;
}
interface AutocompleteProps<T = string> {
options: AutocompleteOption<T>[];
placeholder?: string;
onSelect?: (option: AutocompleteOption<T> | null) => void;
disabled?: boolean;
getOptionKey?: (option: AutocompleteOption<T>) => string | number;
customStyles?: AutocompleteStyles;
noResultsText?: string;
value?: AutocompleteOption<T> | null;
renderInput?: (props: AutocompleteInputProps) => ReactNode;
}
```
The component uses inline styles by default, which you can fully customize using the `customStyles` prop:
```tsx
import { Autocomplete, type AutocompleteStyles } from 'react-search-select-dropdown';
import type { AutocompleteOption } from 'react-search-select-dropdown';
import 'react-search-select-dropdown/styles.css';
const customStyles: AutocompleteStyles = {
input: {
borderColor: '#10b981',
fontSize: '16px',
},
inputFocus: {
borderColor: '#059669',
boxShadow: '0 0 0 3px rgba(16, 185, 129, 0.1)',
},
dropdown: {
borderColor: '#10b981',
boxShadow: '0 10px 25px rgba(0, 0, 0, 0.15)',
},
option: {
padding: '12px 16px',
fontSize: '15px',
},
optionHighlighted: {
backgroundColor: '#d1fae5',
},
optionSelected: {
backgroundColor: '#10b981',
color: 'white',
},
};
function App() {
return (
<Autocomplete
options={options}
placeholder="Search..."
customStyles={customStyles}
/>
);
}
```
- `wrapper` - Main container
- `inputWrapper` - Input container
- `input` - Input field base styles
- `inputFocus` - Input field when focused
- `inputDisabled` - Input field when disabled
- `clearButton` - Clear button base styles
- `clearButtonHover` - Clear button on hover
- `arrow` - Dropdown arrow base styles
- `arrowOpen` - Dropdown arrow when open
- `dropdown` - Dropdown list container
- `option` - Individual option base styles
- `optionHighlighted` - Option when highlighted
- `optionSelected` - Option when selected
- `optionHighlightedSelected` - Option when both highlighted and selected
- `noOptions` - No results message
```tsx
<Autocomplete
options={options}
noResultsText="No se encontraron resultados"
/>
```
You can provide your own input component using the `renderInput` prop. This is useful when you want to integrate with UI libraries like Material-UI, Ant Design, or any custom input:
```tsx
import { Autocomplete, AutocompleteInputProps } from 'react-search-select-dropdown';
import type { AutocompleteOption } from 'react-search-select-dropdown';
import 'react-search-select-dropdown/styles.css';
// Example with a custom styled input
function CustomInputExample() {
return (
<Autocomplete
options={options}
renderInput={(props: AutocompleteInputProps) => (
<input
{...props}
className="my-custom-input-class"
style={{
...props.style,
borderWidth: '2px',
borderStyle: 'solid',
borderColor: '#10b981',
fontSize: '16px',
fontWeight: 'bold',
}}
/>
)}
/>
);
}
// Example with Material-UI TextField
import TextField from '@mui/material/TextField';
function MaterialUIExample() {
return (
<Autocomplete
options={options}
renderInput={(props: AutocompleteInputProps) => (
<TextField
inputRef={props.ref}
value={props.value}
onChange={props.onChange}
onFocus={props.onFocus}
onBlur={props.onBlur}
onKeyDown={props.onKeyDown}
placeholder={props.placeholder}
disabled={props.disabled}
fullWidth
variant="outlined"
/>
)}
/>
);
}
```
- **Arrow Down**: Navigate to next option
- **Arrow Up**: Navigate to previous option
- **Enter**: Select highlighted option
- **Escape**: Close dropdown and blur input
MIT
Contributions are welcome! Please feel free to submit a Pull Request.