lensclear design system

Create link, skeleton, empty, and error rows for lists.

Overview

One idiom for the console's list pages, so an operator moving between them reads the same shape every time.

The table is always rendered. Loading, empty, and error are body states, not page states, so the thead stays on screen through all four and nothing jumps when the data lands.

The create action is a solid pill in the PageHeader right slot. It looks like a button and is a link, because navigation is the whole action.

Create link

The header-slot action. A 36px pill, the wizards' primary weight scaled to sit on a title baseline.

Strategies

Per-collateral risk parameters and the positions they govern.

import { ListCreateLink } from "@clear-labs/lens/next/list-page";
import { PageHeader } from "@clear-labs/lens/next/page-header";

<PageHeader
  title="Strategies"
  right={
    <ListCreateLink href="/ez/strategies/new" testId="strategies-new-link">
      + New strategy
    </ListCreateLink>
  }
/>

Loading

Rows by columns of pulsing cells under the real thead. Widths cycle through three fixed steps rather than guessing the data's shape.

AssetDebt ceilingMax LTVPositions
<table>
  <thead>{/* the real header, in every state */}</thead>
  <tbody>
    <ListSkeletonRows columns={4} testId="strategies-loading" />
  </tbody>
</table>

Empty

One full-width row: the headline says what is true, the hint says why that is fine, and the primary action gets full weight. The secondary link is optional and demoted.

AssetDebt ceilingMax LTVPositions

No strategies registered on chain.

Zero add_strategy calls have landed on this cluster. That is chain truth, not a loading artifact.

Add the first strategyOpen the generic wizard instead
<ListEmptyRow
  columns={4}
  headline="No strategies registered on chain."
  hint="Zero add_strategy calls have landed on this cluster."
  cta={{
    href: "/ez/strategies/new",
    label: "Add the first strategy",
    testId: "strategies-empty-add-link",
  }}
  secondaryCta={{
    href: "/ez/feeds/new",
    label: "Open the generic wizard instead",
    testId: "feeds-empty-wizard-link",
  }}
  testId="strategies-empty"
/>

Error

Tinted fill, no border, and the message verbatim. An operator debugging a failed read needs the real string.

AssetDebt ceilingMax LTVPositions

RPC 429: too many requests. Retry in 12s.

<ListErrorRow
  columns={4}
  message={error.message}
  testId="strategies-error"
/>

ListCreateLink props

hrefstringrequired

The wizard entry for this list. A real link, so cmd-click works.

childrenReactNoderequired

The label. Usually a plus and a noun.

testIdstringrequired

data-testid on the anchor.

ListSkeletonRows props

columnsnumberrequired

Cells per row. Match the thead.

testIdstringrequired

data-testid on the first row only.

rowsnumberdefault: 5

How many pulsing rows to reserve.

ListEmptyRow props

columnsnumberrequired

The colspan. Match the thead.

headlinestringrequired

What is true. Chain truth, stated plainly.

hintstringrequired

Why that is not an error.

The primary action at full weight: a 48px pill.

A demoted text link under the pill. Never a second button.

testIdstringrequired

data-testid on the centred block.

ListErrorRow props

columnsnumberrequired

The colspan. Match the thead.

messagestringrequired

The underlying error, verbatim. No euphemism.

testIdstringrequired

data-testid on the tinted block.