KQ-1733 · PIN/CRITIQUE
Pin Critique
A design-critique board: numbered pins on an artwork, each starting a thread in the column beside it. Clicking the artwork or pressing P drops a pin at the cursor — it falls 16px and lands on springs.recoil with two small bounces while its ground shadow grows and its numeral rolls up into the head on springs.snap just behind the touch-down — and the composer opens for what you see. Hovering or focusing a thread draws a ring around its pin on springs.glide while the pin lifts on snap; pins drag 1:1 and settle on snap with the throw, resolved pins fade to a check, and below a 40rem container the threads open as a sheet that rises on glide, follows a downward drag and pans the artwork so the pin stays in view. Pins are one roving tab stop: arrows move between them, Alt with an arrow nudges one, Shift with an arrow moves the drop cursor and Escape closes what is open.
Booking screen · v4
4 open, 1 resolved
P drops a pin at the cursor. Shift with an arrow moves the cursor. Arrow keys move between pins, Alt with an arrow moves the focused pin, Enter opens its thread.
5 pins · 4 open · 1 resolved
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/pin-critique
Props
- pins / defaultPins
- CritiquePin[]
- default — / defaultCritiquePins
- Pins as { id, x, y }, positions as 0–1 fractions of the artwork; a pin's number is its place in the list.
- onPinsChange
- (pins) => void
- default —
- Fires with every pin after a drop is posted or a pin is dragged or nudged.
- threads / defaultThreads
- CritiqueThread[]
- default — / defaultCritiqueThreads
- Threads as { id, pinId, resolved?, comments: { id, author, at, text }[] }.
- onThreadsChange
- (threads) => void
- default —
- Fires with every thread after a post, a reply or a resolve.
- selected / defaultSelected / onSelectedChange
- string | null
- default — / null
- The open thread's id, controlled or initial, and the callback from the pin, card or key that changed it.
- onPin
- (pin, thread) => void
- default —
- A dropped pin was posted with its first comment; both are already in the lists.
- onReply
- (threadId, comment) => void
- default —
- A reply was posted on a thread.
- onResolve
- (threadId, resolved) => void
- default —
- A thread was resolved (true) or reopened (false).
- artwork
- ReactNode
- default a drawn Waylight booking screen
- What is being critiqued: an image or drawn design that fills its box.
- artworkLabel
- string
- default "Waylight booking screen, version 4"
- The artwork's accessible name; it also names the canvas group.
- aspect
- number
- default 0.75
- The artwork's width over its height; the canvas fits it inside the box.
- pinStyle
- "drop" | "disc" | "tab"
- default "drop"
- A teardrop whose point is the spot, a disc centred on it, or a speech tab whose corner is it (flipped near the right edge).
- density
- "compact" | "regular" | "roomy"
- default "regular"
- Pin size (22/26/30px), thread type and card padding.
- accent
- string
- default "var(--accent-bright)"
- Pins, rings, the open card's edge and the post buttons — any CSS colour; pins take its hue at a fixed lightness so white numerals read in both themes.
- showResolved / defaultShowResolved / onShowResolvedChange
- boolean
- default — / true
- Whether resolved pins and threads show, controlled or initial, and the Resolved toggle's callback.
- author
- string
- default "Noor Haddad"
- The name new comments are posted under.
- now
- number | Date
- default defaultCritiqueNow
- The moment relative times count from, so server and browser render the same text.
- title
- string
- default "Booking screen · v4"
- The board's heading and accessible name.
- status
- "ready" | "loading" | "error"
- default "ready"
- Loading shows a skeleton artwork and threads; error shows Try again.
- onRetry
- () => void
- default —
- Try again was pressed after the board failed to load.
- height
- number
- default 540
- The board's height in px; the canvas fits inside it and the threads scroll.
- sound
- boolean
- default false
- Play the pop of a pin landing (and a softer one on resolve and on a dragged pin's landing).
- disabled
- boolean
- default false
- Read only: nothing can be dropped, moved, posted or resolved.
- className
- string
- default —
- Classes for the board.
| Prop | Type | Default | Description |
|---|---|---|---|
| pins / defaultPins | CritiquePin[] | — / defaultCritiquePins | Pins as { id, x, y }, positions as 0–1 fractions of the artwork; a pin's number is its place in the list. |
| onPinsChange | (pins) => void | — | Fires with every pin after a drop is posted or a pin is dragged or nudged. |
| threads / defaultThreads | CritiqueThread[] | — / defaultCritiqueThreads | Threads as { id, pinId, resolved?, comments: { id, author, at, text }[] }. |
| onThreadsChange | (threads) => void | — | Fires with every thread after a post, a reply or a resolve. |
| selected / defaultSelected / onSelectedChange | string | null | — / null | The open thread's id, controlled or initial, and the callback from the pin, card or key that changed it. |
| onPin | (pin, thread) => void | — | A dropped pin was posted with its first comment; both are already in the lists. |
| onReply | (threadId, comment) => void | — | A reply was posted on a thread. |
| onResolve | (threadId, resolved) => void | — | A thread was resolved (true) or reopened (false). |
| artwork | ReactNode | a drawn Waylight booking screen | What is being critiqued: an image or drawn design that fills its box. |
| artworkLabel | string | "Waylight booking screen, version 4" | The artwork's accessible name; it also names the canvas group. |
| aspect | number | 0.75 | The artwork's width over its height; the canvas fits it inside the box. |
| pinStyle | "drop" | "disc" | "tab" | "drop" | A teardrop whose point is the spot, a disc centred on it, or a speech tab whose corner is it (flipped near the right edge). |
| density | "compact" | "regular" | "roomy" | "regular" | Pin size (22/26/30px), thread type and card padding. |
| accent | string | "var(--accent-bright)" | Pins, rings, the open card's edge and the post buttons — any CSS colour; pins take its hue at a fixed lightness so white numerals read in both themes. |
| showResolved / defaultShowResolved / onShowResolvedChange | boolean | — / true | Whether resolved pins and threads show, controlled or initial, and the Resolved toggle's callback. |
| author | string | "Noor Haddad" | The name new comments are posted under. |
| now | number | Date | defaultCritiqueNow | The moment relative times count from, so server and browser render the same text. |
| title | string | "Booking screen · v4" | The board's heading and accessible name. |
| status | "ready" | "loading" | "error" | "ready" | Loading shows a skeleton artwork and threads; error shows Try again. |
| onRetry | () => void | — | Try again was pressed after the board failed to load. |
| height | number | 540 | The board's height in px; the canvas fits inside it and the threads scroll. |
| sound | boolean | false | Play the pop of a pin landing (and a softer one on resolve and on a dragged pin's landing). |
| disabled | boolean | false | Read only: nothing can be dropped, moved, posted or resolved. |
| className | string | — | Classes for the board. |