KQ-1684 · PLATE/PICKER
Plate Picker
An asset picker dialog for choosing several images in order: picking a plate lands a numbered badge on it on springs.recoil while a copy of the picture flies from the tile into its slot in the tray on springs.glide, and dropping one makes every later badge roll its digit down on springs.snap while the tray closes the gap on glide. The tray reorders by drag — the thumb follows the finger 1:1, neighbours glide aside, the drop snaps home with the release velocity — or by Shift with an arrow, and tabs switch between the library (folders over plates drawn in code), uploads whose veil lifts with their progress, and links. The grid is a multiselectable role="grid" with one roving cell; Space or Enter picks.
Choose plates
Fieldline · Issue 14, Tidelines
Arrow keys move between plates, Home and End to the ends of a row. Space or Enter chooses a plate or puts it back.
Left and Right move between chosen plates. Shift with Left or Right moves the plate, Delete removes it.
3 chosen · salt flat at dusk first
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/plate-picker
Props
- assets
- PlateAsset[]
- default defaultPlateAssets
- Every plate: { id, name, folder, width?, height?, src?, art?, status?, progress?, error? }; without src a plate is drawn in code from art (or its id).
- folders
- PlateFolder[]
- default defaultPlateFolders
- The folders, in order: { id, name }; "All" is added in front.
- folder / defaultFolder
- string
- default — / "all"
- The folder shown, controlled or initial ("all" for every folder).
- onFolderChange
- (folder: string) => void
- default —
- Fires from the chip or key that changed the folder.
- selected / defaultSelected
- string[]
- default — / []
- The chosen plate ids in insert order, controlled or initial.
- onSelect
- (ids: string[]) => void
- default —
- Fires from every pick, removal and reorder with the ids in order.
- max
- number
- default 6
- How many plates may be chosen; past it the unpicked plates dim and say why.
- onUpload
- (file, report) => Promise<void | Partial<PlateAsset>>
- default simulated
- Uploads one file; call report(0–1) as it goes; resolve (optionally with fields to merge) or reject with the reason the tile shows.
- onLink
- (url: string) => void | Promise<void>
- default —
- An https address was added from the Link tab; reject to refuse it with a reason. Nothing is fetched.
- onInsert
- (assets: PlateAsset[]) => void
- default —
- Insert was pressed, with the chosen plates in order.
- onCancel
- () => void
- default —
- Cancel, the close button or Escape.
- tab / defaultTab
- "library" | "upload" | "link"
- default — / "library"
- The tab shown, controlled or initial.
- onTabChange
- (tab) => void
- default —
- Fires from the tab press or key that changed the tab.
- columns
- number
- default 4
- Grid columns at full width; containers under 30rem always use two.
- badgeStyle
- "disc" | "tab" | "frame"
- default "disc"
- The pick number as a disc on the corner, a folded corner tab, or a thick frame with the number in its corner.
- accent
- string
- default "var(--accent-bright)"
- Badges, the selection ring, the tray's numbers and the tab indicator; filled badges take its hue at a fixed lightness.
- title
- string
- default "Choose plates"
- The dialog's heading and accessible name.
- subtitle
- string
- default "Fieldline · Issue 14, Tidelines"
- A quiet line under the heading.
- itemLabel
- { one: string; other: string }
- default { one: "plate", other: "plates" }
- What one asset is called, in counts and on the Insert button.
- height
- number
- default 540
- Dialog height in px; the grid scrolls inside it.
- status
- "ready" | "loading" | "error"
- default "ready"
- Loading shows breathing placeholder tiles; error offers Try again.
- onReload
- () => void
- default —
- Try again was pressed after the library failed to load.
- modal
- boolean
- default false
- Trap Tab inside the dialog and set aria-modal; pass it when you render it in an overlay.
- disabled
- boolean
- default false
- Nothing can be picked, uploaded or inserted.
- className
- string
- default —
- Classes for the dialog panel.
| Prop | Type | Default | Description |
|---|---|---|---|
| assets | PlateAsset[] | defaultPlateAssets | Every plate: { id, name, folder, width?, height?, src?, art?, status?, progress?, error? }; without src a plate is drawn in code from art (or its id). |
| folders | PlateFolder[] | defaultPlateFolders | The folders, in order: { id, name }; "All" is added in front. |
| folder / defaultFolder | string | — / "all" | The folder shown, controlled or initial ("all" for every folder). |
| onFolderChange | (folder: string) => void | — | Fires from the chip or key that changed the folder. |
| selected / defaultSelected | string[] | — / [] | The chosen plate ids in insert order, controlled or initial. |
| onSelect | (ids: string[]) => void | — | Fires from every pick, removal and reorder with the ids in order. |
| max | number | 6 | How many plates may be chosen; past it the unpicked plates dim and say why. |
| onUpload | (file, report) => Promise<void | Partial<PlateAsset>> | simulated | Uploads one file; call report(0–1) as it goes; resolve (optionally with fields to merge) or reject with the reason the tile shows. |
| onLink | (url: string) => void | Promise<void> | — | An https address was added from the Link tab; reject to refuse it with a reason. Nothing is fetched. |
| onInsert | (assets: PlateAsset[]) => void | — | Insert was pressed, with the chosen plates in order. |
| onCancel | () => void | — | Cancel, the close button or Escape. |
| tab / defaultTab | "library" | "upload" | "link" | — / "library" | The tab shown, controlled or initial. |
| onTabChange | (tab) => void | — | Fires from the tab press or key that changed the tab. |
| columns | number | 4 | Grid columns at full width; containers under 30rem always use two. |
| badgeStyle | "disc" | "tab" | "frame" | "disc" | The pick number as a disc on the corner, a folded corner tab, or a thick frame with the number in its corner. |
| accent | string | "var(--accent-bright)" | Badges, the selection ring, the tray's numbers and the tab indicator; filled badges take its hue at a fixed lightness. |
| title | string | "Choose plates" | The dialog's heading and accessible name. |
| subtitle | string | "Fieldline · Issue 14, Tidelines" | A quiet line under the heading. |
| itemLabel | { one: string; other: string } | { one: "plate", other: "plates" } | What one asset is called, in counts and on the Insert button. |
| height | number | 540 | Dialog height in px; the grid scrolls inside it. |
| status | "ready" | "loading" | "error" | "ready" | Loading shows breathing placeholder tiles; error offers Try again. |
| onReload | () => void | — | Try again was pressed after the library failed to load. |
| modal | boolean | false | Trap Tab inside the dialog and set aria-modal; pass it when you render it in an overlay. |
| disabled | boolean | false | Nothing can be picked, uploaded or inserted. |
| className | string | — | Classes for the dialog panel. |