- Backend REST/gateway/track routes, analytics, Blockscout proxy paths. - Frontend wallet and liquidity surfaces; MetaMask token list alignment. - Deployment docs, verification scripts, address inventory updates. Check: go build ./... under backend/ (pass). Made-with: Cursor
102 lines
3.2 KiB
TypeScript
102 lines
3.2 KiB
TypeScript
import { ReactNode } from 'react'
|
|
import clsx from 'clsx'
|
|
|
|
interface Column<T> {
|
|
header: string
|
|
accessor: (row: T) => ReactNode
|
|
className?: string
|
|
}
|
|
|
|
interface TableProps<T> {
|
|
columns: Column<T>[]
|
|
data: T[]
|
|
className?: string
|
|
emptyMessage?: string
|
|
/** Stable key for each row (e.g. row => row.id or row => row.hash). Falls back to index if not provided. */
|
|
keyExtractor?: (row: T) => string | number
|
|
}
|
|
|
|
export function Table<T>({
|
|
columns,
|
|
data,
|
|
className,
|
|
emptyMessage = 'No data available right now.',
|
|
keyExtractor,
|
|
}: TableProps<T>) {
|
|
if (data.length === 0) {
|
|
return (
|
|
<div
|
|
className={clsx(
|
|
'rounded-xl border border-dashed border-gray-300 bg-white px-4 py-6 text-sm text-gray-600 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-400',
|
|
className,
|
|
)}
|
|
>
|
|
{emptyMessage}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div className={clsx('space-y-3', className)}>
|
|
<div className="grid gap-3 md:hidden">
|
|
{data.map((row, rowIndex) => (
|
|
<div
|
|
key={keyExtractor ? keyExtractor(row) : rowIndex}
|
|
className="rounded-xl border border-gray-200 bg-white p-4 shadow-sm dark:border-gray-700 dark:bg-gray-900"
|
|
>
|
|
<dl className="space-y-3">
|
|
{columns.map((column, colIndex) => (
|
|
<div key={colIndex} className="space-y-1">
|
|
<dt className="text-[11px] font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">
|
|
{column.header}
|
|
</dt>
|
|
<dd className={clsx('min-w-0 text-sm text-gray-900 dark:text-gray-100', column.className)}>
|
|
{column.accessor(row)}
|
|
</dd>
|
|
</div>
|
|
))}
|
|
</dl>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
<div className="hidden overflow-x-auto md:block">
|
|
<table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
|
<thead className="bg-gray-50 dark:bg-gray-800">
|
|
<tr>
|
|
{columns.map((column, index) => (
|
|
<th
|
|
key={index}
|
|
className={clsx(
|
|
'px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-gray-400 lg:px-6',
|
|
column.className
|
|
)}
|
|
>
|
|
{column.header}
|
|
</th>
|
|
))}
|
|
</tr>
|
|
</thead>
|
|
<tbody className="divide-y divide-gray-200 bg-white dark:divide-gray-700 dark:bg-gray-900">
|
|
{data.map((row, rowIndex) => (
|
|
<tr key={keyExtractor ? keyExtractor(row) : rowIndex} className="hover:bg-gray-50 dark:hover:bg-gray-800">
|
|
{columns.map((column, colIndex) => (
|
|
<td
|
|
key={colIndex}
|
|
className={clsx(
|
|
'px-4 py-4 align-top text-sm text-gray-900 dark:text-gray-100 lg:px-6',
|
|
column.className
|
|
)}
|
|
>
|
|
{column.accessor(row)}
|
|
</td>
|
|
))}
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|