how far a video has played: a track, what has loaded, the played part
npx shadcn add @ag/media-lineAlready installed? Preview an update
npx shadcn add @ag/media-line --dry-runYour installed source stays unchanged until you update it. How updates work
Latest changes · 2026-10-11
- New: the one drawing of how far a video or a clip has played: a track, what of it has loaded lighter on it, and the played part solid from the start to where it is now, its edge the position. It draws in the text's colour (white over a picture, a player's accent); moments or chapters go in as children, placed with
lineShare;styleplaces a line (one chapter of a longer bar). The gallery's cells draw their strip with it, andplayer's bar each of its chapters, so the two say progress the same way.playernow pulls inmedia-line.
the demo above, verbatim
"use client"
import { Sample } from "@/app/samples"
import { MediaLine } from "@/registry/base-nova/ui/media-line"
// #region states
// A thirty-second file at four moments of its life: nothing loaded yet, a third of it
// loaded and nothing played, playing with the next stretch loaded ahead, and played
// through. The played part is solid to where it is now; what has loaded is lighter
// ahead of it.
const STATES = [
{ name: "not loaded", at: null, loaded: [] },
{ name: "loading", at: 0, loaded: [{ from: 0, to: 10_000 }] },
{ name: "playing", at: 12_000, loaded: [{ from: 0, to: 21_000 }] },
{ name: "played through", at: 30_000, loaded: [{ from: 0, to: 30_000 }] },
]
// #endregion
export default function Demo() {
return (
<div className="space-y-12">
<Sample
name="states"
glance
with="states"
title="what has loaded, what has played"
>
<div className="grid gap-6 sm:grid-cols-2">
{STATES.map((s) => (
<div key={s.name} className="space-y-2">
<MediaLine
duration={30_000}
at={s.at}
loaded={s.loaded}
className="h-1.5 rounded-full text-foreground"
/>
<p className="text-xs text-muted-foreground">{s.name}</p>
</div>
))}
</div>
</Sample>
<Sample
name="over-a-picture"
title="over a picture"
description="in white on a dark ground at the picture's foot, as a gallery cell draws it"
>
<div className="relative aspect-video max-w-md overflow-hidden rounded-xl bg-[url(/glass-valley.jpg)] bg-cover">
<div className="absolute inset-x-0 bottom-0 h-1.5 bg-black/40">
<MediaLine
duration={30_000}
at={12_000}
loaded={[{ from: 0, to: 21_000 }]}
className="size-full text-white"
/>
</div>
</div>
</Sample>
</div>
)
}the source, every file an install writes
import type * as React from "react"
import { cn } from "@/lib/utils"
import type { Span } from "@/registry/base-nova/lib/clip"
// A MEDIUM'S LINE, the one drawing of how far a video or a clip has played: a track,
// what of it has loaded lighter on it, and the played part solid from the start to
// where it is now, its edge the position (no mark of its own: a fill says how far and
// where at once). Every player and every preview draws its progress with this, so a
// line means the same thing everywhere. It draws in the text's colour (`text-*` on it
// or above it: white over a picture, a player's accent), the track and the loaded
// part at fixed shares of it. Size it with `className` (height, rounding); what a
// consumer marks along it (moments, chapters) goes in as children, over the played
// part, each placed with `lineShare`.
/** Where `ms` falls along a line of `duration` ms, as a CSS percentage, held to the
* line's ends. */
export const lineShare = (ms: number, duration: number) =>
`${(Math.min(Math.max(ms, 0), duration) / duration) * 100}%`
export function MediaLine({
duration,
at,
loaded = [],
className,
style,
children,
}: {
/** The medium's length, ms; more than zero. */
duration: number
/** Where it is now, ms, held to the line's ends; null before it has a position
* (nothing played). */
at: number | null
/** What of it has loaded (a video's buffered ranges), ms. */
loaded?: readonly Span[]
className?: string
/** Where the line is placed (one chapter of a longer bar). */
style?: React.CSSProperties
/** Marks along it, over the played part. */
children?: React.ReactNode
}) {
if (!(duration > 0)) throw new Error(`media-line: a duration of ${duration}`)
return (
<div
data-slot="media-line"
className={cn("relative overflow-hidden bg-current/20", className)}
style={style}
>
{loaded.map((s) => (
<div
key={s.from}
data-slot="media-line-loaded"
className="absolute inset-y-0 left-(--media-line-from) w-(--media-line-span) bg-current/25"
style={
{
"--media-line-from": lineShare(s.from, duration),
"--media-line-span": lineShare(s.to - s.from, duration),
} as React.CSSProperties
}
/>
))}
{at !== null && (
<div
data-slot="media-line-played"
className="absolute inset-y-0 left-0 w-(--media-line-played) bg-current"
style={
{
"--media-line-played": lineShare(at, duration),
} as React.CSSProperties
}
/>
)}
{children}
</div>
)
}