TimelineClip
Individual clip rendering - layout, thumbnail strips, selection, provisionals, and customization
TimelineClip renders a single clip as an absolutely positioned element inside a TimelineTrack. It handles:
- Layout: positions itself from
timelineStartand sizes fromtimelineEnd - timelineStartat the currentppf - Selection state: electric-cyan selection ring from
data-selected - Thumbnail strip: renders thumbnail images inside the clip if a
thumbnailProvideris present - Provisional state: renders ghost clips during drag from
ProvisionalState - Trim handles: start and end edge handles for resize gestures
Live Example
Usage
import { TimelineClip } from '@timelinx/ui';
import { createClip, toFrame } from '@timelinx/core';
const clip = createClip({
id: 'clip-1',
assetId: 'asset-interview',
trackId: 'v1',
timelineStart: toFrame(0),
timelineEnd: toFrame(600),
mediaIn: toFrame(0),
mediaOut: toFrame(600),
});
<TimelineClip
clip={clip}
ppf={6}
scrollLeft={0}
isSelected={false}
/>Props
Prop
Type
Key Props
| Prop | Type | Notes |
|---|---|---|
clip | Clip | The clip data object. |
ppf | number | Pixels per frame. Determines the clip's pixel width and position. |
scrollLeft | number | Current horizontal scroll offset of the track container. |
isSelected | boolean | Adds the selection ring when true. |
isGhost | boolean | Renders as a translucent ghost during drag preview. Default false. |
onPointerDown | (e: PointerEvent) => void | Forward to the tool router. |
thumbnails | WaveformData | ThumbnailData | null | Pre-extracted media preview data. |
Clip Positioning
TimelineClip is absolutely positioned inside the track container:
left: clip.timelineStart × ppf - scrollLeft
width: (clip.timelineEnd - clip.timelineStart) × ppfIt never controls its own top/height - those come from the TimelineTrack container.
Ghost Clips
During a drag gesture, the SelectionTool returns provisional state from onPointerMove. TimelineTrack passes isGhost={true} to the provisionally-positioned clip:
{provisionalClips.map((p) => (
<TimelineClip
key={`ghost-${p.clipId}`}
clip={mergedClip}
isGhost={true}
ppf={ppf}
scrollLeft={scrollLeft}
isSelected={false}
/>
))}Ghost clips render with reduced opacity and the amber accent outline.
Waveform Display
For audio clips, pass WaveformData to render the waveform inside the clip body:
<TimelineClip
clip={audioClip}
ppf={ppf}
scrollLeft={scrollLeft}
isSelected={false}
thumbnails={{ type: 'waveform', data: waveformData }}
/>Custom Clip Rendering
Replace TimelineClip entirely with a custom component by rendering inside TimelineTrack with renderClip:
<TimelineTrack
trackId="v1"
renderClip={(clip, props) => (
<MyCustomClip key={clip.id} clip={clip} {...props} />
)}
/>props includes ppf, scrollLeft, isSelected, and pointer handlers from the tool router.
CSS Classes and Data Attributes
| Class | Element |
|---|---|
.tl-clip | Root element |
.tl-clip-label | Name label |
.tl-clip-thumbnail | Thumbnail/waveform strip |
.tl-trim-handle | Start/end trim handles |
| Attribute | Values |
|---|---|
data-clip-id | Clip ID string |
data-selected | "true" / "false" |
data-enabled | "true" / "false" |
data-ghost | "true" when provisional |
data-track-type | "video" / "audio" / etc. |
/* Hover effect on clips */
.tl-clip:hover .tl-trim-handle {
opacity: 1;
}
/* Custom color for a specific clip */
.tl-clip[data-clip-id="clip-featured"] {
background: var(--accent-subtle);
border-color: var(--accent-border);
}Standalone component
TimelineClip is a pure presentational component - it does not use React context or hooks. All data is passed via props. You can render it outside a TimelineProvider for static previews or storybook stories.