KQ-1734 · TIMECODE/NOTES
Timecode Notes
Review notes pinned to moments in a clip: the clip plays in a procedural frame (or your own `renderFrame`), every note is a marker on the timeline, and the notes list sits beside the player, under it below a 40rem container. As the playhead passes a marker it pulses on springs.snap and its note rises into the foot of the frame on springs.recoil, holds, and leaves on the exit ease; choosing a note, marker or cluster glides the playhead there on springs.glide while the frame cuts to its scene, and markers closer than 14px merge into counted bubbles. Space plays, M adds a note at the playhead, and the timeline is a slider whose arrows, Page keys, Home, End and [ ] brackets move by seconds and between notes.
Waylight onboarding cut · v3
1:24 · 6 notes · 5 open
Space plays and pauses, M adds a note at the playhead. Arrows step a second, Shift with an arrow five, Page Up and Page Down ten. Left and right bracket move to the previous and next note.
ready · 1:24 · 6 notes · 5 open
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/timecode-notes
Props
- media
- TimecodeMedia
- default defaultTimecodeMedia
- The clip: { title, kind: "video" | "audio", duration, scenes?: { at, label }[], peaks?: number[] }.
- notes / defaultNotes
- TimecodeNote[]
- default — / defaultTimecodeNotes
- Notes as { id, at (seconds), author, text, createdAt, resolved? }, controlled or initial.
- onNotesChange
- (notes) => void
- default —
- Fires with every note after an add or a resolve.
- time / defaultTime
- number
- default — / 0
- The playhead in seconds, controlled or initial; a controlled change of more than a second seeks.
- onTimeChange
- (seconds) => void
- default —
- Fires on every seek and once a second while playing, with whole seconds.
- playing / defaultPlaying / onPlayingChange
- boolean
- default — / false
- Playback, controlled or initial, and the callback from the button, key or end of clip that changed it.
- onAdd
- (note) => void
- default —
- A note was added at the playhead; it is already in the list.
- onSeek
- (time, noteId?) => void
- default —
- The visitor moved the playhead; `noteId` when a note, marker or cluster was chosen.
- onResolve
- (noteId, resolved) => void
- default —
- A note was resolved (true) or reopened (false).
- renderFrame
- (time, media) => ReactNode
- default procedural frame per scene
- Draw the frame yourself; called with whole seconds and on every seek.
- markerStyle
- "tick" | "dot" | "avatar"
- default "tick"
- A hairline tick with a cap above the track, a dot on the track, or the author's initial on a stem.
- density
- "compact" | "regular" | "roomy"
- default "regular"
- List type and padding, and the size of the player's controls.
- accent
- string
- default "var(--accent-bright)"
- Playhead, fill, markers and the current note's edge — any CSS colour; fills take its hue at a fixed lightness.
- holdMs
- number
- default 2600
- How long a passed note stays risen over the frame, in ms.
- author
- string
- default "Noor Haddad"
- The name new notes are written under.
- now
- number | Date
- default defaultTimecodeNow
- The moment relative times count from, so server and browser render the same text.
- title
- string
- default media.title
- The heading and the surface's accessible name.
- status
- "ready" | "loading" | "error"
- default "ready"
- Loading shows a skeleton frame and list; error shows Try again.
- onRetry
- () => void
- default —
- Try again was pressed after the clip failed to load.
- height
- number
- default 540
- The surface's height in px; the player and notes scroll inside it.
- disabled
- boolean
- default false
- Read only: the clip plays and notes can be read and chosen, nothing added or resolved.
- className
- string
- default —
- Classes for the surface.
| Prop | Type | Default | Description |
|---|---|---|---|
| media | TimecodeMedia | defaultTimecodeMedia | The clip: { title, kind: "video" | "audio", duration, scenes?: { at, label }[], peaks?: number[] }. |
| notes / defaultNotes | TimecodeNote[] | — / defaultTimecodeNotes | Notes as { id, at (seconds), author, text, createdAt, resolved? }, controlled or initial. |
| onNotesChange | (notes) => void | — | Fires with every note after an add or a resolve. |
| time / defaultTime | number | — / 0 | The playhead in seconds, controlled or initial; a controlled change of more than a second seeks. |
| onTimeChange | (seconds) => void | — | Fires on every seek and once a second while playing, with whole seconds. |
| playing / defaultPlaying / onPlayingChange | boolean | — / false | Playback, controlled or initial, and the callback from the button, key or end of clip that changed it. |
| onAdd | (note) => void | — | A note was added at the playhead; it is already in the list. |
| onSeek | (time, noteId?) => void | — | The visitor moved the playhead; `noteId` when a note, marker or cluster was chosen. |
| onResolve | (noteId, resolved) => void | — | A note was resolved (true) or reopened (false). |
| renderFrame | (time, media) => ReactNode | procedural frame per scene | Draw the frame yourself; called with whole seconds and on every seek. |
| markerStyle | "tick" | "dot" | "avatar" | "tick" | A hairline tick with a cap above the track, a dot on the track, or the author's initial on a stem. |
| density | "compact" | "regular" | "roomy" | "regular" | List type and padding, and the size of the player's controls. |
| accent | string | "var(--accent-bright)" | Playhead, fill, markers and the current note's edge — any CSS colour; fills take its hue at a fixed lightness. |
| holdMs | number | 2600 | How long a passed note stays risen over the frame, in ms. |
| author | string | "Noor Haddad" | The name new notes are written under. |
| now | number | Date | defaultTimecodeNow | The moment relative times count from, so server and browser render the same text. |
| title | string | media.title | The heading and the surface's accessible name. |
| status | "ready" | "loading" | "error" | "ready" | Loading shows a skeleton frame and list; error shows Try again. |
| onRetry | () => void | — | Try again was pressed after the clip failed to load. |
| height | number | 540 | The surface's height in px; the player and notes scroll inside it. |
| disabled | boolean | false | Read only: the clip plays and notes can be read and chosen, nothing added or resolved. |
| className | string | — | Classes for the surface. |