KQ-1699 · LAST/SLOT
Last Slot
A project grid at its plan's limit: the used projects, then one ghost slot carrying a lock. Hovering or focusing the ghost unlatches the lock on springs.snap (a shackle swings, a bolt slides out of its keeper, or a dial spins and pops) and a beat later the card turns over on springs.glide, about Y in the grid and about X when it is a wide row, to show the plan that holds more projects and its price while the header's meter glides to the new limit. Every project stays a working button with arrow-key travel by the measured columns; the ghost is one labelled button that upgrades (admins) or asks an admin (members), with Compare plans and Not now beside it.
Projects
Starter5 of 5 projects · starter plan
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/last-slot
Props
- projects
- LastSlotProject[]
- default defaultLastSlotProjects
- The projects in the grid: { id, name, owner, updatedAt, tasks?, done?, next?, members?, tint? }.
- usage
- { used: number; limit: number }
- default { used: projects.length, limit: plan.limit }
- How many projects are used and allowed; at the limit the next cell carries the lock.
- plan
- LastSlotPlan
- default defaultLastSlotPlan
- The workspace's plan: { id, name, limit, price?, unit?, period?, perks? }.
- offer
- LastSlotPlan
- default defaultLastSlotOffer
- The plan that holds more projects, shown on the back of the slot with its price and two perks.
- viewerRole
- "admin" | "member"
- default "admin"
- Admins upgrade from the slot; members ask an admin.
- adminName
- string
- default "Mara"
- Who members ask.
- onUpgrade
- (offer: LastSlotPlan) => void
- default —
- The slot was pressed by an admin at the limit.
- onAskAdmin
- (offer: LastSlotPlan) => void
- default —
- The slot was pressed by a member at the limit.
- onCompare
- () => void
- default —
- Compare plans was pressed.
- onDismiss
- () => void
- default —
- Not now was pressed: the slot steps aside and a limit chip appears in the header.
- dismissed / defaultDismissed
- boolean
- default — / false
- Whether the slot has been set aside, controlled or initial.
- onDismissedChange
- (dismissed: boolean) => void
- default —
- Fires from Not now (true) or the header's chip (false).
- onOpenProject
- (project: LastSlotProject) => void
- default —
- A project card was pressed.
- onCreate
- () => void
- default —
- The slot was pressed while there is still room under the limit (it shows a plus, not a lock).
- now
- number
- default defaultLastSlotNow
- The moment edit times count from, in ms; never the wall clock.
- formatPrice
- (amount: number) => string
- default US dollars
- Formats the offer's price; cents only when there are some.
- slotStyle
- "dashed" | "frosted" | "grid"
- default "dashed"
- The empty slot: a dashed outline, frosted glass over a faint card, or blueprint grid paper.
- lockStyle
- "shackle" | "bolt" | "dial"
- default "shackle"
- A padlock whose shackle lifts and swings, a door bolt that slides out of its keeper, or a combination dial that spins and pops.
- accent
- "cobalt" | "signal" | "amber" | "graphite" | string
- default "cobalt"
- The open lock, the offer's eyebrow and checks, the grid paper and the meter's offer run.
- status
- "ready" | "loading" | "error"
- default "ready"
- Whether the projects have arrived: placeholder cards while loading, a retry on error.
- onRetry
- () => void
- default —
- Try again was pressed after the projects failed to load.
- title
- string
- default "Projects"
- The heading.
- disabled
- boolean
- default false
- Holds every press; the slot no longer turns.
- className
- string
- default —
- Classes for the surface; give it a height and the grid scrolls inside it.
| Prop | Type | Default | Description |
|---|---|---|---|
| projects | LastSlotProject[] | defaultLastSlotProjects | The projects in the grid: { id, name, owner, updatedAt, tasks?, done?, next?, members?, tint? }. |
| usage | { used: number; limit: number } | { used: projects.length, limit: plan.limit } | How many projects are used and allowed; at the limit the next cell carries the lock. |
| plan | LastSlotPlan | defaultLastSlotPlan | The workspace's plan: { id, name, limit, price?, unit?, period?, perks? }. |
| offer | LastSlotPlan | defaultLastSlotOffer | The plan that holds more projects, shown on the back of the slot with its price and two perks. |
| viewerRole | "admin" | "member" | "admin" | Admins upgrade from the slot; members ask an admin. |
| adminName | string | "Mara" | Who members ask. |
| onUpgrade | (offer: LastSlotPlan) => void | — | The slot was pressed by an admin at the limit. |
| onAskAdmin | (offer: LastSlotPlan) => void | — | The slot was pressed by a member at the limit. |
| onCompare | () => void | — | Compare plans was pressed. |
| onDismiss | () => void | — | Not now was pressed: the slot steps aside and a limit chip appears in the header. |
| dismissed / defaultDismissed | boolean | — / false | Whether the slot has been set aside, controlled or initial. |
| onDismissedChange | (dismissed: boolean) => void | — | Fires from Not now (true) or the header's chip (false). |
| onOpenProject | (project: LastSlotProject) => void | — | A project card was pressed. |
| onCreate | () => void | — | The slot was pressed while there is still room under the limit (it shows a plus, not a lock). |
| now | number | defaultLastSlotNow | The moment edit times count from, in ms; never the wall clock. |
| formatPrice | (amount: number) => string | US dollars | Formats the offer's price; cents only when there are some. |
| slotStyle | "dashed" | "frosted" | "grid" | "dashed" | The empty slot: a dashed outline, frosted glass over a faint card, or blueprint grid paper. |
| lockStyle | "shackle" | "bolt" | "dial" | "shackle" | A padlock whose shackle lifts and swings, a door bolt that slides out of its keeper, or a combination dial that spins and pops. |
| accent | "cobalt" | "signal" | "amber" | "graphite" | string | "cobalt" | The open lock, the offer's eyebrow and checks, the grid paper and the meter's offer run. |
| status | "ready" | "loading" | "error" | "ready" | Whether the projects have arrived: placeholder cards while loading, a retry on error. |
| onRetry | () => void | — | Try again was pressed after the projects failed to load. |
| title | string | "Projects" | The heading. |
| disabled | boolean | false | Holds every press; the slot no longer turns. |
| className | string | — | Classes for the surface; give it a height and the grid scrolls inside it. |