Timelinx

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 timelineStart and sizes from timelineEnd - timelineStart at the current ppf
  • Selection state: electric-cyan selection ring from data-selected
  • Thumbnail strip: renders thumbnail images inside the clip if a thumbnailProvider is present
  • Provisional state: renders ghost clips during drag from ProvisionalState
  • Trim handles: start and end edge handles for resize gestures

Live Example

Loading...

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

PropTypeNotes
clipClipThe clip data object.
ppfnumberPixels per frame. Determines the clip's pixel width and position.
scrollLeftnumberCurrent horizontal scroll offset of the track container.
isSelectedbooleanAdds the selection ring when true.
isGhostbooleanRenders as a translucent ghost during drag preview. Default false.
onPointerDown(e: PointerEvent) => voidForward to the tool router.
thumbnailsWaveformData | ThumbnailData | nullPre-extracted media preview data.

Clip Positioning

TimelineClip is absolutely positioned inside the track container:

left:  clip.timelineStart × ppf - scrollLeft
width: (clip.timelineEnd - clip.timelineStart) × ppf

It 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

ClassElement
.tl-clipRoot element
.tl-clip-labelName label
.tl-clip-thumbnailThumbnail/waveform strip
.tl-trim-handleStart/end trim handles
AttributeValues
data-clip-idClip 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.

On this page