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