136 lines
4.1 KiB
TypeScript
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>
|
|
);
|
|
};
|