99 lines
3.4 KiB
TypeScript

export function Spinner() {
return (
<div className="flex items-center justify-center p-12">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500"></div>
</div>
)
}
export function EmptyState({ message = 'No data found' }: { message?: string }) {
return (
<div className="flex items-center justify-center p-12 text-gray-500">
<div className="text-center">
<svg className="mx-auto h-12 w-12 text-gray-600 mb-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4"
/>
</svg>
<p>{message}</p>
</div>
</div>
)
}
export function HashBadge({ hash, short = true }: { hash: string; short?: boolean }) {
const display = short && hash.length > 8 ? hash.slice(0, 8) : hash
return (
<span className="inline-block px-2 py-1 bg-gray-800/50 rounded font-mono text-xs text-gray-400">{display}</span>
)
}
export function Pagination({
total,
limit,
offset,
onPageChange,
onLimitChange,
}: {
total: number
limit: number
offset: number
onPageChange: (newOffset: number) => void
onLimitChange: (newLimit: number) => void
}) {
const currentPage = Math.floor(offset / limit) + 1
const totalPages = Math.ceil(total / limit)
const startItem = total === 0 ? 0 : offset + 1
const endItem = Math.min(offset + limit, total)
const canPrev = offset > 0
const canNext = offset + limit < total
return (
<div className="flex items-center justify-between border-t border-gray-800 bg-gray-900/50 px-4 py-3 mt-4">
<div className="flex items-center gap-4">
<span className="text-sm text-gray-400">
{startItem}-{endItem} of {total}
</span>
<div className="flex items-center gap-2">
<label className="text-sm text-gray-500">Per page:</label>
<select
value={limit}
onChange={(e) => {
onLimitChange(parseInt(e.target.value, 10))
onPageChange(0) // reset to first page
}}
className="bg-gray-800 text-gray-300 border border-gray-700 rounded px-2 py-1 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value="25">25</option>
<option value="50">50</option>
<option value="100">100</option>
</select>
</div>
</div>
<div className="flex items-center gap-2">
<button
onClick={() => onPageChange(offset - limit)}
disabled={!canPrev}
className="px-3 py-1 bg-gray-800 text-gray-300 rounded border border-gray-700 disabled:opacity-50 disabled:cursor-not-allowed hover:bg-gray-700 text-sm"
>
Previous
</button>
<span className="text-sm text-gray-400">
Page {currentPage} / {totalPages || 1}
</span>
<button
onClick={() => onPageChange(offset + limit)}
disabled={!canNext}
className="px-3 py-1 bg-gray-800 text-gray-300 rounded border border-gray-700 disabled:opacity-50 disabled:cursor-not-allowed hover:bg-gray-700 text-sm"
>
Next
</button>
</div>
</div>
)
}