Files
llink/js/src/features/particles/playback-page-indicator.tsx
T

107 lines
3.1 KiB
TypeScript

import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
import type { HumanPresence } from "@/hooks/use-presence-positions";
const MAX_VISIBLE_AVATARS = 3;
interface PlaybackPageIndicatorProps {
total: number;
current: number;
progress: number;
onGoTo: (index: number) => void;
presenceBySegment?: Map<number, HumanPresence[]>;
/** Render only avatars or only tracks. Omit to render both. */
layer?: "avatars" | "tracks";
}
export function PlaybackPageIndicator({
total,
current,
progress,
onGoTo,
presenceBySegment,
layer,
}: PlaybackPageIndicatorProps) {
if (total === 0) return null;
const showAvatars = layer !== "tracks";
const showTracks = layer !== "avatars";
return (
<div className="flex w-full items-end gap-px leading-none">
{Array.from({ length: total }, (_, i) => {
const presence = presenceBySegment?.get(i);
return (
<div key={i} className="flex flex-1 flex-col items-stretch">
{showAvatars && presence && presence.length > 0 && (
<SegmentPresenceAvatars presence={presence} />
)}
{showTracks && (
<button
onClick={(e) => {
e.stopPropagation();
onGoTo(i);
}}
className="group relative block h-3 w-full"
>
{/* Dim track */}
<div className="absolute inset-x-0 bottom-0 h-[3px] bg-white/30 transition-all group-hover:h-1.5" />
{/* Fill */}
<div
className="absolute left-0 bottom-0 h-[3px] bg-white/90 transition-all group-hover:h-1.5"
style={{
width:
i < current
? "100%"
: i === current
? `${progress * 100}%`
: "0%",
transition: i === current ? "width 300ms linear" : "none",
}}
/>
</button>
)}
</div>
);
})}
</div>
);
}
function SegmentPresenceAvatars({
presence,
}: {
presence: HumanPresence[];
}) {
const visible = presence.slice(0, MAX_VISIBLE_AVATARS);
const overflow = presence.length - MAX_VISIBLE_AVATARS;
return (
<div className="flex items-center justify-center -space-x-1.5 pb-0.5">
{visible.map((human) => (
<Tooltip key={human.humanId}>
<TooltipTrigger asChild>
<Avatar size="xs" className="ring-1 ring-black/50">
<AvatarFallback>
{human.emailPrefix.slice(0, 2).toUpperCase()}
</AvatarFallback>
</Avatar>
</TooltipTrigger>
<TooltipContent side="top" className="text-xs">
{human.email}
</TooltipContent>
</Tooltip>
))}
{overflow > 0 && (
<span className="text-[10px] text-white/70 pl-1">
+{overflow}
</span>
)}
</div>
);
}