KQ-1676 · MIXED/VALUES
Mixed Values
A bulk edit form over a selection of records: a field whose values differ shows them as a chip of variants with counts — a deck that fans out on springs.snap when you reach the field, a share bar, or a row — and typing or choosing a new value collapses the chips into the first on springs.glide while the new-value chip pops in on snap. Committing the value sends a ripple down the records list beside the form, each record the change touches lighting in a cascade with its diff, while records that already hold the value stay still and fields left alone keep every original. A preview names how many records each change touches, Apply reports only the changed fields and turns into a bar that glides with each record's result while rows tick or cross, and failures are listed with a Retry failed; Tab and Enter work throughout and Escape takes a field back.
Edit products
14 products · no changes yet
no changes · 14 of 14 products selected
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/mixed-values
Props
- records
- MixedValuesRecord[]
- default defaultMixedValuesRecords
- The records being edited: { id, name, values } with values as text (money as a decimal).
- fields
- MixedValuesField[]
- default defaultMixedValuesFields
- The form: { id, label, kind?, options?, suffix?, validate? }.
- selected / defaultSelected
- string[]
- default — / every record
- The selection, controlled or initial.
- onSelectedChange
- (ids) => void
- default —
- Fires from the checkbox that changed the selection.
- onChange
- (changes) => void
- default —
- Fires from the keystroke or choice that changed the pending edit, with only the changed fields.
- onApply
- (changes, ids, report) => void | Promise
- default —
- Apply was pressed with the changed fields and the records they touch; call report(id, ok, reason?) per record, return a promise to show progress until it settles.
- onCancel
- (changes) => void
- default —
- Cancel was pressed, with the changes being dropped.
- chipStyle
- "stack" | "bar" | "inline"
- default "stack"
- How differing values show: a deck that fans on focus, a share bar, or every value in a row.
- density
- "compact" | "regular" | "roomy"
- default "regular"
- Field and row heights.
- accent
- string
- default "var(--accent-bright)"
- The new-value chip, the ripple, Apply and progress — any CSS colour; buttons take its hue at a fixed lightness so white text reads in both themes.
- itemLabel
- { one: string; other: string }
- default { one: "product", other: "products" }
- What one record is called in counts and announcements.
- heading
- string
- default "Edit products"
- The panel's heading and accessible name.
- currency
- string
- default "USD"
- The currency for money fields.
- locale
- string
- default "en-US"
- The locale for money fields.
- status
- "ready" | "loading" | "error"
- default "ready"
- Loading shows skeleton fields; error shows Try again.
- onRetry
- () => void
- default —
- Try again was pressed after the records failed to load.
- height
- number
- default 540
- The panel's height in px; the form and the list scroll inside it.
- disabled
- boolean
- default false
- Nothing can be edited or applied.
- className
- string
- default —
- Classes for the panel.
| Prop | Type | Default | Description |
|---|---|---|---|
| records | MixedValuesRecord[] | defaultMixedValuesRecords | The records being edited: { id, name, values } with values as text (money as a decimal). |
| fields | MixedValuesField[] | defaultMixedValuesFields | The form: { id, label, kind?, options?, suffix?, validate? }. |
| selected / defaultSelected | string[] | — / every record | The selection, controlled or initial. |
| onSelectedChange | (ids) => void | — | Fires from the checkbox that changed the selection. |
| onChange | (changes) => void | — | Fires from the keystroke or choice that changed the pending edit, with only the changed fields. |
| onApply | (changes, ids, report) => void | Promise | — | Apply was pressed with the changed fields and the records they touch; call report(id, ok, reason?) per record, return a promise to show progress until it settles. |
| onCancel | (changes) => void | — | Cancel was pressed, with the changes being dropped. |
| chipStyle | "stack" | "bar" | "inline" | "stack" | How differing values show: a deck that fans on focus, a share bar, or every value in a row. |
| density | "compact" | "regular" | "roomy" | "regular" | Field and row heights. |
| accent | string | "var(--accent-bright)" | The new-value chip, the ripple, Apply and progress — any CSS colour; buttons take its hue at a fixed lightness so white text reads in both themes. |
| itemLabel | { one: string; other: string } | { one: "product", other: "products" } | What one record is called in counts and announcements. |
| heading | string | "Edit products" | The panel's heading and accessible name. |
| currency | string | "USD" | The currency for money fields. |
| locale | string | "en-US" | The locale for money fields. |
| status | "ready" | "loading" | "error" | "ready" | Loading shows skeleton fields; error shows Try again. |
| onRetry | () => void | — | Try again was pressed after the records failed to load. |
| height | number | 540 | The panel's height in px; the form and the list scroll inside it. |
| disabled | boolean | false | Nothing can be edited or applied. |
| className | string | — | Classes for the panel. |