import { useMemo, type CSSProperties } from "react";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "../ui/tooltip.js";
import {
type CollabUser,
dedupeCollabUsersByEmail,
emailToColor,
emailToName,
} from "./types.js";
export interface PresenceBarProps {
/** Active collaborators on this document. */
activeUsers: CollabUser[];
/** Whether the agent has a durable presence entry. */
agentPresent?: boolean;
/** Whether the agent is actively making edits right now. */
agentActive?: boolean;
/** Current user's email (to exclude from the list). */
currentUserEmail?: string;
/** Max visible avatars before "+N" overflow. Default: 5 */
maxVisible?: number;
/** Additional CSS classes. */
className?: string;
/**
* Called when an avatar is clicked. Receives the user being clicked
* (or null for the agent avatar). Use this to start/stop follow mode.
*/
onAvatarClick?: (user: CollabUser | null) => void;
/**
* The email of the user currently being followed. Highlighted with a
* blue ring to indicate active follow mode.
*/
followingEmail?: string | null;
}
const AVATAR_SIZE = 28;
const OVERLAP = -8;
const BORDER_WIDTH = 2;
const FONT_SIZE = 12;
const AGENT_COLOR = "#00B5FF";
const baseAvatarStyle: CSSProperties = {
width: AVATAR_SIZE,
height: AVATAR_SIZE,
borderRadius: "50%",
display: "flex",
alignItems: "center",
justifyContent: "center",
fontSize: FONT_SIZE,
fontWeight: 700,
color: "#fff",
border: `${BORDER_WIDTH}px solid #fff`,
flexShrink: 0,
position: "relative",
cursor: "default",
boxSizing: "border-box",
};
const containerStyle: React.CSSProperties = {
display: "flex",
alignItems: "center",
flexDirection: "row",
};
const pulseKeyframes = `
@keyframes _anPresencePulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.6; }
}
`;
let styleInjected = false;
function injectStyles() {
if (styleInjected || typeof document === "undefined") return;
const style = document.createElement("style");
style.textContent = pulseKeyframes;
document.head.appendChild(style);
styleInjected = true;
}
function UserAvatar({
user,
isFirst,
onClick,
isFollowing,
}: {
user: CollabUser;
isFirst: boolean;
onClick?: () => void;
isFollowing?: boolean;
}) {
const color = user.color || emailToColor(user.email);
const name = user.name || emailToName(user.email);
const initial = name.charAt(0).toUpperCase();
return (
) : (
initial
)}