KQ-2110 · BUBBLE/UP

Bubble Up

A project sidebar where what changed floats to the top: a project with new activity shows its count at home, popping in on springs.snap, and after liftDelay the row lifts out with a shadow on springs.flick and glides up on springs.glide into an Updated group that opens a slot for it while the group's count rolls, leaving a dotted placeholder that holds its home for a beat and then closes as the rows below slide up. Opening a project marks it read and sends it back down to its home on glide, and Shift+R sends the whole group home in cascade order. Every row is placed from a pure layout of fixed heights, so the server renders the positions the browser animates and the DOM order never changes; Up and Down walk the rows as they appear, Enter opens, M mutes, and below an 18.5rem container the Updated group folds into a count chip.

KQ-2110 · BUBBLE/UP

1 updated · waiting for activity

Install

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

$ pnpm dlx shadcn@latest add @kinetiq/bubble-up

Props

liftDelay
number
default 500
How long a row with new activity waits at home, its count showing, before it rises, in ms.
density
"compact" | "cozy" | "roomy"
default "cozy"
Row height and what each row says: one line, a meta line, or meta and client.
accent
"cobalt" | "plum" | "mint" | "ink" | string
default "cobalt"
Accent for counts, the chip and the Updated group. A name or any CSS colour.
projects
BubbleProject[]
default defaultBubbleProjects
The projects, in order within their groups: { id, name, client, group, hue? }.
groups
BubbleGroup[]
default defaultBubbleGroups
The home groups, in order: { id, label }.
events
BubbleEvent[]
default defaultBubbleEvents
Activity on the projects, any order: { id, projectId, at, by, kind }. A project is updated while it has events after its read time.
readAt / defaultReadAt
Record<string, number>
default — / defaultBubbleReadAt
For each project, the time it was read up to, controlled or initial.
onMarkRead
(projectIds: string[], readAt: Record<string, number>) => void
default —
Projects were marked read, by opening one or by Shift+R, with the whole new read state.
muted / defaultMuted
string[]
default — / []
Muted project ids, controlled or initial; a muted project never rises.
onMute
(projectId: string, muted: boolean) => void
default —
A project was muted or unmuted, by its bell or by M.
onOpen
(projectId: string) => void
default —
A project was opened.
current / defaultCurrent
string | null
default — / null
The open project's id, controlled or initial.
placeholderBeat
number
default 700
How long the dotted placeholder holds a risen row's home before it closes, in ms.
now
number
default defaultBubbleNow
The moment relative times count from, in ms, so server and browser agree.
title
string
default "Fernworks"
The studio's name in the header.
subtitle
string
default "Client projects"
The line under the name.
status
"ready" | "loading" | "error"
default "ready"
Whether the projects have arrived: skeleton rows while loading, a message with Try again on error.
onRetry
() => void
default —
Try again after an error.
label
string
default —
The navigation's accessible name. Defaults to "<title> projects".
className
string
default —
Classes for the outer container.