KQ-631 · HOLDINGS/RING
Holdings Ring
An allocation ring that re-proportions rather than redraws: every asset is one stroked circle whose pathLength is its share and whose pathOffset is its start on the rim, so adding or removing an asset animates two numbers on glide and the whole ring settles as one body. Segments draw in on a cascade at mount and shrink away on the exit ease, while pointing at one lifts it along its own mid angle on snap and cross-fades the hub to that asset's amount and share. The SVG is decorative; the legend under it is a real radiogroup with a roving tabindex where arrows step, Home and End jump, Space selects and Escape hands the hub back to the total.
Mix 4 assets · Total 45,350
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/holdings-ring
Props
- assets
- RingAsset[]
- default —
- The mix clockwise from twelve o'clock: id, label and value. Non-positive assets are dropped.
- value / defaultValue
- string | null
- default null
- Controlled or initial selected asset id; null reads the portfolio total.
- onValueChange
- (id: string | null) => void
- default —
- Fires from the click, key or Escape that changed the selection.
- format
- (value: number) => string
- default Intl.NumberFormat("en-US", { style: "currency", currency: "USD", maximumFractionDigits: 0 })
- Formats every amount. The default pins an explicit locale so server and client agree.
- label
- React.ReactNode
- default —
- Visible group heading; omit it and pass aria-label to name the ring invisibly.
- totalLabel
- string
- default "Total"
- Caption over the hub figure while nothing is selected.
- thickness
- number
- default 13
- Segment stroke width in the 120-unit viewBox.
| Prop | Type | Default | Description |
|---|---|---|---|
| assets | RingAsset[] | — | The mix clockwise from twelve o'clock: id, label and value. Non-positive assets are dropped. |
| value / defaultValue | string | null | null | Controlled or initial selected asset id; null reads the portfolio total. |
| onValueChange | (id: string | null) => void | — | Fires from the click, key or Escape that changed the selection. |
| format | (value: number) => string | Intl.NumberFormat("en-US", { style: "currency", currency: "USD", maximumFractionDigits: 0 }) | Formats every amount. The default pins an explicit locale so server and client agree. |
| label | React.ReactNode | — | Visible group heading; omit it and pass aria-label to name the ring invisibly. |
| totalLabel | string | "Total" | Caption over the hub figure while nothing is selected. |
| thickness | number | 13 | Segment stroke width in the 120-unit viewBox. |