Components
Registry
Components
Time
Event
Endpoint
Status
Latency
Attempt
Live tailingCtrl+Space
"use client"
import { useEffect, useState } from "react"Built on the Virtual Data Table, so the
table scrolls internally and rows render through the virtualizer. By default it
is inverted like the portal: newest deliveries at the visual bottom, infinite
scrollback above, and the live-tail strip underneath when onToggleStream is
wired.
Installation#
pnpm dlx shadcn@latest add @repost/delivery-history-tableInstalls this item, every component it depends on, and the npm packages listed below.
Usage#
This component is presentational: it renders what you pass it and calls back on
interaction. In a live dashboard the data comes from useLogsFeed() or useLogs()
(@repost/portal-react), but any data of
the same shape works.
"use client"
import { useEffect, useState } from "react"
import { DeliveryHistoryTable, deliveryHistoryRowId } from "@/components/delivery-history-table"
import { fixtureLogRows } from "@/fixtures/webhook-fixtures"
import type { WebhookLogRow } from "@/lib/webhook-types"
const EVENT_TYPES = ["user.created", "user.updated", "invoice.paid", "subscription.renewed"]
/** Fabricate a live row on top of the newest one. */
const nextLiveRow = (index: number): WebhookLogRow => ({
deliveryId: `del_live_${index}`,
messageId: `msg_live_${index}`,
endpointId: "ep_primary",
type: EVENT_TYPES[index % EVENT_TYPES.length]!,
generation: 0,
attempt: 1,
url: "https://api.acme.dev/webhooks/repost",
responseStatus: index % 9 === 4 ? 500 : 200,
latencyMs: 40 + ((index * 53) % 300),
errorClass: null,
timestamp: new Date().toISOString(),
})
export function DeliveryHistoryTableDemo() {
const [selected, setSelected] = useState<WebhookLogRow | null>(null)
const [live, setLive] = useState(true)
const [rows, setRows] = useState<WebhookLogRow[]>(() => fixtureLogRows(60))
// The table is presentational — this stands in for `useLogsFeed`.
useEffect(() => {
if (!live) return
let index = 0
const timer = setInterval(() => {
index += 1
setRows((current) => [nextLiveRow(index), ...current])
}, 3000)
return () => clearInterval(timer)
}, [live])
return (
<DeliveryHistoryTable
className="h-[420px] w-full"
rows={rows}
onRowClick={(row) => setSelected((current) => (current && deliveryHistoryRowId(current) === deliveryHistoryRowId(row) ? null : row))}
selectedRowId={selected ? deliveryHistoryRowId(selected) : null}
streaming={live}
streamStatus={live ? "live" : "off"}
onToggleStream={() => setLive((value) => !value)}
/>
)
}
Props#
| Prop | Type | Description |
|---|---|---|
rows | WebhookLogRow[] | Attempt rows, newest first. |
className | string | Size the frame here: the table scrolls internally (e.g. h-[420px], or min-h-0 flex-1). |
isLoading | boolean | Renders skeleton rows. |
onRowClick | (row) => void | Open your detail view. |
selectedRowId | string | null | Highlights the active row. |
inverted | boolean | Log-style: newest at the visual bottom. Default true, like the portal. |
onLoadOlder / isLoadingOlder | — | Infinite scrollback; wire to loadOlder from useLogsFeed. |
streaming / streamStatus / onToggleStream | — | The live tail: auto-stick, the "Streaming · N new" pill, and the tail strip. |
streamDroppedCount / onClearStreamDropped | — | Surface and clear rows the stream dropped. |
hiddenColumns | string[] | Hide columns by id: timestamp, type, url, status, latency, attempt. |
emptyMessage | ReactNode | Shown when there are no rows. |