125 lines
3.8 KiB
TypeScript
125 lines
3.8 KiB
TypeScript
'use client';
|
|
|
|
import { useRef } from 'react';
|
|
import { HugeiconsIcon } from '@hugeicons/react';
|
|
import {
|
|
Search01Icon,
|
|
Cancel01Icon,
|
|
ColumnsThreeCogIcon,
|
|
Download01Icon,
|
|
} from '@hugeicons/core-free-icons';
|
|
import { Input } from '@/components/ui/input';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuCheckboxItem,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuTrigger,
|
|
} from '@/components/ui/dropdown-menu';
|
|
import { type ColumnDef, type ExportFormat } from './types';
|
|
|
|
interface TableToolbarProps<T> {
|
|
columns: ColumnDef<T>[];
|
|
hiddenColumns: Set<string>;
|
|
globalFilter: string;
|
|
showExport?: boolean;
|
|
exportFormats?: ExportFormat[];
|
|
selectedCount?: number;
|
|
onGlobalFilterChange: (value: string) => void;
|
|
onColumnVisibilityChange: (key: string) => void;
|
|
onExport?: (format: ExportFormat) => void;
|
|
onClearSelection?: () => void;
|
|
}
|
|
|
|
export function TableToolbar<T>({
|
|
columns,
|
|
hiddenColumns,
|
|
globalFilter,
|
|
showExport,
|
|
exportFormats = ['csv', 'excel', 'pdf'],
|
|
selectedCount = 0,
|
|
onGlobalFilterChange,
|
|
onColumnVisibilityChange,
|
|
onExport,
|
|
onClearSelection,
|
|
}: TableToolbarProps<T>) {
|
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
|
|
return (
|
|
<div className="flex items-center gap-2 p-3 border-b">
|
|
{/* Global search */}
|
|
<div className="relative flex-1 max-w-xs">
|
|
<HugeiconsIcon
|
|
icon={Search01Icon}
|
|
className="absolute left-2.5 top-1/2 -translate-y-1/2 size-4 text-muted-foreground pointer-events-none"
|
|
/>
|
|
<Input
|
|
ref={inputRef}
|
|
value={globalFilter}
|
|
onChange={e => onGlobalFilterChange(e.target.value)}
|
|
placeholder="Search…"
|
|
className="pl-8 pr-8 h-8 text-sm"
|
|
/>
|
|
{globalFilter && (
|
|
<button
|
|
onClick={() => onGlobalFilterChange('')}
|
|
className="absolute right-2.5 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
|
|
>
|
|
<HugeiconsIcon icon={Cancel01Icon} className="size-3.5" />
|
|
</button>
|
|
)}
|
|
</div>
|
|
|
|
{/* Selection badge */}
|
|
{selectedCount > 0 && (
|
|
<div className="flex items-center gap-1.5">
|
|
<Badge variant="secondary" className="text-xs">
|
|
{selectedCount} selected
|
|
</Badge>
|
|
<Button variant="ghost" size="xs" onClick={onClearSelection} className="text-xs h-6">
|
|
Clear
|
|
</Button>
|
|
</div>
|
|
)}
|
|
|
|
{/* Column visibility */}
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger render={<Button variant="outline" size="sm" className="gap-1.5" />}>
|
|
<HugeiconsIcon icon={ColumnsThreeCogIcon} className="size-4" />
|
|
Columns
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent align="end" className="w-48">
|
|
{columns.map(col => (
|
|
<DropdownMenuCheckboxItem
|
|
key={col.key}
|
|
checked={!hiddenColumns.has(col.key)}
|
|
onCheckedChange={() => onColumnVisibilityChange(col.key)}
|
|
>
|
|
{col.title}
|
|
</DropdownMenuCheckboxItem>
|
|
))}
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
|
|
{/* Export */}
|
|
{showExport && onExport && (
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger render={<Button variant="outline" size="sm" className="gap-1.5" />}>
|
|
<HugeiconsIcon icon={Download01Icon} className="size-4" />
|
|
Export
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent align="end">
|
|
{exportFormats.map(fmt => (
|
|
<DropdownMenuItem key={fmt} onClick={() => onExport(fmt)}>
|
|
{fmt.toUpperCase()}
|
|
</DropdownMenuItem>
|
|
))}
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|