first commit
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
"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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user