KQ-2105 · TRAY/WINDOWS
Tray Windows
A workspace of floating windows where any record can be parked in a tray: minimising sends a ghost of the window — title bar and body lines — flying into its chip's slot on springs.glide, position, size and corner radius from one value, and the chip lands with a small drop on springs.recoil; restoring flies it back to exactly where the window was and returns focus to it. Chips keep an unsaved marker and ask before discarding, and a full tray puts the rest behind a +N menu. Cmd or Ctrl+M minimises the focused window and Cmd or Ctrl with a number restores that chip.
KQ-2105 · TRAY/WINDOWS
Support desk
FernworksRe: Refund for order 3195
To Ines Duarte · Coldbrook Café
Unsaved changes
Invoice INV-2048 shows the wrong depot
Ada Mensah · Basinworks LogisticsWaiting on us
The invoice says Hull but the goods went to Wakefield. Our finance team can’t pay it until the address matches.
Nothing waiting to send
Stock update for order 3201
To Tomas Berg · Gaugeworks Depot
Saved
3 open · 2 parked · 1 unsaved
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/tray-windows
Props
- records
- TrayRecord[]
- default defaultTrayRecords
- Every record with a window: drafts (an editable body) and tickets (the last message and a reply).
- tray / defaultTray
- string[]
- default — / ["t-2210", "t-2219"]
- The ids parked in the tray, in the order they were parked. Controlled or uncontrolled.
- onTrayChange
- (ids: string[]) => void
- default —
- Fires with the whole tray after a minimise, a restore or a close.
- unsaved / defaultUnsaved
- string[]
- default — / ["d-3195"]
- The ids with unsaved changes. Controlled or uncontrolled.
- onUnsavedChange
- (ids: string[]) => void
- default —
- Fires when typing or saving changes which ids are unsaved.
- positions
- Record<string, { x, y, w, h }>
- default defaultTrayPositions
- Where each window sits, as fractions of the workspace; a restore flies back here.
- onMinimise
- (id: string) => void
- default —
- A window was minimised into the tray.
- onRestore
- (id: string) => void
- default —
- A chip was restored to its window.
- onCloseItem
- (id: string) => void
- default —
- A window or a chip was closed, after the unsaved check.
- onSave
- (id: string, body: string) => void
- default —
- Save (a draft) or Send (a ticket reply) was pressed, with the text.
- chipSize
- "sm" | "md" | "lg"
- default "md"
- Chip length and height; large adds a meta line.
- trayPosition
- "bottom" | "floating" | "side"
- default "bottom"
- A bar docked along the bottom edge, a dock floating above it, or a column on the right.
- accent
- string
- default "var(--accent-bright)"
- Unsaved markers, focus and the primary buttons — any CSS colour.
- trayLimit
- number
- default 6
- The most chips shown before the rest go behind the overflow menu (fewer when the tray is shorter).
- status
- "ready" | "loading" | "error"
- default "ready"
- Whether the records have arrived.
- onRetry
- () => void
- default —
- Try again was pressed after the records failed to load.
- workspace
- string
- default "Fernworks"
- The workspace's name.
- title
- string
- default "Support desk"
- The frame's title.
- height
- number
- default 520
- The frame's height, px.
- className
- string
- default —
- Classes for the outer frame.
| Prop | Type | Default | Description |
|---|---|---|---|
| records | TrayRecord[] | defaultTrayRecords | Every record with a window: drafts (an editable body) and tickets (the last message and a reply). |
| tray / defaultTray | string[] | — / ["t-2210", "t-2219"] | The ids parked in the tray, in the order they were parked. Controlled or uncontrolled. |
| onTrayChange | (ids: string[]) => void | — | Fires with the whole tray after a minimise, a restore or a close. |
| unsaved / defaultUnsaved | string[] | — / ["d-3195"] | The ids with unsaved changes. Controlled or uncontrolled. |
| onUnsavedChange | (ids: string[]) => void | — | Fires when typing or saving changes which ids are unsaved. |
| positions | Record<string, { x, y, w, h }> | defaultTrayPositions | Where each window sits, as fractions of the workspace; a restore flies back here. |
| onMinimise | (id: string) => void | — | A window was minimised into the tray. |
| onRestore | (id: string) => void | — | A chip was restored to its window. |
| onCloseItem | (id: string) => void | — | A window or a chip was closed, after the unsaved check. |
| onSave | (id: string, body: string) => void | — | Save (a draft) or Send (a ticket reply) was pressed, with the text. |
| chipSize | "sm" | "md" | "lg" | "md" | Chip length and height; large adds a meta line. |
| trayPosition | "bottom" | "floating" | "side" | "bottom" | A bar docked along the bottom edge, a dock floating above it, or a column on the right. |
| accent | string | "var(--accent-bright)" | Unsaved markers, focus and the primary buttons — any CSS colour. |
| trayLimit | number | 6 | The most chips shown before the rest go behind the overflow menu (fewer when the tray is shorter). |
| status | "ready" | "loading" | "error" | "ready" | Whether the records have arrived. |
| onRetry | () => void | — | Try again was pressed after the records failed to load. |
| workspace | string | "Fernworks" | The workspace's name. |
| title | string | "Support desk" | The frame's title. |
| height | number | 520 | The frame's height, px. |
| className | string | — | Classes for the outer frame. |