KQ-1686 · SENDER/STACKS
Sender Stacks
A notification panel that gathers notifications by who sent them, each source a neat pile of cards — the newest on top, the next few peeking below, the unread count pinned on top. Opening a pile fans its cards down into a list on springs.glide, top card first, while the pile's height glides with them, and closing gathers them back in reverse; dismissing a source sweeps the whole pile aside together, the header leading and each card trailing a beat behind, then the gap closes and Undo is offered. Up and Down walk the stacks and an open stack's cards, Enter opens, Escape closes, Delete dismisses.
Notifications
8 unreadUp and Down move between stacks and through an open stack. Enter opens a stack, Escape closes it, Delete dismisses it.
8 unread · gathered by sender
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/sender-stacks
Props
- items
- SenderItem[]
- default defaultSenderItems
- The notifications, grouped by source: { id, source, title, body?, at, read? }.
- sources
- SenderSource[]
- default defaultSenderSources
- The senders: { id, name, icon? } with icon one of deploy, review, billing, mention, alert, calendar.
- now
- number
- default defaultSenderNow
- The moment relative times count from, ms; never read from the clock during render.
- open / defaultOpen
- string[]
- default — / []
- The open stacks' source ids, controlled or initial.
- onOpenChange
- (open: string[]) => void
- default —
- Fires from the press or key that opened or closed a stack.
- multiple
- boolean
- default false
- Let several stacks stay open at once.
- onOpen
- (sourceId: string) => void
- default —
- A stack was opened.
- onDismiss
- (sourceId: string, itemIds: string[]) => void
- default —
- A stack was dismissed, with the ids it held.
- onRestore
- (sourceId: string) => void
- default —
- Undo brought a dismissed stack back.
- onMarkRead
- (sourceId: string, itemIds: string[]) => void
- default —
- A stack was marked read, with the ids that were unread.
- onMarkAllRead
- (itemIds: string[]) => void
- default —
- Mark all read was pressed, with every unread id.
- onItemOpen
- (item: SenderItem) => void
- default —
- A notification in an open stack was pressed; it is marked read.
- stackDepth
- number
- default 2
- How many cards peek under each stack's top card, 0 to 4.
- cardStyle
- "raised" | "flat" | "outline"
- default "raised"
- Shadowed paper cards, filled flat cards, or hairline outline cards; lower layers take a shade darker.
- accent
- string
- default "var(--accent-bright)"
- Count pills, unread dots and an open stack's rail; filled pills take its hue at a fixed lightness.
- undoMs
- number
- default 5000
- How long Undo is offered after a dismissal, ms.
- maxHeight
- number
- default 560
- Tallest the panel grows before its stacks scroll, px.
- title
- string
- default "Notifications"
- The panel's heading and accessible name.
- status
- "ready" | "loading" | "error"
- default "ready"
- Loading draws breathing skeleton piles; error offers Try again; no items draws the caught-up state.
- onReload
- () => void
- default —
- Try again was pressed after the notifications failed to load.
- disabled
- boolean
- default false
- Nothing can be opened, read or dismissed.
- className
- string
- default —
- Classes for the panel.
| Prop | Type | Default | Description |
|---|---|---|---|
| items | SenderItem[] | defaultSenderItems | The notifications, grouped by source: { id, source, title, body?, at, read? }. |
| sources | SenderSource[] | defaultSenderSources | The senders: { id, name, icon? } with icon one of deploy, review, billing, mention, alert, calendar. |
| now | number | defaultSenderNow | The moment relative times count from, ms; never read from the clock during render. |
| open / defaultOpen | string[] | — / [] | The open stacks' source ids, controlled or initial. |
| onOpenChange | (open: string[]) => void | — | Fires from the press or key that opened or closed a stack. |
| multiple | boolean | false | Let several stacks stay open at once. |
| onOpen | (sourceId: string) => void | — | A stack was opened. |
| onDismiss | (sourceId: string, itemIds: string[]) => void | — | A stack was dismissed, with the ids it held. |
| onRestore | (sourceId: string) => void | — | Undo brought a dismissed stack back. |
| onMarkRead | (sourceId: string, itemIds: string[]) => void | — | A stack was marked read, with the ids that were unread. |
| onMarkAllRead | (itemIds: string[]) => void | — | Mark all read was pressed, with every unread id. |
| onItemOpen | (item: SenderItem) => void | — | A notification in an open stack was pressed; it is marked read. |
| stackDepth | number | 2 | How many cards peek under each stack's top card, 0 to 4. |
| cardStyle | "raised" | "flat" | "outline" | "raised" | Shadowed paper cards, filled flat cards, or hairline outline cards; lower layers take a shade darker. |
| accent | string | "var(--accent-bright)" | Count pills, unread dots and an open stack's rail; filled pills take its hue at a fixed lightness. |
| undoMs | number | 5000 | How long Undo is offered after a dismissal, ms. |
| maxHeight | number | 560 | Tallest the panel grows before its stacks scroll, px. |
| title | string | "Notifications" | The panel's heading and accessible name. |
| status | "ready" | "loading" | "error" | "ready" | Loading draws breathing skeleton piles; error offers Try again; no items draws the caught-up state. |
| onReload | () => void | — | Try again was pressed after the notifications failed to load. |
| disabled | boolean | false | Nothing can be opened, read or dismissed. |
| className | string | — | Classes for the panel. |