Track List
A playlist of tracks with numbers, artists, durations, and a live meter on the one that's playing.
betaTrackList.tsx
Examples
Basic
Pick a track to move the meter.
track-list/basic.tsx
"use client";
import { useState } from "react";
import { TrackList } from "@streamuilib/react";
import { useDemoTracks } from "@/lib/audio/demo-tracks";
export default function TrackListBasic() {
const [index, setIndex] = useState(0);
return (
<TrackList
tracks={useDemoTracks()}
index={index}
playing
onSelect={setIndex}
className="w-full max-w-sm border-2 border-ink shadow-brutal"
/>
);
}Installation
bun add @streamuilib/reactusage.tsx
import { TrackList } from "@streamuilib/react";
<TrackList tracks={tracks} index={current} playing onSelect={setCurrent} />API
| Prop | Type | Default | Description |
|---|---|---|---|
| tracks | Track[] | — | Tracks in order. Required. |
| index | number | — | The current track. |
| playing | boolean | false | Animates the current track's meter. |
| onSelect | (index: number) => void | — | Called when a track is chosen. |
| ...props | ComponentProps<"ol"> | — | Native list attributes. |
Accessibility
- An ordered list of buttons, so screen readers announce the count and position.
- The current track has aria-current and a hidden "Now playing" prefix; the meter itself is decorative.
Source
packages/react/src/TrackList.tsx
"use client";
import type { ComponentProps } from "react";
import { cn } from "./cn";
import { Equalizer } from "./Equalizer";
import { formatTime, type Track } from "./music";
export type TrackListProps = Omit<ComponentProps<"ol">, "onSelect"> & {
tracks: Track[];
/** The current track. */
index?: number;
/** Whether the current track is playing (animates its meter). */
playing?: boolean;
onSelect?: (index: number) => void;
};
/** A playlist. The current track shows a live meter and is marked aria-current. */
export function TrackList({ tracks, index, playing = false, onSelect, className, ...props }: TrackListProps) {
return (
<ol aria-label="Playlist" className={cn("divide-y-2 divide-ink bg-paper-raised", className)} {...props}>
{tracks.map((t, i) => {
const current = i === index;
return (
<li key={t.id ?? `${i}-${t.title}`}>
<button
type="button"
aria-current={current || undefined}
onClick={() => onSelect?.(i)}
className={cn(
"flex w-full cursor-pointer items-center gap-3 px-4 py-3 text-left transition-colors duration-150 hover:bg-lime-soft",
current && "bg-lime-soft",
)}
>
<span className="grid w-5 shrink-0 place-items-center font-mono text-xs text-ink-muted tabular-nums">
{current ? <Equalizer playing={playing} bars={3} className="h-3.5" /> : i + 1}
</span>
<span className="flex min-w-0 flex-1 flex-col">
<span className="truncate font-semibold">
{current && <span className="sr-only">{playing ? "Now playing: " : "Selected: "}</span>}
{t.title}
</span>
{t.artist && <span className="truncate text-xs text-ink-muted">{t.artist}</span>}
</span>
{t.duration != null && (
<span className="shrink-0 font-mono text-xs text-ink-muted tabular-nums">{formatTime(t.duration)}</span>
)}
</button>
</li>
);
})}
</ol>
);
}