KQ-2112 · MILESTONE/RINGS
Milestone Rings
A project sidebar where every row wraps its icon in a ring whose arc is progress toward the project's next milestone, with a tally of tiny flags for the milestones already reached. When the data says one is reached, the arc closes on springs.glide, flashes once with a halo on a tween, a flag lifts off the ring and flies to the tally (x on glide, y on springs.snap, landing on springs.recoil), and the ring resets on snap — the arc empties forward and the next milestone's share draws in as the meta line changes. Rows are a listbox where Up and Down walk, Enter chooses, Alt+M opens the focused milestone and Alt with an arrow moves the row, a grip drags rows 1:1 while the others glide aside, and the sidebar folds to a rail where the rings stay wrapped round their icons.
Up and Down move between projects, Enter opens one, Alt+M opens its next milestone, Alt with Up or Down moves the project.
Gauge Mini · Tooling sign-off 82% · 2 flags
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/milestone-rings
Props
- projects
- MilestoneProject[]
- default defaultMilestoneProjects
- The projects in their starting order: { id, name, icon?, milestones: { id, name, due (ISO day), progress 0–1, done? }[] }. The first milestone not reached is the next one.
- current / defaultCurrent
- string
- default the first project
- The chosen project, controlled or initial.
- onNavigate
- (projectId: string) => void
- default —
- A project was chosen with a click, Enter or Space.
- onOpenMilestone
- (projectId: string, milestone: Milestone | null) => void
- default —
- Alt+M on a row, or a milestone in the side pane, asked to open a milestone; null when the project has none upcoming.
- onReorder
- (ids: string[]) => void
- default —
- The rows were reordered by a grip drag or Alt with an arrow; the new id order.
- folded / defaultFolded
- boolean
- default false
- The sidebar as a 60px rail of rings, controlled or initial.
- onFoldedChange
- (folded: boolean) => void
- default —
- Fires from the button that folded or opened the sidebar.
- now
- string | number | Date
- default "2026-10-14"
- Today, for due and overdue; read in UTC so server and browser agree.
- ringSize
- number
- default 26
- The ring's diameter in px; icons and row heights scale with it.
- overdueStyle
- "tint" | "hatch" | "label"
- default "tint"
- How a late milestone shows: the arc and date in danger, a hatched danger track, or a calm ring with a "4d late" chip.
- accent
- "cobalt" | "plum" | "mint" | "ink" | string
- default "cobalt"
- Arcs, the current row and flags. A name or any CSS colour.
- flagLimit
- number
- default 4
- Flags drawn in a row's tally before it reads "+n".
- status
- "ready" | "loading" | "error"
- default "ready"
- Whether the projects have arrived: still skeleton rows while loading, a message and Retry on error.
- onRetry
- () => void
- default —
- Retry was pressed after an error.
- workspace
- string
- default "Gaugeworks"
- The workspace in the header.
- title
- string
- default "Product lines"
- The list's heading and accessible name.
- frameHeight
- number
- default 520
- The frame's height in px; the list scrolls inside it.
- disabled
- boolean
- default false
- Read only: nothing can be chosen, opened or moved.
- className
- string
- default —
- Classes for the frame.
| Prop | Type | Default | Description |
|---|---|---|---|
| projects | MilestoneProject[] | defaultMilestoneProjects | The projects in their starting order: { id, name, icon?, milestones: { id, name, due (ISO day), progress 0–1, done? }[] }. The first milestone not reached is the next one. |
| current / defaultCurrent | string | the first project | The chosen project, controlled or initial. |
| onNavigate | (projectId: string) => void | — | A project was chosen with a click, Enter or Space. |
| onOpenMilestone | (projectId: string, milestone: Milestone | null) => void | — | Alt+M on a row, or a milestone in the side pane, asked to open a milestone; null when the project has none upcoming. |
| onReorder | (ids: string[]) => void | — | The rows were reordered by a grip drag or Alt with an arrow; the new id order. |
| folded / defaultFolded | boolean | false | The sidebar as a 60px rail of rings, controlled or initial. |
| onFoldedChange | (folded: boolean) => void | — | Fires from the button that folded or opened the sidebar. |
| now | string | number | Date | "2026-10-14" | Today, for due and overdue; read in UTC so server and browser agree. |
| ringSize | number | 26 | The ring's diameter in px; icons and row heights scale with it. |
| overdueStyle | "tint" | "hatch" | "label" | "tint" | How a late milestone shows: the arc and date in danger, a hatched danger track, or a calm ring with a "4d late" chip. |
| accent | "cobalt" | "plum" | "mint" | "ink" | string | "cobalt" | Arcs, the current row and flags. A name or any CSS colour. |
| flagLimit | number | 4 | Flags drawn in a row's tally before it reads "+n". |
| status | "ready" | "loading" | "error" | "ready" | Whether the projects have arrived: still skeleton rows while loading, a message and Retry on error. |
| onRetry | () => void | — | Retry was pressed after an error. |
| workspace | string | "Gaugeworks" | The workspace in the header. |
| title | string | "Product lines" | The list's heading and accessible name. |
| frameHeight | number | 520 | The frame's height in px; the list scrolls inside it. |
| disabled | boolean | false | Read only: nothing can be chosen, opened or moved. |
| className | string | — | Classes for the frame. |