banatie-service/apps/landing/src/components/layout/lab/LabSidebar.tsx

136 lines
4.1 KiB
TypeScript

'use client';
/**
* Lab Sidebar - Filter Panel
*
* Narrow left sidebar for filtering lab content.
* Matches DocsSidebar visual style with collapsible filter sections.
*
* Features:
* - Multiple filter groups (Status, Date Range, Source Type)
* - Collapsible sections
* - Checkbox/radio button controls
* - Clean, minimal design
*/
import { useState } from 'react';
import { FilterPlaceholder } from '@/components/lab/FilterPlaceholder';
type FilterGroupProps = {
id: string;
label: string;
icon: string;
options: Array<{ id: string; label: string; count?: number }>;
multiSelect?: boolean;
};
const filterGroups: FilterGroupProps[] = [
{
id: 'status',
label: 'Status',
icon: '📊',
options: [
{ id: 'all', label: 'All', count: 127 },
{ id: 'completed', label: 'Completed', count: 98 },
{ id: 'pending', label: 'Pending', count: 23 },
{ id: 'failed', label: 'Failed', count: 6 },
],
multiSelect: false,
},
{
id: 'date',
label: 'Date Range',
icon: '📅',
options: [
{ id: 'today', label: 'Today', count: 12 },
{ id: 'week', label: 'Last 7 days', count: 45 },
{ id: 'month', label: 'Last 30 days', count: 89 },
{ id: 'all-time', label: 'All time', count: 127 },
],
multiSelect: false,
},
{
id: 'source',
label: 'Source Type',
icon: '🎨',
options: [
{ id: 'text', label: 'Text-to-Image', count: 78 },
{ id: 'upload', label: 'Uploads', count: 34 },
{ id: 'reference', label: 'Reference Images', count: 15 },
],
multiSelect: true,
},
];
export const LabSidebar = () => {
const [expandedSections, setExpandedSections] = useState<string[]>(['status', 'date', 'source']);
const toggleSection = (id: string) => {
setExpandedSections((prev) =>
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
);
};
const isExpanded = (id: string) => expandedSections.includes(id);
return (
<aside className="h-full bg-slate-950 border-r border-white/10" aria-label="Lab filters">
{/* Header */}
<div className="p-6 border-b border-slate-800">
<h2 className="text-lg font-semibold text-white">Filters</h2>
<p className="text-xs text-gray-500 mt-1">Refine your results</p>
</div>
{/* Filter Groups */}
<div className="p-4 space-y-4">
{filterGroups.map((group) => {
const expanded = isExpanded(group.id);
return (
<div key={group.id} className="border-b border-slate-800 pb-4 last:border-b-0">
{/* Section Header */}
<button
onClick={() => toggleSection(group.id)}
className="w-full flex items-center justify-between px-2 py-2 rounded-lg text-sm transition-colors text-gray-400 hover:text-white hover:bg-white/5"
aria-expanded={expanded}
>
<span className="flex items-center gap-2">
<span className="text-base">{group.icon}</span>
<span className="font-medium">{group.label}</span>
</span>
<svg
className={`w-4 h-4 transition-transform ${expanded ? 'rotate-90' : ''}`}
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
</svg>
</button>
{/* Filter Options */}
{expanded && (
<FilterPlaceholder
options={group.options}
multiSelect={group.multiSelect}
groupId={group.id}
/>
)}
</div>
);
})}
</div>
{/* Bottom Actions */}
<div className="mt-auto p-4 border-t border-slate-800">
<button
className="w-full px-3 py-2 text-sm text-gray-500 hover:text-gray-300 transition-colors rounded-lg hover:bg-white/5"
aria-label="Reset all filters"
>
Reset Filters
</button>
</div>
</aside>
);
};