Live rows
ProThe rows of a list whose data changes under the person: Inbox's items and ApprovalQueue's requests. A row added after the list first drew rises in; one that leaves the data stays, inert and hidden from screen readers, for its out moment in the place it had; a real re-sort slides each row to its new place. With selection, each live row has a checkbox in a 24px target before it. The item draws each row's content (row) and its empty state (empty), and passes its own motion moments, so each item's motion is named for it.
Key it by the view it shows, so changing the view starts it over: its rows are there, not arriving or leaving. An internal part of Inbox and ApprovalQueue, installed with them.
Version 5.2.0· other versions
This page is for 5.2.0, the current release. An earlier release’s page is the Markdown that release published, so the docs match the version you have installed (candor-lock.json records it).
Not in the docs of 1.3.0, 1.4.0, 1.5.0, 1.5.1, 1.6.0, 1.7.0, 1.8.0, 1.8.1, 1.9.0, 1.9.1, 2.0.0, 2.1.0, 2.2.0, 2.3.0, 2.4.0, 2.5.0, 2.5.1, 2.6.0, 2.6.1, 2.6.2, 2.7.0, 2.8.0, 2.9.0, 2.10.0, 3.0.0, 4.0.0, 4.0.1, 4.0.2, 4.1.0, 4.2.0, 4.3.0, 4.4.0, 4.5.0 and 5.0.0: the page came later, or had another name then.
Docs pages weren’t kept for 1.0.0, 1.0.1, 1.1.0, 1.1.1, 1.2.0 and 1.2.1: they are kept from 1.3.0 on, and before that only llms.txt was published.
Preview
Loading the preview
Install
First time? Get started sets up the registry and theme. Or with shadcn directly: npx shadcn@latest add @candor/live-rows
Usage
"use client"
import * as React from "react"
import { LiveRows } from "@/components/candor/live-rows"
import { motion } from "@/lib/candor/motion"
/** The example's own moments, as Inbox and ApprovalQueue name theirs. */
const rowMotion = {
arrive: { style: motion("live-rows-example", "arrive", "arrive"), className: "cd-arrive" },
leave: { style: motion("live-rows-example", "leave", "reveal"), className: "cd-reveal-out calm:hidden" },
reorder: { style: motion("live-rows-example", "reorder", "move"), className: "data-[reorder]:cd-move" },
}
type Task = { id: string; title: string }
export default function Example() {
const [tasks, setTasks] = React.useState<Task[]>([
{ id: "a", title: "Review the migration" },
{ id: "b", title: "Answer the region question" },
])
const [sel, setSel] = React.useState<Set<string>>(() => new Set())
const next = React.useRef(3)
return (
<div className="flex max-w-md flex-col gap-3">
<div className="flex flex-wrap gap-2">
<button
type="button"
className="rounded-md border px-2 py-1 text-sm"
onClick={() => {
const n = next.current++
setTasks((list) => [{ id: `t${n}`, title: `New task ${n}` }, ...list])
}}
>
Add one
</button>
<button type="button" className="rounded-md border px-2 py-1 text-sm" onClick={() => setTasks((list) => list.slice(1))}>
Clear the first
</button>
<button type="button" className="rounded-md border px-2 py-1 text-sm" onClick={() => setTasks((list) => [...list].reverse())}>
Re-sort
</button>
</div>
<div className="rounded-xl border bg-card">
<LiveRows
items={tasks}
keyOf={(task) => task.id}
motion={rowMotion}
className="relative divide-y"
rowClassName="flex items-center"
selection={{
sel,
toggle: (id) =>
setSel((s) => {
const out = new Set(s)
if (out.has(id)) out.delete(id)
else out.add(id)
return out
}),
label: (task) => `Select ${task.title}`,
className: "flex shrink-0 items-center ps-3",
}}
empty={<p className="px-4 py-8 text-center text-sm text-muted-foreground">Nothing left.</p>}
row={(task) => (
<button type="button" className="flex-1 px-3 py-2.5 text-start text-sm outline-none hover:bg-accent focus-visible:bg-accent">
{task.title}
</button>
)}
/>
</div>
</div>
)
}<LiveRows>
| Prop | Type | Default | Description |
|---|---|---|---|
| itemsrequired | T[] | — | The rows to draw, in order. |
| keyOfrequired | (item: T) => string | — | A row's id: its key, `data-row-id`, and what `selection` holds. |
| motionrequired | LiveRowsMotion | — | The item's own motion moments. |
| rowrequired | (item: T) => React.ReactNode | — | A row's content, after its checkbox: usually the button that opens it. |
| emptyrequired | React.ReactNode | — | Drawn in place of the list while there are no rows, leaving ones included. |
| selection | LiveRowsSelection<T> | undefined | — | Adds a checkbox before each live row. |
| className | string | undefined | — | Classes for the list. |
| rowClassName | string | undefined | — | Classes for the box inside each row that holds its checkbox and content. |
Types
- LiveRowsMoment
- One of the item's motion moments: its chain, made with `motion(item, moment, family)` in the item's file, and the family utility that plays it, named there too, so each item's motion stays its own.
{ /** The moment's chain, for the row's style. */ style: React.CSSProperties; /** The family utility (and any classes it needs), for the row's classes. */ className: string; }- LiveRowsMotion
- The item's own motion moments.
{ /** A row added after the list first drew rises in. */ arrive: LiveRowsMoment; /** A row that left the data collapses out. */ leave: LiveRowsMoment; /** A row slides to its new place when the data re-sorts the list; the part sets and applies the row's offset. */ reorder: LiveRowsMoment; }- LiveRowsSelection
- Selection on the rows: a checkbox before each one that isn't leaving.
{ /** The ids selected. */ sel: Set<string>; /** Ticks or clears a row by its id. */ toggle: (id: string) => void; /** The checkbox's name for a row, in the item's words ("Select {title}"). */ label: (item: T) => string; /** Classes for the box's 24px target's holder, which places it beside the row. */ className: string; }