Files
llink/js/src/features/particles/playback-page-indicator.tsx
T
2026-04-01 11:02:48 -07:00

101 lines
2.9 KiB
TypeScript

import { cn } from "@/lib/utils";
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[]>;
}
export function PlaybackPageIndicator({
total,
current,
progress,
onGoTo,
presenceBySegment,
}: PlaybackPageIndicatorProps) {
if (total === 0) return null;
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">
{/* Presence avatars above the track */}
{presence && presence.length > 0 && (
<SegmentPresenceAvatars presence={presence} />
)}
<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>
);
}