Pho Design System

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.

200POST

+1 332 259 3374 received a message from +1 332 222 1111.

59 ms2 min ago
201POST

Support agent was created.

120 ms9 min ago
404GET

No message with that id belongs to this project.

14 ms24 min ago
500POST

The carrier did not answer within 10 seconds.

10.2 s1 h ago
204DELETE

+1 332 259 3374 was released.

88 ms3 h ago

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.

Delivered
message.delivered

https://example.com/hooks/photon answered 200 in 320 ms.

1 of 32 min ago
Failed
message.failed

https://example.com/hooks/photon answered 503 three times.

3 of 314 min ago
Pending
agent.created

Retrying https://example.com/hooks/photon in 4 minutes.

2 of 331 min ago

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.

Received
Hi, is my order still on the way?

Received from +1 332 259 3374.

2 min ago
Received
Thanks, that works.

Received from +1 332 259 3374.

just now
<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

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

Best practices