Replace generic slate/indigo dark theme with a custom editorial direction: warm paper neutrals, oxblood signal, Fraunces serif display, Inter body, JetBrains Mono for codes. SVG paper-texture noise overlay and thin rules across the app. Refactored: Home, Settings, JoinRoom, Pair, Share, plus every modal and panel (DropZone, DevicePairingPanel, PublicRoomPanel, ProfileSetup, TextShareModal, ReceiveDialog, TransferProgress, PeerList, PeerAvatar). Also drops three pre-existing Uint8Array/BlobPart strictness errors so the production build is green again. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
91 lines
2.3 KiB
TypeScript
91 lines
2.3 KiB
TypeScript
import type { DeviceType } from "@anydrop/shared";
|
|
|
|
interface PeerAvatarProps {
|
|
displayName: string;
|
|
deviceType: DeviceType;
|
|
avatar?: string;
|
|
online?: boolean;
|
|
onClick?: () => void;
|
|
isSelected?: boolean;
|
|
size?: "sm" | "md" | "lg";
|
|
}
|
|
|
|
const DEVICE_GLYPH: Record<DeviceType, string> = {
|
|
phone: "phone",
|
|
tablet: "tablet",
|
|
laptop: "laptop",
|
|
desktop: "desk",
|
|
};
|
|
|
|
const sizeClasses = {
|
|
sm: { container: "w-12 h-12", label: "text-[10px]" },
|
|
md: { container: "w-16 h-16", label: "text-[11px]" },
|
|
lg: { container: "w-20 h-20", label: "text-xs" },
|
|
};
|
|
|
|
export default function PeerAvatar({
|
|
displayName,
|
|
deviceType,
|
|
avatar,
|
|
online = true,
|
|
onClick,
|
|
isSelected,
|
|
size = "md",
|
|
}: PeerAvatarProps) {
|
|
const s = sizeClasses[size];
|
|
const isOffline = !online;
|
|
|
|
return (
|
|
<button
|
|
onClick={onClick}
|
|
className={`
|
|
group flex flex-col items-center gap-2.5
|
|
transition-transform duration-fast ease-crisp
|
|
${isOffline ? "opacity-60" : ""}
|
|
`}
|
|
>
|
|
<div className="relative">
|
|
<div
|
|
className={`
|
|
${s.container}
|
|
rounded-full flex items-center justify-center overflow-hidden
|
|
border transition-all duration-fast ease-crisp
|
|
${isSelected
|
|
? "border-signal ring-1 ring-signal"
|
|
: "border-paper-edge group-hover:border-ink"
|
|
}
|
|
${avatar ? "" : "bg-paper"}
|
|
`}
|
|
>
|
|
{avatar ? (
|
|
<img
|
|
src={avatar}
|
|
alt=""
|
|
className={`w-full h-full object-cover ${isOffline ? "grayscale" : ""}`}
|
|
/>
|
|
) : (
|
|
<span className="font-mono text-[10px] uppercase tracking-widest text-ink-muted">
|
|
{DEVICE_GLYPH[deviceType]}
|
|
</span>
|
|
)}
|
|
</div>
|
|
<span
|
|
className={`
|
|
absolute -bottom-0.5 -right-0.5 w-2.5 h-2.5 rounded-full border border-paper
|
|
${online ? "bg-ok" : "bg-ink-faint"}
|
|
`}
|
|
aria-label={online ? "online" : "offline"}
|
|
/>
|
|
</div>
|
|
<span
|
|
className={`
|
|
${s.label} max-w-[88px] truncate transition-colors
|
|
${isSelected ? "text-ink" : isOffline ? "text-ink-faint" : "text-ink-muted group-hover:text-ink"}
|
|
`}
|
|
>
|
|
{displayName}
|
|
</span>
|
|
</button>
|
|
);
|
|
}
|