Log List
Rows of requests, deliveries, or messages, newest first, each one line: a
mono id you can copy, a status, the method, what happened, and how long ago.
Reach for it where a DataTable would be columns of the same few fields: a
log reads left to right as one line, and the page filters it from a
FilterBar pinned above. The surface is BasicPage.Card’s, so it sits in a
Stack as any card does.
+1 332 259 3374 received a message from +1 332 222 1111.
Support agent was created.
No message with that id belongs to this project.
The carrier did not answer within 10 seconds.
+1 332 259 3374 was released.
Open: pho_req_01m04avv2ffzxv4xgt7ym32r6n. In an app the row is a link (href or render), and the open one keeps the wash beside its page.
Import
import { LogList } from "@photon-ai/pho-ui/components/log-list";
Anatomy
Root is the card. Row is one line: id (mono, middle-truncated, copied
on click, the whole id a tooltip away), a status slot, a method, the
label (the route or the event type; code sets it in mono), a
description line with real content, and the trailing duration and
time. A row is a link with href (through the host’s LinkProvider), a
button with onClick, or whatever render says; current marks the row
whose page is open beside the list.
<LogList.Root>
<LogList.Row
id="pho_req_01m04axjqrfzxv4xjet40a84gb"
status={<LogList.Status value={200} />}
method="POST"
code
label="/v1/messages"
description="+1 332 259 3374 received a message from +1 332 222 1111."
duration="59 ms"
time="2 min ago"
href="/requests/pho_req_01m04axjqrfzxv4xjet40a84gb"
/>
</LogList.Root>
Status and method
LogList.Status is a low-contrast Badge colored by logStatusColor: an
HTTP code (2xx success, 3xx gray, 4xx warning, 5xx error) or a word
(delivered, failed, pending). label overrides the derived word.
LogList.Method is the quiet mono chip for POST and GET; the row renders
one from its method prop.
<LogList.Status value={200} /> // 200, success
<LogList.Status value="failed" /> // Failed, error
<LogList.Status value="timed_out" /> // Timed out, error
Deliveries
The same row for an event’s deliveries: the event id, a status word, the
event type as the label, and the destination’s answer as the description.
Anything else that belongs at the end (the attempt count) goes in
children, before the time.
Recent messages
A number’s recent messages are the same row with nothing new: the message
text is the label, Received from +1 332 259 3374. or
Sent to +1 332 259 3374. is the description, and the time trails. A
muted badge in the status slot says the direction at a glance. No id
column: leave id off and the row starts at the badge.
Sent to +1 332 259 3374.
<LogList.Row
status={
<Badge size="sm" contrast="low" color="muted">
Received
</Badge>
}
label={message.text}
description={`Received from ${message.from}.`}
time="2 min ago"
href={`/messages/${message.id}`}
/>
Loading, empty, and more
LogList.Loading lays bones in the rows’ place; mark the Root busy while
it shows. LogList.Empty is StatusView at card size, with the page’s own
words. LogList.LoadMore is the sentinel at the end: it asks for the next
page as it scrolls into view (rootMargin ahead, 200px by default), shows
a spinner while loading, offers a button for keyboards, and renders
nothing once hasMore is false. The observer is created in an effect, so
the list renders on the server.
<LogList.Root aria-busy={isPending || undefined}>
{isPending ? (
<LogList.Loading />
) : rows.length === 0 ? (
<LogList.Empty title="No requests yet" description="Requests show here as your app calls the API." />
) : (
<>
{rows.map((row) => <LogList.Row key={row.id} … />)}
<LogList.LoadMore onLoadMore={fetchNextPage} loading={isFetchingNextPage} hasMore={hasNextPage} />
</>
)}
</LogList.Root>
Props
LogList.Row
- id —
string— mono, middle-truncated (pho_req_01m0…a84gb), copied on click - status —
ReactNode— usuallyLogList.Status - method —
string—POST,GET - label —
ReactNode— the route, the event type, the message - code —
boolean— the label in mono - description —
ReactNode— the line under the label; a sentence, with its period - duration / time —
ReactNode— trailing, tabular - children — trailing content before the duration and time
- href —
string— the row is a link throughLinkProvider - onClick —
(event) => void— the row is a button - render — a render element for the row’s link (a router’s
Link) - current —
boolean— the row whose page is open keeps the wash - …plus any
<div>attribute on the row
LogList.Status: value (number | string), label, size (default sm).
LogList.LoadMore: onLoadMore, loading, hasMore (default true), rootMargin, label.
LogList.Empty: title (default Nothing here yet.), description, children (an action).
LogList.Loading: rows (default 5).
Helpers: logStatusColor(status), logStatusLabel(status), truncateMiddle(id, head?, tail?).
Accessibility
- One link per row. The label is the link (or button), stretched over
the row, so the row is one pointer target and one tab stop; the id’s copy
control sits above it and is its own stop. The link is described by the
row’s cells (
aria-describedby), so a screen reader hears the id, the status, the method, and the time after the label. - Copy announces. The id button is named
Copy {id}, turns to{id} copied, and a polite live region saysCopied. - Status is text. The badge carries a word or a code, never a color alone.
Best practices
- Descriptions carry real content: who received what from whom, what the destination answered. Never restate the label.
- Relative times (
2 min ago) and durations (59 ms) are the page’s to format; the row shows what it is given. - Actions live on the row’s page, one level down. The row is the way there.
- Pin a
FilterBarabove the list in the same scroll pane; the list has no scroll container of its own.