UNPKG

react-search-select-dropdown

Version:

A customizable React autocomplete/search select dropdown component with TypeScript support

569 lines (496 loc) 16.2 kB
# React Search Select Dropdown A highly customizable React autocomplete/search select dropdown component with TypeScript support and inline styles. ## Features - 🔍 **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)} /> ); } ``` ## Usage Examples ### Example 1: String Values (Default) ```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> ); } ``` ### Example 2: Number Values ```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> ); } ``` ### Example 3: Custom Object Values ```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> ); } ``` ### Complete Example with Multiple Instances ```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; ``` ## API Reference ### Props | 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 | ### Types ```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; } ``` ## Customization ### Custom Styles 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} /> ); } ``` #### Available Style Properties - `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 ### Custom No Results Text ```tsx <Autocomplete options={options} noResultsText="No se encontraron resultados" /> ``` ### Custom Input Component 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" /> )} /> ); } ``` ## Keyboard Navigation - **Arrow Down**: Navigate to next option - **Arrow Up**: Navigate to previous option - **Enter**: Select highlighted option - **Escape**: Close dropdown and blur input ## License MIT ## Contributing Contributions are welcome! Please feel free to submit a Pull Request.