フィルター検索パネルブロック
FilterSearchPanelコンポーネントは、ReactとTypeScriptで構築された、完全にカスタマイズ可能でアクセシブルなフィルタリング機能付き検索インターフェースです。モダンなデザインパターン、フォームバリデーション、柔軟なカスタマイズオプションを備えた、完全な検索・フィルター体験を提供します。一貫したスタイリングのためにMUIコンポーネント上に構築されています。
🚀 インストール
npm install @nodeblocks/frontend-filter-search-panel-block@0.3.0
📖 使用方法
import {FilterSearchPanel} from '@nodeblocks/frontend-filter-search-panel-block';
- 基本的な使用方法
- 高度な使用方法
function SimpleFilterSearchPanel() { const [filters, setFilters] = useState([ { label: 'アクティブ', key: 'status-active', groupName: 'ステータス' }, { label: 'ウェブ開発', key: 'category-web', groupName: 'カテゴリー' } ]); const handleRemoveFilter = (filter) => { setFilters(filters.filter(f => f.key !== filter.key)); }; const handleSearch = ({search}) => { console.log('検索が実行されました:', search); setFilters(prev => [...prev, {label: search, key: search, groupName: 'カスタム'}]); }; return ( <FilterSearchPanel filters={filters} onClickRemoveFilter={handleRemoveFilter} onClickFilterButton={() => console.log('フィルターボタンがクリックされました')} onSearch={handleSearch} searchPlaceholder="サービスを検索..." noFilterText="フィルターが適用されていません" filterLabel="フィルター設定" > <FilterSearchPanel.SearchInput /> <FilterSearchPanel.ActionGroup /> </FilterSearchPanel> ); }
function AdvancedFilterSearchPanel() { const [filters, setFilters] = useState([ {label: 'フロントエンド', key: 'tech-frontend', groupName: 'スタック'}, {label: 'React', key: 'framework-react', groupName: 'スタック'}, {label: 'シニア', key: 'level-senior', groupName: 'レベル'}, ]); 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: 'クエリ'}]); } }; return ( <FilterSearchPanel filters={filters} onClickRemoveFilter={handleRemoveFilter} onClickFilterButton={() => console.log('フィルターパネルが開かれました')} onSearch={handleSearch} searchPlaceholder="スキル、役割、名前で開発者を検索..." noFilterText="アクティブなフィルターなし" filterLabel="フィルター" 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', }} > 開発者検索 </div> <div style={{ fontSize: '13px', color: '#64748b', }} > チームに最適な人材を見つける </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} オンライン </span> </div> </div> ); const statsBar = ( <div style={{ display: 'flex', gap: '16px', padding: '14px 20px', background: '#f8fafc', borderRadius: '12px', border: '1px solid #e2e8f0', }} > {[ {label: 'アクティブフィルター', value: filters.length, color: '#3b82f6'}, { label: '結果', value: Math.max(50, 234 - filters.length * 45), color: '#8b5cf6', }, {label: '平均レート', value: '$85/時', 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> ); }
🔧 プロパティリファレンス
メインコンポーネントのプロパティ
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
filters | FilterChip[] | [] | 表示する現在選択されているフィルターチップの配列 |
defaultSearchValue | string | undefined | 検索入力フィールドのデフォルト値 |
onClickRemoveFilter | (filter: FilterChip) => void | undefined | フィルターチップが削除されたときにトリガーされるコールバック関数 |
onClickFilterButton | () => void | undefined | フィルターボタンがクリックされたときにトリガーされるコールバック関数 |
onSearchChange | (event: React.ChangeEvent<HTMLInputElement>) => void | undefined | 検索入力値が変更されたときにトリガーされるコールバック関数 |
onSearch | (data: T) => void | undefined | 検索データでフォームが送信されたときにトリガーされるコールバック関数 |
searchPlaceholder | string | undefined | 検索入力フィールドのプレースホルダーテキスト |
noFilterText | string | undefined | フィルターが選択されていないときに表示されるテキスト |
filterLabel | string | undefined | フィルターボタンのラベルテキスト |
spacing | number | string | 1.5 | 子要素間のスペース(MUI Stackのspacing) |
direction | StackDirection | 'column' | スタックレイアウトの方向 |
className | string | undefined | フォームコンテナのスタイリング用の追加CSSクラス名 |
sx | SxProps<Theme> | { p: 2, bgcolor: 'background.default' } | カスタムスタイリング用のMUIシステムプロパティ |
children | BlocksOverride | undefined | デフォルトレンダリングをオーバーライドするカスタムブロックコンポーネント |
注意: このコンポーネントはすべてのMUI Stackコンポーネントプロパティ(component="form"としてレンダリング)を継承します。フォーム送信は内部で処理されます - フォームデータを受け取るにはonSearchコールバックを使用してください。
サブコンポーネント
FilterSearchPanelコンポーネントは複数のサブコンポーネントを提供します。すべてのサブコンポーネントは、メインコンポーネントのコンテキストからデフォルト値を受け取り、プロパティを通じてこれらの値をオーバーライドできます。
FilterSearchPanel.SearchInput
MUI TextFieldを使用して検索入力フィールドをレンダリングします。
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
searchPlaceholder | string | '新規サービス開発' | 検索入力に表示されるプレースホルダーテキスト |
onSearchChange | (event: React.ChangeEvent<HTMLInputElement>) => void | コンテキスト値 | 入力値が変更されたときにトリガーされるコールバック関数 |
defaultSearchValue | string | コンテキスト値 | 検索入力のデフォルト値 |
name | string | 'search' | 入力のフォームフィールド名 |
type | string | 'text' | HTML入力タイプ |
children | ReactNode | undefined | カスタムコンテンツ - TextField全体を置き換えます |
className | string | undefined | スタイリング用の追加CSSクラス名 |
sx | SxProps<Theme> | undefined | カスタムスタイリング用のMUIシステムプロパティ |
注意: このコンポーネントはすべてのMUI TextFieldコンポーネントプロパティを継承します。デフォルトはsize="small"です。フォームを送信する検索アイコンボタンが含まれています。
FilterSearchPanel.FilterButton
MUI Buttonを使用してフィルター設定ボタンをレンダリングします。
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
filterLabel | string | '絞込み設定' | フィルターボタンに表示されるラベルテキスト |
onClickFilterButton | () => void | コンテキスト値 | ボタンがクリックされたときにトリガーされるコールバック関数 |
children | ReactNode | デフォルトアイコン+ラベル | デフォルトレンダリングをオーバーライドするカスタムコンテンツ |
className | string | undefined | スタイリング用の追加CSSクラス名 |
sx | SxProps<Theme> | undefined | カスタムスタイリング用のMUIシステムプロパティ |
variant | ButtonVariant | 'outlined' | MUI Buttonバリアント |
size | ButtonSize | 'small' | MUI Buttonサイズ |
注意: このコンポーネントはすべてのMUI Buttonコンポーネントプロパティを継承します。デフォルトコンテンツには@mui/icons-materialからのTuneアイコンが含まれています。
FilterSearchPanel.SelectedFilterList
MUI Boxを使用して選択されたフィルターのリストまたは空の状態をレンダリングします。
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
filters | FilterChip[] | コンテキスト値 | 表示するフィルターチップの配列 |
onClickRemoveFilter | (filter: FilterChip) => void | コンテキスト値 | フィルターが削除されたときにトリガーされるコールバック関数 |
noFilterText | string | '条件未設定' | フィルターが選択されていないときに表示されるテキスト |
children | ReactNode | デフォルトフィルターリスト | デフォルトレンダリングをオーバーライドするカスタムコンテンツ |
className | string | undefined | スタイリング用の追加CSSクラス名 |
sx | SxProps<Theme> | undefined | カスタムスタイリング用のMUIシステムプロパティ |
注意: このコンポーネントはすべてのMUI Boxコンポーネントプロパティを継承します。groupNameプロパティでフィルターをグループ化し、グループラベルを表示します。
FilterSearchPanel.FilterBadge
MUI Chipを使用して個別のフィルターチップをレンダリングします。
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
filter | FilterChip | 必須 | ラベル、キー、オプションのgroupNameを含むフィルターチップオブジェクト |
onClickRemoveFilter | (filter: FilterChip) => void | コンテキスト値 | フィルターが削除されたときにトリガーされるコールバック関数 |
children | ReactNode | filter.label | ラベルテキストをオーバーライドするカスタムコンテンツ |
className | string | undefined | スタイリング用の追加CSSクラス名 |
sx | SxProps<Theme> | undefined | カスタムスタイリング用のMUIシステムプロパティ |
注意: このコンポーネントはすべてのMUI Chipコンポーネントプロパティを継承します。削除用のカスタム閉じるアイコンが含まれています。
FilterSearchPanel.ActionGroup
MUI Stackを使用してFilterButtonとSelectedFilterListを水平レイアウトで組み合わせます。
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
filters | FilterChip[] | コンテキスト値 | フィルターチップの配列(SelectedFilterListに渡されます) |
onClickRemoveFilter | (filter: FilterChip) => void | コンテキスト値 | フィルター削除のコールバック |
filterLabel | string | コンテキスト値 | フィルターボタンのラベル |
noFilterText | string | コンテキスト値 | フィルターがないときのテキスト |
onClickFilterButton | () => void | コンテキスト値 | フィルターボタンクリックのコールバック |
children | ReactNode | デフォルトレイアウト | デフォルトレンダリングをオーバーライドするカスタムコンテンツ |
className | string | undefined | スタイリング用の追加CSSクラス名 |
sx | SxProps<Theme> | undefined | カスタムスタイリング用のMUIシステムプロパティ |
注意: このコンポーネントはすべてのMUI Stackコンポーネントプロパティを継承します。デフォルトはdirection="row"とspacing={1}です。オーバーフロー処理のために非表示スクロールバー付きの水平スクロールが有効になっています。
🎨 設定例
カスタム検索入力スタイリング
<FilterSearchPanel.SearchInput
sx={{
'& .MuiOutlinedInput-root': {
bgcolor: 'background.paper',
borderRadius: 2
}
}}
placeholder="カスタムプレースホルダー..."
/>
カスタムフィルターボタンスタイリング
<FilterSearchPanel.FilterButton
variant="contained"
color="primary"
sx={{ minWidth: 150 }}>
🎯 カスタムフィルター
</FilterSearchPanel.FilterButton>
カスタムアクショングループレイアウト
<FilterSearchPanel.ActionGroup
direction="column"
spacing={2}
sx={{ alignItems: 'stretch' }}
/>
🔧 TypeScript サポート
包括的な型定義による完全なTypeScriptサポート:
import FilterSearchPanel from '@nodeblocks/frontend-filter-search-panel-block';
import {StackProps, TextFieldProps, ButtonProps, ChipProps, BoxProps} from '@mui/material';
// フィルターチップの構造
interface FilterChip {
label: string;
key: string;
groupName?: string;
}
// デフォルトの検索フォームデータ構造
interface SearchFormData {
search: string;
}
// メインコンポーネントプロパティはMUI StackProps(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: 'アクティブ', key: 'status-active', groupName: 'ステータス'},
{label: 'プレミアム', key: 'type-premium', groupName: 'タイプ'},
]);
const handleSearch = (formData: SearchFormData) => {
console.log('検索が実行されました:', 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('フィルターモーダルを開いています')}
searchPlaceholder="サービスを検索..."
noFilterText="フィルターが適用されていません"
filterLabel="フィルターオプション"
spacing={2}
sx={{maxWidth: 600}}
>
<FilterSearchPanel.SearchInput sx={{bgcolor: 'background.paper'}} />
<FilterSearchPanel.ActionGroup />
</FilterSearchPanel>
);
}
📝 注意事項
- コンポーネントはデフォルトで
spacing={1.5}とdirection="column"のMUIStackをform要素としてレンダリングします - デフォルト背景は
bgcolor: 'background.default'とp: 2のパディングです - フォーム送信は内部で処理されます -
onSearchコールバックはパースされたフォームデータを受け取ります - SearchInputにはフォームを送信する検索アイコンボタンが含まれています
- FilterButtonは
@mui/icons-materialからのMUITuneアイコンを使用します - SelectedFilterListは
groupNameプロパティでフィルターをグループ化し、グループラベルを表示します - FilterBadgeにはラベルの前に配置されたカスタム閉じるアイコンが含まれています
- ActionGroupにはオーバーフロー処理のための非表示スクロールバー付き水平スクロールがあります
- すべてのサブコンポーネントはMUIシステムスタイリング用の
sxプロパティをサポートします - ブロックオーバーライドパターンにより、デフォルトブロック間にカスタムブロックを挿入できます
React、TypeScript、MUIを使用して❤️で構築されました。