KQ-1742 · ROOM/RAIL

Room Rail

A day of meeting rooms on one rail: rooms as rows with capacity, equipment and who holds them now, the working day across, the past dimmed behind a now-line that follows the now prop on springs.drift so a ticking clock sweeps instead of stepping. Pressing a free gap grows a booking out from that point to its default length on springs.snap with the end time riding its edge; both edges then drag 1:1 in quarter hours, rubber-band at the gap's bounds and settle on snap, and Book lands it on springs.flick. Your own bookings extend by a grip and release back to the now-line on springs.glide; arrow keys walk the free gaps, Enter grows a booking and Left and Right size it.

KQ-1742 · ROOM/RAIL

Basinworks rooms

Thursday 22 October · 2 of 6 rooms free now

10:20

Arrow keys walk the free time and your bookings. Enter on free time starts a booking; then Left and Right move its end, Shift with them its start, Enter books and Escape cancels. On your booking, the right bracket extends it and Delete releases it while it is in progress.

  • BasinGround

    12

    In use · Joel Brandt until 11:30

  • WeirFirst

    6

    Free until 13:00

  • SluiceFirst

    4

    Air handling repair · back Monday

  • DeltaSecond

    8

    In use · you until 11:00

  • SpillwaySecond

    2

    In use · Lotte Brink until 10:45

  • EstuaryThird

    20

    Free until 16:00

10:20 · 2 of 6 rooms free now

Install

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

$ pnpm dlx shadcn@latest add @kinetiq/room-rail

Props

rowHeight
number
default 56
Height of each room's row in px; from 56px rooms show their status line and blocks their holder.
blockStyle
"soft" | "solid" | "outline"
default "soft"
How bookings are drawn: a tint with a coloured lead edge, a pigment fill, or an outline.
accent
string
default "var(--accent-bright)"
Any CSS colour: your bookings, the draft and its readouts.
rooms
RailRoom[]
default defaultRailRooms
The rooms: { id, name, floor?, capacity, equipment: ("screen" | "video" | "whiteboard" | "projector")[], outOfService? (reason) }.
bookings / defaultBookings
RailBooking[]
default — / defaultRailBookings
The bookings, controlled or initial: { id, room, start, end (minutes), title, holder }.
onBookingsChange
(bookings: RailBooking[]) => void
default —
Fires from the book, extend or release that changed the bookings, with all of them.
onBook
(draft: { roomId: string; start: number; end: number }) => void
default —
A draft was booked.
onExtend
(booking: RailBooking, end: number) => void
default —
One of your bookings was extended to end.
onRelease
(booking: RailBooking) => void
default —
One of your bookings in progress was released; it now ends at now.
viewer
string
default "Rowan Hale"
Whose bookings are yours (matched against holder).
now
number
default 620
The current time in minutes from midnight. Never read from the clock.
day
[number, number]
default [8, 19]
The working day as whole hours.
step
number
default 15
The quarter everything snaps to, minutes.
defaultLength
number
default 30
A new booking's length before it is adjusted, minutes.
date
string
default "Thursday 22 October"
The day, as a heading.
status
"ready" | "loading" | "error"
default "ready"
Whether the rooms have arrived: loading shows skeleton rows, error a Try again.
onRetry
() => void
default —
Try again was pressed after the rooms failed to load.
title
string
default "Basinworks rooms"
The heading.
height
number
default 540
Height of the surface in px; its body scrolls inside.
disabled
boolean
default false
Nothing can be booked, extended or released.
className
string
default —
Classes for the outer surface.