Skip to main content

Filter Search Panel Block

FilterSearchPanel is a search-and-filter toolbar built on MUI with a submit search field, filter settings button, and removable filter chips.

Installationโ€‹

npm install @nodeblocks/frontend-filter-search-panel-block

What You Needโ€‹

ItemWhy it matters
filtersActive filter chips shown in the panel ({ label, key, groupName? })
defaultSearchValueInitial value for the search field
onSearchCalled on form submit with parsed form data (default field: search)
onSearchChangeReceives search input change events when you want controlled search
onClickRemoveFilterRemoves a chip when the user deletes it
onClickFilterButtonOpens your filter UI (modal, drawer, route, etc.)
searchPlaceholderPlaceholder text used by SearchInput
noFilterTextText shown when no filters are selected
filterLabelLabel shown on the filter settings button
Parent-owned filter state

FilterSearchPanel does not own the filters array. Keep chips in your app state and update them in onClickRemoveFilter (and when applying filters elsewhere).

Code Examplesโ€‹

Live Editor
function Example() {
  const defaultFilters = [
    {label: 'Active', key: 'status-active'},
    {label: 'Inactive', key: 'status-inactive'},
  ];

  const filterCatalog = [
    {label: 'Active', key: 'status-active'},
    {label: 'Inactive', key: 'status-inactive'},
    {label: 'Remote', key: 'work-remote', groupName: 'Work type'},
    {label: 'Full-time', key: 'employment-full-time', groupName: 'Employment'},
    {label: 'Contract', key: 'employment-contract', groupName: 'Employment'},
  ];

  const [filters, setFilters] = React.useState(defaultFilters);
  const handleRemoveFilter = filter => {
    setFilters(current => current.filter(f => f.key !== filter.key));
  };

  const handleFilterButton = () => {
    const nextFilter = filterCatalog.find(option => !filters.some(f => f.key === option.key));

    if (nextFilter) {
      setFilters(current => [...current, nextFilter]);
    }
  };

  const handleSearch = ({search}) => {
    if (!search?.trim()) {
      return;
    }

    setFilters(current => [...current, {label: search, key: `search-${Date.now()}`, groupName: 'Search'}]);
  };

  return (
    <FilterSearchPanel
      filters={filters}
      searchPlaceholder="Search services..."
      noFilterText="No filters applied"
      filterLabel="Filter Settings"
      onClickFilterButton={handleFilterButton}
      onClickRemoveFilter={handleRemoveFilter}
      onSearch={handleSearch}
    />
  );
}
Result
Loading...

Important Propsโ€‹

Core Propsโ€‹

PropTypeRequiredDefaultDescription
filtersFilterChip[]No[]Selected filter chips shown in the panel
defaultSearchValuestringNoundefinedInitial value used by SearchInput
onSearch(data: T) => voidNoundefinedCalled on form submit; T defaults to { search: string }
onSearchChange(event: React.ChangeEvent<HTMLInputElement>) => voidNoundefinedSearch field change handler, typically used for controlled search input
onClickRemoveFilter(filter: FilterChip) => voidNoundefinedCalled when a chip delete icon is clicked
onClickFilterButton() => voidNoundefinedCalled when the filter settings button is clicked

Content Propsโ€‹

PropTypeRequiredDefaultDescription
searchPlaceholderstringNoใƒ•ใƒชใƒผใƒฏใƒผใƒ‰ใงๆคœ็ดขPlaceholder shown in the search input
noFilterTextstringNoๆกไปถๆœช่จญๅฎšText shown when no filters are selected
filterLabelstringNo็ตž่พผใฟ่จญๅฎšLabel shown on the filter settings button

Layout and Composition Propsโ€‹

PropTypeRequiredDefaultDescription
childrenBlocksOverride | ReactNodeNoundefinedOverride default blocks or render compound children
classNamestringNoundefinedClass name on the root form
sxSxPropsNoundefinedMUI system styles for the root form
spacingStackProps['spacing']No{ xs: 1.5, sm: 2.5 }Space between root blocks
directionStackProps['direction']No'column'Flex direction of the root stack

FilterSearchPanel inherits StackProps (except children, component, and onSubmit) and renders as a form. Submit is handled internally and calls onSearch when provided.

Sub-component propsโ€‹

Sub-components share context with root so by default they use prop values from context, but they can be overriden locally:

Sub-componentMain propsInherits
FilterSearchPanel.SearchInputsearchPlaceholder, defaultSearchValue, onSearchChange, name, typeTextField
FilterSearchPanel.FilterButtonfilterLabel, onClickFilterButton, childrenButton
FilterSearchPanel.SelectedFilterListfilters, noFilterText, onClickRemoveFilter, childrenBox
FilterSearchPanel.FilterBadgefilter (required), onClickRemoveFilter, childrenChip
FilterSearchPanel.ActionGroupchildren (default: filter button + chip list)Stack

Default UI Blocksโ€‹

BlockBuilt onNotes
FilterSearchPanel (root)Stack rendered as formHandles submit internally and provides search/filter context to child blocks
FilterSearchPanel.SearchInputTextField + submit IconButtonname="search", size="small", search icon submits the form
FilterSearchPanel.ActionGroupStackHorizontal row: filter button + selected chips (scrollable)

Default render order: searchInput โ†’ actionGroup.

Extra field primitivesโ€‹

PrimitiveBuilt onNotes
FilterSearchPanel.FilterButtonButton + Tune iconOpens your filter UI via onClickFilterButton; default label is ็ตž่พผใฟ่จญๅฎš
FilterSearchPanel.SelectedFilterListBox + TypographyShows noFilterText (ๆกไปถๆœช่จญๅฎš) when empty, otherwise groups chips by groupName
FilterSearchPanel.FilterBadgeChipRemovable chip; delete calls onClickRemoveFilter(filter)

TypeScriptโ€‹

import * as React from 'react';
import {FilterSearchPanel} from '@nodeblocks/frontend-filter-search-panel-block';

type FilterChip = {
label: string;
key: string;
groupName?: string;
};

type SearchFormData = {
search: string;
};

const filterCatalog: FilterChip[] = [
{label: 'Remote', key: 'work-remote', groupName: 'Work type'},
{label: 'Full-time', key: 'employment-full-time', groupName: 'Employment'},
];

export function ServiceSearchToolbar() {
const [filters, setFilters] = React.useState<FilterChip[]>([
{label: 'Active', key: 'status-active', groupName: 'Status'},
]);

const handleRemoveFilter = (filter: FilterChip) => {
setFilters(current => current.filter(f => f.key !== filter.key));
};

const handleFilterButton = () => {
const nextFilter = filterCatalog.find(option => !filters.some(f => f.key === option.key));

if (nextFilter) {
setFilters(current => [...current, nextFilter]);
}
};

const handleSearch = (data: SearchFormData) => {
const search = data.search.trim();
if (!search) {
return;
}

setFilters(current => [...current, {label: search, key: `search-${Date.now()}`, groupName: 'Search'}]);
};

return (
<FilterSearchPanel<SearchFormData>
filters={filters}
searchPlaceholder="Search services..."
noFilterText="No filters applied"
filterLabel="Filter Settings"
onClickFilterButton={handleFilterButton}
onClickRemoveFilter={handleRemoveFilter}
onSearch={handleSearch}
/>
);
}