BillTracker/client/components/SearchFilterPanel.tsx

98 lines
2.9 KiB
TypeScript

import type { ReactNode } from 'react';
import { ChevronDown, ChevronUp, Search, X } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils';
interface SearchFilterPanelProps {
title?: string;
collapsed?: boolean;
onCollapsedChange?: (collapsed: boolean) => void;
hasFilters?: boolean;
resultLabel?: string;
sortLabel?: string;
onClear?: () => void;
children?: ReactNode;
className?: string;
variant?: 'default' | 'embedded';
headerActions?: ReactNode;
}
export default function SearchFilterPanel({
title = 'Search & filters',
collapsed,
onCollapsedChange,
hasFilters,
resultLabel,
sortLabel,
onClear,
children,
className,
variant = 'default',
headerActions,
}: SearchFilterPanelProps) {
const embedded = variant === 'embedded';
const ToggleIcon = collapsed ? ChevronUp : ChevronDown;
return (
<section
className={cn(
embedded
? 'rounded-lg'
: 'overflow-hidden rounded-xl border border-border/70 bg-card/80 shadow-sm shadow-black/5 backdrop-blur-sm',
className,
)}
>
<div
className={cn(
'flex flex-wrap items-center gap-3',
embedded ? 'py-1' : 'bg-muted/25 px-3 py-2.5 sm:px-4',
)}
>
<button
type="button"
onClick={() => onCollapsedChange?.(!collapsed)}
aria-expanded={!collapsed}
className="group inline-flex min-w-0 flex-1 items-center gap-2.5 rounded-lg text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
>
<span className="flex h-7 w-7 shrink-0 items-center justify-center rounded-lg border border-primary/15 bg-primary/10 text-primary">
<Search className="h-3.5 w-3.5" />
</span>
<span className="min-w-0">
<span className="block text-sm font-semibold text-foreground">{title}</span>
<span className="block truncate text-xs text-muted-foreground">
{[resultLabel, hasFilters ? 'filters active' : 'no filters', sortLabel]
.filter(Boolean)
.join(' · ')}
</span>
</span>
<ToggleIcon className="ml-auto h-4 w-4 shrink-0 text-muted-foreground transition-colors group-hover:text-foreground" />
</button>
{hasFilters && onClear && (
<Button
type="button"
variant="outline"
onClick={onClear}
className="h-8 gap-2 px-2.5 text-xs"
>
<X className="h-3.5 w-3.5" />
Clear
</Button>
)}
{headerActions}
</div>
{!collapsed && (
<div
className={cn(
'space-y-3',
embedded ? 'pt-3' : 'border-t border-border/60 bg-card/45 px-3 py-3 sm:px-4',
)}
>
{children}
</div>
)}
</section>
);
}