KQ-2129 · RECORD/STEPPER

Record Stepper

A detail-page header that knows where the record sits in its list — "12 of 240" — with Previous and Next: stepping slides the title stack up or down by slideDistance on springs.glide in the direction you moved, clipped to the header so it reads as the list moving under a window, while the counter's digits roll the same way on the same glide, the record body cross-slides by half the distance and the peek of the next record updates. A neighbour the host has not loaded still moves the counter while the title and body show skeletons until it lands, and at either end the missing step leans the title 4 px on springs.flick and returns it on springs.snap. J and K step, Escape returns to the list, and below 30rem the counter becomes a small progress tick.

KQ-2129 · RECORD/STEPPER

GW-4417

Can't add a second admin to our workspace

Ticket 12 of 240 in Open tickets
Ines Duarte · BasinworksOpenUrgentupdated 12 min ago

The invite goes out but the role drops back to member when they accept. I'm the only admin and I'm away from Monday.

NextGW-4418Login loop after the SSO change

J next · K previous · Esc back to the list

12 of 240 · GW-4417 · loaded

Install

One command — the source lands in your repo. Or copy it from the Code tab.

$ pnpm dlx shadcn@latest add @kinetiq/record-stepper

Props

order
string[]
default defaultRecordStepperOrder
Every record id in the list, in list order; its length is the total the counter reads.
records
RecordStepperRecord[]
default defaultRecordStepperRecords
The records the host has loaded: { id, title, requester, company, state, priority, updatedAt, excerpt }. A position whose record is missing shows as a loading neighbour until it arrives.
position / defaultPosition
number
default 12
The 1-based position in the list, controlled or initial.
onStep
(direction: 1 | -1, position: number, record: RecordStepperRecord | null) => void
default —
Fires from the press or key that stepped, with the new position and its record — null while the host has not loaded it, which is the cue to fetch.
onBackToList
() => void
default —
Fires from the list button, the end-of-list link or Escape.
listLabel
string
default "Open tickets"
The list's name, on the back button and in the announcements.
filters
{ id: string; label: string }[]
default defaultRecordStepperFilters
The filters the list is under: a count on the back button, the chips themselves from 52rem, and part of its accessible name.
slideDistance
number
default 16
How far a title travels when stepping, px; the body travels half of it.
counterStyle
"words" | "slash" | "track"
default "words"
How the place in the list reads: "12 of 240", "12/240" in an inset pill, or "12 of 240" over a small track whose thumb glides to the position. Below 30rem every style becomes a vertical progress tick.
accent
"cobalt" | "plum" | "mint" | "ink" | string
default "cobalt"
The track thumb, the requester mark and the end-of-list link: a named accent or any CSS colour.
now
number
default defaultRecordStepperNow
The time updates are measured from ("updated 12 min ago"), ms since the epoch.
shortcuts
boolean
default true
Listen for J, K and Escape while focus is inside the piece (ignored while typing in a field).
status
"ready" | "error"
default "ready"
Record state: error shows a retry in place of the record; stepping still works.
onRetry
() => void
default —
Called by the error state's Retry button.
disabled
boolean
default false
Turns stepping and the buttons off.
className
string
default —
Classes for the outer section.