mirror of
https://github.com/warmbly/warmbly.git
synced 2026-09-12 08:04:41 +00:00
feat: render teammate avatars with initials fallback on live cursors so it is always clear who each pointer belongs to
This commit is contained in:
@@ -14,7 +14,7 @@ export default function CanvasCursors({ cursors }: { cursors: RemoteCursor[] })
|
||||
return (
|
||||
<div className="pointer-events-none absolute inset-0 z-20 overflow-hidden">
|
||||
{cursors.map((c) => (
|
||||
<Cursor key={c.userId} color={c.color} name={c.name} left={x + c.x * zoom} top={y + c.y * zoom} />
|
||||
<Cursor key={c.userId} color={c.color} name={c.name} avatar={c.avatar} left={x + c.x * zoom} top={y + c.y * zoom} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,17 +1,29 @@
|
||||
// Cursor — one teammate's live pointer, positioned in screen space by the layer
|
||||
// that renders it (canvas or page). A short CSS transition smooths the gaps
|
||||
// between throttled frames so movement reads as continuous.
|
||||
// that renders it (canvas or page). The label carries the person's real avatar
|
||||
// (initials fallback) next to their name so it is always clear who it is. A
|
||||
// short CSS transition smooths the gaps between throttled frames.
|
||||
|
||||
import React from "react";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
|
||||
function initialsOf(name: string | null): string {
|
||||
if (!name) return "?";
|
||||
const parts = name.trim().split(/\s+/).filter(Boolean);
|
||||
if (parts.length === 0) return "?";
|
||||
if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();
|
||||
return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
|
||||
}
|
||||
|
||||
export default function Cursor({
|
||||
color,
|
||||
name,
|
||||
avatar,
|
||||
left,
|
||||
top,
|
||||
}: {
|
||||
color: string;
|
||||
name: string | null;
|
||||
avatar?: string | null;
|
||||
left: number;
|
||||
top: number;
|
||||
}) {
|
||||
@@ -35,13 +47,21 @@ export default function Cursor({
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
{name ? (
|
||||
<span
|
||||
className="absolute left-3.5 top-3.5 max-w-[140px] truncate rounded-full px-1.5 py-0.5 text-[10.5px] font-medium text-white shadow-sm"
|
||||
{name || avatar ? (
|
||||
<div
|
||||
className="absolute left-3 top-3.5 flex max-w-[160px] items-center gap-1 rounded-full py-0.5 pl-0.5 pr-2 shadow-sm"
|
||||
style={{ backgroundColor: color }}
|
||||
>
|
||||
{name}
|
||||
</span>
|
||||
<Avatar className="size-3.5 shrink-0 ring-1 ring-white/80">
|
||||
{avatar ? <AvatarImage src={avatar} alt={name ?? ""} /> : null}
|
||||
<AvatarFallback className="bg-white/90 text-[7px] font-semibold text-slate-700">
|
||||
{initialsOf(name)}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
{name ? (
|
||||
<span className="truncate text-[10.5px] font-medium leading-none text-white">{name}</span>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -123,7 +123,7 @@ function GlobalCursorsOverlay({ scrollRef }: { scrollRef: React.RefObject<HTMLEl
|
||||
style={{ left: r.left, top: r.top, width: r.width, height: r.height }}
|
||||
>
|
||||
{live.cursors.map((c) => (
|
||||
<Cursor key={c.userId} color={c.color} name={c.name} left={c.x - sl} top={c.y - st} />
|
||||
<Cursor key={c.userId} color={c.color} name={c.name} avatar={c.avatar} left={c.x - sl} top={c.y - st} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user