27 lines
2.0 KiB
TypeScript
27 lines
2.0 KiB
TypeScript
'use client'
|
|
import { ReactNode } from 'react'
|
|
import { ChevronUp, ChevronDown, ChevronsUpDown } from 'lucide-react'
|
|
export interface Column<T> { key: string; title: string; render?: (row: T) => ReactNode; sortable?: boolean; width?: string }
|
|
interface TableProps<T> { columns: Column<T>[]; data: T[]; rowKey: (row: T) => string | number; sortKey?: string; sortOrder?: 'asc' | 'desc'; onSort?: (key: string) => void }
|
|
export default function Table<T extends Record<string, unknown>>({ columns, data, rowKey, sortKey, sortOrder, onSort }: TableProps<T>) {
|
|
return (
|
|
<div className="overflow-x-auto rounded-xl border border-slate-200 dark:border-slate-700">
|
|
<table className="w-full text-sm">
|
|
<thead className="bg-slate-50 dark:bg-slate-800">
|
|
<tr>
|
|
{columns.map(col => (
|
|
<th key={col.key} style={{ width: col.width }} className={`px-4 py-3 text-left font-medium text-slate-600 dark:text-slate-300 ${col.sortable ? 'cursor-pointer select-none hover:bg-slate-100 dark:hover:bg-slate-700' : ''}`} onClick={() => col.sortable && onSort?.(col.key)}>
|
|
<span className="inline-flex items-center">{col.title}{col.sortable && (sortKey !== col.key ? <ChevronsUpDown size={14} className="ml-1 text-slate-400" /> : sortOrder === 'asc' ? <ChevronUp size={14} className="ml-1 text-blue-500" /> : <ChevronDown size={14} className="ml-1 text-blue-500" />)}</span>
|
|
</th>
|
|
))}
|
|
</tr>
|
|
</thead>
|
|
<tbody className="divide-y divide-slate-200 dark:divide-slate-700">
|
|
{data.map(row => <tr key={rowKey(row)} className="hover:bg-slate-50 dark:hover:bg-slate-800/50 transition-colors">{columns.map(col => <td key={col.key} className="px-4 py-3 text-slate-700 dark:text-slate-300">{col.render ? col.render(row) : String(row[col.key] ?? '')}</td>)}</tr>)}
|
|
</tbody>
|
|
</table>
|
|
{data.length === 0 && <div className="py-12 text-center text-slate-500 dark:text-slate-400">暂无数据</div>}
|
|
</div>
|
|
)
|
|
}
|