KQ-1633 · TALLY/FACETS
Tally Facets
A faceted sidebar beside a results list where every value carries a live count and a slim count bar, counted disjunctively so a facet's own picks never zero its siblings. Picking a value flicks its check in on springs.flick and, in the same frame, every other facet's bars glide to their new lengths on springs.glide while their counts roll; values that would now return nothing dim and sink to the foot of their group on glide, and results leave on the exit ease while the rest close up and returning rows rise in a cascade. Arrow keys walk every value in the order you see it, Space toggles, and below a 560px container the facets fold into a modal sheet whose buttons carry the live result count.
Orders
19 of 64 orders
19 of 64 orders · region north
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/tally-facets
Props
- facets
- TallyFacet[]
- default defaultTallyFacets
- The facets in order: { id, label, values?, badge? }. Values omitted are read from the rows, largest first; a badge facet shows as a pill on each result.
- rows
- TallyRow[]
- default defaultTallyRows
- The records counted and listed: { id, title, code, amount, at, facets } where facets maps facet id to value id.
- value / defaultValue
- Record<string, string[]>
- default — / {}
- Picked value ids by facet id, controlled or initial.
- onValueChange
- (selection) => void
- default —
- Fires with the whole selection after every pick, clear or chip removal.
- onChange
- (change: { facetId, valueId, selected, selection }) => void
- default —
- Fires once per decision with the value that changed and whether it is now picked.
- onClear
- () => void
- default —
- Fires when Clear empties the selection.
- onSaveView
- (selection) => void
- default —
- Fires from Save view with the current selection; the button only shows when this is passed.
- status
- "ready" | "loading" | "error"
- default "ready"
- Loading stripes every bar and skeletons the results; error keeps the last counts, hatched, under a Retry banner.
- onRetry
- () => void
- default —
- Retry was pressed after a failed refresh.
- density
- "compact" | "cozy" | "roomy"
- default "cozy"
- Facet row height (26, 30 or 36px) and type size.
- barStyle
- "bar" | "line" | "fill"
- default "bar"
- How a count is drawn: a slim bar under the label, a hairline rule, or a wash behind the whole row.
- accent
- string
- default "var(--accent-bright)"
- Checks, bars, chips and the live count, any CSS colour.
- sinkEmpty
- boolean
- default true
- Values that would return nothing sink to the foot of their group.
- now
- number
- default defaultTallyNow
- Today in ms, for the "3d ago" on each result; never read from the clock during render.
- formatAmount
- (amount: number) => string
- default whole US dollars
- Formats each result's amount.
- title
- string
- default "Orders"
- The heading over the results and the surface's accessible name.
- itemLabel
- { one: string; other: string }
- default { one: "order", other: "orders" }
- What one result is called, in counts and announcements.
- height
- number
- default 520
- Height of the surface in px; the facets and the results scroll inside it.
- disabled
- boolean
- default false
- Nothing can be picked, cleared or saved; the surface dims.
- className
- string
- default —
- Classes for the outer surface.
| Prop | Type | Default | Description |
|---|---|---|---|
| facets | TallyFacet[] | defaultTallyFacets | The facets in order: { id, label, values?, badge? }. Values omitted are read from the rows, largest first; a badge facet shows as a pill on each result. |
| rows | TallyRow[] | defaultTallyRows | The records counted and listed: { id, title, code, amount, at, facets } where facets maps facet id to value id. |
| value / defaultValue | Record<string, string[]> | — / {} | Picked value ids by facet id, controlled or initial. |
| onValueChange | (selection) => void | — | Fires with the whole selection after every pick, clear or chip removal. |
| onChange | (change: { facetId, valueId, selected, selection }) => void | — | Fires once per decision with the value that changed and whether it is now picked. |
| onClear | () => void | — | Fires when Clear empties the selection. |
| onSaveView | (selection) => void | — | Fires from Save view with the current selection; the button only shows when this is passed. |
| status | "ready" | "loading" | "error" | "ready" | Loading stripes every bar and skeletons the results; error keeps the last counts, hatched, under a Retry banner. |
| onRetry | () => void | — | Retry was pressed after a failed refresh. |
| density | "compact" | "cozy" | "roomy" | "cozy" | Facet row height (26, 30 or 36px) and type size. |
| barStyle | "bar" | "line" | "fill" | "bar" | How a count is drawn: a slim bar under the label, a hairline rule, or a wash behind the whole row. |
| accent | string | "var(--accent-bright)" | Checks, bars, chips and the live count, any CSS colour. |
| sinkEmpty | boolean | true | Values that would return nothing sink to the foot of their group. |
| now | number | defaultTallyNow | Today in ms, for the "3d ago" on each result; never read from the clock during render. |
| formatAmount | (amount: number) => string | whole US dollars | Formats each result's amount. |
| title | string | "Orders" | The heading over the results and the surface's accessible name. |
| itemLabel | { one: string; other: string } | { one: "order", other: "orders" } | What one result is called, in counts and announcements. |
| height | number | 520 | Height of the surface in px; the facets and the results scroll inside it. |
| disabled | boolean | false | Nothing can be picked, cleared or saved; the surface dims. |
| className | string | — | Classes for the outer surface. |