Filter Search Panel Block
The FilterSearchPanel Component is a fully customizable and accessible search interface with filtering capabilities built with React and TypeScript. It provides a complete search and filter experience with modern design patterns, form validation, and flexible customization options. Built on top of MUI components for consistent styling.
๐ Installationโ
npm install @nodeblocks/frontend-filter-search-panel-block@0.3.0
๐ Usageโ
import {FilterSearchPanel} from '@nodeblocks/frontend-filter-search-panel-block';
- Basic Usage
- Advanced Usage
function SimpleFilterSearchPanel() { const [filters, setFilters] = useState([ {label: 'Active', key: 'status-active', groupName: 'Status'}, {label: 'Web Development', key: 'category-web', groupName: 'Category'}, ]); const handleRemoveFilter = (filter) => { setFilters(filters.filter(f => f.key !== filter.key)); }; const handleSearch = ({search}) => { console.log('Search submitted:', search); setFilters(prev => [...prev, {label: search, key: search, groupName: 'Custom'}]); }; return ( <FilterSearchPanel filters={filters} onClickRemoveFilter={handleRemoveFilter} onClickFilterButton={() => console.log('Filter button clicked')} onSearch={handleSearch} searchPlaceholder="Search services..." noFilterText="No filters applied" filterLabel="Filter Settings" > <FilterSearchPanel.SearchInput /> <FilterSearchPanel.ActionGroup /> </FilterSearchPanel> ); }
function AdvancedFilterSearchPanel() { const [filters, setFilters] = useState([ {label: 'Frontend', key: 'tech-frontend', groupName: 'Stack'}, {label: 'React', key: 'framework-react', groupName: 'Stack'}, {label: 'Senior', key: 'level-senior', groupName: 'Level'}, ]); const handleRemoveFilter = (filter) => { setFilters(filters.filter(f => f.key !== filter.key)); }; const handleSearch = ({search}) => { if (search.trim()) { setFilters(prev => [...prev, {label: search, key: `search-${Date.now()}`, groupName: 'Query'}]); } }; return ( <FilterSearchPanel filters={filters} onClickRemoveFilter={handleRemoveFilter} onClickFilterButton={() => console.log('Filter panel opened')} onSearch={handleSearch} searchPlaceholder="Search developers by skill, role, or name..." noFilterText="No active filters" filterLabel="Filters" spacing={2.5} sx={{ background: '#ffffff', borderRadius: '16px', p: 3, border: '1px solid #e2e8f0', boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.05), 0 10px 15px -3px rgba(0, 0, 0, 0.08)', }} > {({defaultBlocks}) => { const header = ( <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '8px', }} > <div style={{display: 'flex', alignItems: 'center', gap: '12px'}}> <div style={{ width: '40px', height: '40px', borderRadius: '10px', background: 'linear-gradient(135deg, #0ea5e9 0%, #6366f1 100%)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '18px', color: '#fff', }} > โก </div> <div> <div style={{ fontSize: '16px', fontWeight: '600', color: '#1e293b', letterSpacing: '-0.01em', }} > Developer Search </div> <div style={{ fontSize: '13px', color: '#64748b', }} > Find the perfect match for your team </div> </div> </div> <div style={{ display: 'flex', alignItems: 'center', gap: '6px', padding: '6px 12px', background: '#ecfdf5', borderRadius: '20px', border: '1px solid #a7f3d0', }} > <div style={{ width: '6px', height: '6px', borderRadius: '50%', background: '#10b981', }} /> <span style={{fontSize: '12px', color: '#059669', fontWeight: '500'}}> {127 + filters.length * 3} online </span> </div> </div> ); const statsBar = ( <div style={{ display: 'flex', gap: '16px', padding: '14px 20px', background: '#f8fafc', borderRadius: '12px', border: '1px solid #e2e8f0', }} > {[ {label: 'Active Filters', value: filters.length, color: '#3b82f6'}, { label: 'Results', value: Math.max(50, 234 - filters.length * 45), color: '#8b5cf6', }, {label: 'Avg. Rate', value: '$85/hr', color: '#0891b2'}, ].map((stat, i) => ( <div key={i} style={{ flex: 1, textAlign: 'center', borderRight: i < 2 ? '1px solid #e2e8f0' : 'none', }} > <div style={{ fontSize: '20px', fontWeight: '700', color: stat.color, fontFamily: '"JetBrains Mono", monospace', }} > {stat.value} </div> <div style={{ fontSize: '11px', color: '#94a3b8', textTransform: 'uppercase', letterSpacing: '0.05em', marginTop: '2px', }} > {stat.label} </div> </div> ))} </div> ); return { blocks: { ...defaultBlocks, header, statsBar, searchInput: ( <FilterSearchPanel.SearchInput sx={{ '& .MuiOutlinedInput-root': { bgcolor: '#f8fafc', borderRadius: '10px', '& .MuiOutlinedInput-notchedOutline': { borderColor: '#e2e8f0', }, '&:hover .MuiOutlinedInput-notchedOutline': { borderColor: '#94a3b8', }, '&.Mui-focused .MuiOutlinedInput-notchedOutline': { borderColor: '#6366f1', borderWidth: '2px', }, }, '& .MuiInputBase-input::placeholder': { color: '#94a3b8', opacity: 1, }, }} /> ), actionGroup: ( <FilterSearchPanel.ActionGroup sx={{ '& .MuiButton-outlined': { borderColor: '#e2e8f0', color: '#475569', bgcolor: '#ffffff', '&:hover': { borderColor: '#6366f1', bgcolor: '#f5f3ff', color: '#6366f1', }, }, }} /> ), }, blockOrder: ['header', 'statsBar', 'searchInput', 'actionGroup'], }; }} </FilterSearchPanel> ); }
๐ง Props Referenceโ
Main Component Propsโ
| Prop | Type | Default | Description |
|---|---|---|---|
filters | FilterChip[] | [] | Array of currently selected filter chips to display |
defaultSearchValue | string | undefined | Default value for the search input field |
onClickRemoveFilter | (filter: FilterChip) => void | undefined | Callback function triggered when a filter chip is removed |
onClickFilterButton | () => void | undefined | Callback function triggered when the filter button is clicked |
onSearchChange | (event: React.ChangeEvent<HTMLInputElement>) => void | undefined | Callback function triggered when search input value changes |
onSearch | (data: T) => void | undefined | Callback function triggered when form is submitted with search data |
searchPlaceholder | string | undefined | Placeholder text for the search input field |
noFilterText | string | undefined | Text displayed when no filters are selected |
filterLabel | string | undefined | Label text for the filter button |
spacing | number | string | 1.5 | Spacing between child elements (MUI Stack spacing) |
direction | StackDirection | 'column' | Direction of the stack layout |
className | string | undefined | Additional CSS class name for styling the form container |
sx | SxProps<Theme> | { p: 2, bgcolor: 'background.default' } | MUI system props for custom styling |
children | BlocksOverride | undefined | Custom block components to override default rendering |
Note: This component inherits all MUI Stack component props (rendered as component="form"). The form submission is handled internally - use onSearch callback to receive form data.
Sub-Componentsโ
The FilterSearchPanel component provides several sub-components. All sub-components receive their default values from the main component's context and can override these values through props.
FilterSearchPanel.SearchInputโ
Renders the search input field using MUI TextField.
| Prop | Type | Default | Description |
|---|---|---|---|
searchPlaceholder | string | 'ๆฐ่ฆใตใผใใน้็บ' | Placeholder text shown in the search input |
onSearchChange | (event: React.ChangeEvent<HTMLInputElement>) => void | Context value | Callback function triggered when input value changes |
defaultSearchValue | string | Context value | Default value for the search input |
name | string | 'search' | Form field name for the input |
type | string | 'text' | HTML input type |
children | ReactNode | undefined | Custom content - replaces entire TextField |
className | string | undefined | Additional CSS class name for styling |
sx | SxProps<Theme> | undefined | MUI system props for custom styling |
Note: This component inherits all MUI TextField component props. Default size="small". Includes a search icon button that submits the form.
FilterSearchPanel.FilterButtonโ
Renders the filter settings button using MUI Button.
| Prop | Type | Default | Description |
|---|---|---|---|
filterLabel | string | '็ต่พผใฟ่จญๅฎ' | Label text displayed on the filter button |
onClickFilterButton | () => void | Context value | Callback function triggered when button is clicked |
children | ReactNode | Default icon + label | Custom content to override default rendering |
className | string | undefined | Additional CSS class name for styling |
sx | SxProps<Theme> | undefined | MUI system props for custom styling |
variant | ButtonVariant | 'outlined' | MUI Button variant |
size | ButtonSize | 'small' | MUI Button size |
Note: This component inherits all MUI Button component props. Default content includes a Tune icon from @mui/icons-material.
FilterSearchPanel.SelectedFilterListโ
Renders the list of selected filters or empty state using MUI Box.
| Prop | Type | Default | Description |
|---|---|---|---|
filters | FilterChip[] | Context value | Array of filter chips to display |
onClickRemoveFilter | (filter: FilterChip) => void | Context value | Callback function triggered when a filter is removed |
noFilterText | string | 'ๆกไปถๆช่จญๅฎ' | Text displayed when no filters are selected |
children | ReactNode | Default filter list | Custom content to override default rendering |
className | string | undefined | Additional CSS class name for styling |
sx | SxProps<Theme> | undefined | MUI system props for custom styling |
Note: This component inherits all MUI Box component props. Groups filters by groupName property and displays group labels.
FilterSearchPanel.FilterBadgeโ
Renders an individual filter chip using MUI Chip.
| Prop | Type | Default | Description |
|---|---|---|---|
filter | FilterChip | Required | Filter chip object containing label, key, and optional groupName |
onClickRemoveFilter | (filter: FilterChip) => void | Context value | Callback function triggered when the filter is removed |
children | ReactNode | filter.label | Custom content to override label text |
className | string | undefined | Additional CSS class name for styling |
sx | SxProps<Theme> | undefined | MUI system props for custom styling |
Note: This component inherits all MUI Chip component props. Includes a custom close icon for deletion.
FilterSearchPanel.ActionGroupโ
Combines FilterButton and SelectedFilterList in a horizontal layout using MUI Stack.
| Prop | Type | Default | Description |
|---|---|---|---|
filters | FilterChip[] | Context value | Array of filter chips (passed to SelectedFilterList) |
onClickRemoveFilter | (filter: FilterChip) => void | Context value | Callback for removing filters |
filterLabel | string | Context value | Label for the filter button |
noFilterText | string | Context value | Text when no filters are selected |
onClickFilterButton | () => void | Context value | Callback for filter button click |
children | ReactNode | Default layout | Custom content to override default rendering |
className | string | undefined | Additional CSS class name for styling |
sx | SxProps<Theme> | undefined | MUI system props for custom styling |
Note: This component inherits all MUI Stack component props. Default direction="row" and spacing={1}. Has horizontal scrolling enabled with hidden scrollbar.
๐จ Configuration examplesโ
Custom Search Input Stylingโ
<FilterSearchPanel.SearchInput
sx={{
'& .MuiOutlinedInput-root': {
bgcolor: 'background.paper',
borderRadius: 2
}
}}
placeholder="Custom placeholder..."
/>
Custom Filter Button Stylingโ
<FilterSearchPanel.FilterButton
variant="contained"
color="primary"
sx={{ minWidth: 150 }}>
๐ฏ Custom Filters
</FilterSearchPanel.FilterButton>
Custom Action Group Layoutโ
<FilterSearchPanel.ActionGroup
direction="column"
spacing={2}
sx={{ alignItems: 'stretch' }}
/>
๐ง TypeScript Supportโ
Full TypeScript support with comprehensive type definitions:
import FilterSearchPanel from '@nodeblocks/frontend-filter-search-panel-block';
import {StackProps, TextFieldProps, ButtonProps, ChipProps, BoxProps} from '@mui/material';
// Filter chip structure
interface FilterChip {
label: string;
key: string;
groupName?: string;
}
// Default search form data structure
interface SearchFormData {
search: string;
}
// Main component props extend MUI StackProps (rendered as form)
interface FilterSearchPanelProps<T extends SearchFormData>
extends Omit<StackProps<'form'>, 'children' | 'component' | 'onSubmit'> {
filters?: FilterChip[];
defaultSearchValue?: string;
onSearchChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
onClickFilterButton?: () => void;
onClickRemoveFilter?: (filter: FilterChip) => void;
onSearch?: (data: T) => void;
searchPlaceholder?: string;
noFilterText?: string;
filterLabel?: string;
children?: BlocksOverride;
}
function TypedFilterSearchPanel() {
const [selectedFilters, setSelectedFilters] = useState<FilterChip[]>([
{label: 'Active', key: 'status-active', groupName: 'Status'},
{label: 'Premium', key: 'type-premium', groupName: 'Type'},
]);
const handleSearch = (formData: SearchFormData) => {
console.log('Search submitted:', formData);
};
const handleRemoveFilter = (filter: FilterChip) => {
setSelectedFilters(prev => prev.filter(f => f.key !== filter.key));
};
return (
<FilterSearchPanel
filters={selectedFilters}
onSearch={handleSearch}
onClickRemoveFilter={handleRemoveFilter}
onClickFilterButton={() => console.log('Opening filter modal')}
searchPlaceholder="Search for services..."
noFilterText="No filters applied"
filterLabel="Filter Options"
spacing={2}
sx={{maxWidth: 600}}
>
<FilterSearchPanel.SearchInput sx={{bgcolor: 'background.paper'}} />
<FilterSearchPanel.ActionGroup />
</FilterSearchPanel>
);
}
๐ Notesโ
- The component uses MUI's
Stackrendered as aformelement with defaultspacing={1.5}anddirection="column" - Default background is
bgcolor: 'background.default'withp: 2padding - Form submission is handled internally - the
onSearchcallback receives parsed form data - The SearchInput includes a search icon button that submits the form
- The FilterButton uses MUI's
Tuneicon from@mui/icons-material - SelectedFilterList groups filters by their
groupNameproperty and displays group labels - FilterBadge includes a custom close icon positioned before the label
- ActionGroup has horizontal scrolling with hidden scrollbar for overflow handling
- All sub-components support the
sxprop for MUI system styling - Block override pattern allows inserting custom blocks between default blocks
Built with โค๏ธ using React, TypeScript, and MUI.