Files
message-inbox-web-frontend-sdk/apps/web/components/overlays/HuddleBar.tsx
T
2026-07-17 21:48:37 +05:30

75 lines
3.5 KiB
TypeScript

"use client";
import { useEffect, useRef, useState } from "react";
import { useSdk, useChannel } from "@lynkd/messaging-inbox-sdk";
import { useUi } from "@/lib/ui-state";
import { Avatar } from "@/components/ui/primitives";
import { Mic, MicOff, Video, VideoOff, MonitorUp, PhoneOff } from "lucide-react";
import clsx from "clsx";
export function HuddleBar() {
const { huddleChannelId, endHuddle } = useUi();
const channel = useChannel(huddleChannelId ?? null);
const { userById } = useSdk();
const [muted, setMuted] = useState(false);
const [video, setVideo] = useState(false);
const [sharing, setSharing] = useState(false);
const [seconds, setSeconds] = useState(0);
const startRef = useRef(0);
useEffect(() => {
if (!huddleChannelId) return;
setSeconds(0);
setMuted(false);
setVideo(false);
setSharing(false);
startRef.current = Date.now();
const t = setInterval(() => setSeconds(Math.floor((Date.now() - startRef.current) / 1000)), 1000);
return () => clearInterval(t);
}, [huddleChannelId]);
if (!huddleChannelId || !channel) return null;
const members = channel.memberIds.slice(0, 4).map((id) => userById(id)).filter(Boolean);
const total = channel.memberIds.length;
const mmss = `${String(Math.floor(seconds / 60)).padStart(2, "0")}:${String(seconds % 60).padStart(2, "0")}`;
return (
<div className="fixed bottom-4 left-1/2 z-50 w-[min(440px,92vw)] -translate-x-1/2 rounded-card border border-border bg-elevated p-3 shadow-pop animate-slide-up">
<div className="flex items-center gap-3">
<span className="relative flex h-9 w-9 items-center justify-center rounded-control bg-success/20 text-success">
<Video size={18} />
<span className="absolute inset-0 animate-ping rounded-control border border-success/40" />
</span>
<div className="min-w-0 flex-1">
<div className="truncate text-[13.5px] font-bold">Huddle · {channel.kind === "channel" ? `#${channel.name}` : channel.name}</div>
<div className="text-[12px] text-muted">{total} {total === 1 ? "person" : "people"} · {mmss}</div>
</div>
<div className="flex -space-x-2">
{members.map((u) => (u ? <Avatar key={u.id} user={u} size={26} showPresence={false} /> : null))}
</div>
</div>
<div className="mt-3 flex items-center justify-center gap-2">
<HuddleBtn icon={muted ? MicOff : Mic} label={muted ? "Unmute" : "Mute"} active={muted} onClick={() => setMuted((m) => !m)} />
<HuddleBtn icon={video ? Video : VideoOff} label={video ? "Stop video" : "Start video"} active={video} onClick={() => setVideo((v) => !v)} />
<HuddleBtn icon={MonitorUp} label={sharing ? "Stop sharing" : "Share screen"} active={sharing} onClick={() => setSharing((s) => !s)} />
<button onClick={endHuddle} className="focus-ring flex items-center gap-2 rounded-control bg-danger px-4 py-2 text-[13px] font-semibold text-white">
<PhoneOff size={15} /> Leave
</button>
</div>
</div>
);
}
function HuddleBtn({ icon: Icon, label, onClick, active }: { icon: typeof Mic; label: string; onClick: () => void; active?: boolean }) {
return (
<button
title={label}
onClick={onClick}
className={clsx("focus-ring grid h-9 w-11 place-items-center rounded-control border transition-colors", active ? "border-accent bg-accent-soft text-accent" : "border-border bg-panel text-ink hover:bg-hover")}
>
<Icon size={16} />
</button>
);
}