KQ-716 · GOAL/CARD
Goal Card
One card for one goal: a ring whose stroke is the share saved, the goal's name and figure beside it, an add control, and a details toggle that unfolds a panel underneath. Pressing Add mints a coin at the ring's rim that drops into the hub on recoil — the two visible bounces of a coin landing in a slot — and only when it lands does the ring grow to the new share on glide and the hub's percentage roll on snap, so the ring moves because the coin arrived; reaching the goal turns the ring success and draws a tick on flick. The panel's height is measured by a ResizeObserver and animated on glide with the chevron turning on snap. The ring is a meter that carries the share in words, the panel is inert while closed, and a status line announces each add once.
- Goal
- $600.00
- Saved
- $360.00
- Remaining
- $240.00
- Weeks left
- 8 at $30.00 a week
By 30 Nov
Saved $360.00 of $600.00 · 60% · Details closed
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/goal-card
Props
- value / defaultValue
- number
- default 0
- Controlled or initial amount saved. An increase mints a coin and the ring waits for it to land; a decrease shrinks the ring at once with no coin.
- onValueChange
- (value: number, added: number) => void
- default —
- Fires from the Add press that changed the amount, with the new total and what was added.
- goal
- number
- default —
- The target; the ring closes here and Add turns aria-disabled once it is reached.
- step
- number
- default 25
- The amount one Add press contributes, clamped so the last press lands exactly on the goal.
- weekly
- number
- default step
- Planned contribution per week; the panel projects the weeks left from it.
- deadline
- string
- default —
- A quiet line in the panel ("By 30 Nov").
- open / defaultOpen
- boolean
- default false
- Controlled or initial state of the details panel.
- onOpenChange
- (open: boolean) => void
- default —
- Fires from the Details toggle press.
- format
- (value: number) => string
- default Intl.NumberFormat("en-US", { style: "currency", currency: "USD" })
- Formats every amount. The default pins an explicit locale so server and client agree.
- label
- string
- default —
- The goal's name; printed as the heading and read as the meter's label.
| Prop | Type | Default | Description |
|---|---|---|---|
| value / defaultValue | number | 0 | Controlled or initial amount saved. An increase mints a coin and the ring waits for it to land; a decrease shrinks the ring at once with no coin. |
| onValueChange | (value: number, added: number) => void | — | Fires from the Add press that changed the amount, with the new total and what was added. |
| goal | number | — | The target; the ring closes here and Add turns aria-disabled once it is reached. |
| step | number | 25 | The amount one Add press contributes, clamped so the last press lands exactly on the goal. |
| weekly | number | step | Planned contribution per week; the panel projects the weeks left from it. |
| deadline | string | — | A quiet line in the panel ("By 30 Nov"). |
| open / defaultOpen | boolean | false | Controlled or initial state of the details panel. |
| onOpenChange | (open: boolean) => void | — | Fires from the Details toggle press. |
| format | (value: number) => string | Intl.NumberFormat("en-US", { style: "currency", currency: "USD" }) | Formats every amount. The default pins an explicit locale so server and client agree. |
| label | string | — | The goal's name; printed as the heading and read as the meter's label. |