102 lines
3.4 KiB
TypeScript
102 lines
3.4 KiB
TypeScript
import { useState } from "react";
|
|
import { Plus, X } from "lucide-react";
|
|
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
|
import { REACTION_EMOJIS, type Reactions } from "@/api/types";
|
|
import { cn } from "@/lib/utils";
|
|
import type { Human } from "@/api/types";
|
|
|
|
interface ReactionBarProps {
|
|
reactions: Reactions;
|
|
currentHumanId: string;
|
|
humans?: Human[];
|
|
onToggle: (emoji: string) => void;
|
|
}
|
|
|
|
function getReactorNames(
|
|
humanIds: string[],
|
|
humans?: Human[],
|
|
): string {
|
|
return humanIds
|
|
.map((id) => {
|
|
const human = humans?.find((h) => h.id === id);
|
|
return human?.email_prefix ?? id;
|
|
})
|
|
.join(", ");
|
|
}
|
|
|
|
export function ReactionBar({ reactions, currentHumanId, humans, onToggle }: ReactionBarProps) {
|
|
const [expanded, setExpanded] = useState(false);
|
|
|
|
const activeEmojis = REACTION_EMOJIS.filter(
|
|
(emoji) => reactions?.[emoji] && reactions[emoji].length > 0,
|
|
);
|
|
|
|
const handleToggle = (emoji: string) => {
|
|
onToggle(emoji);
|
|
setExpanded(false);
|
|
};
|
|
|
|
return (
|
|
<div className="flex items-center gap-1.5">
|
|
{/* Existing reaction pills */}
|
|
{activeEmojis.map((emoji) => {
|
|
const reactors = reactions![emoji];
|
|
const isMine = reactors.includes(currentHumanId);
|
|
return (
|
|
<Tooltip key={emoji}>
|
|
<TooltipTrigger asChild>
|
|
<button
|
|
onClick={(e) => { e.stopPropagation(); handleToggle(emoji); }}
|
|
className={cn(
|
|
"flex items-center gap-1 rounded-full px-2 py-0.5 text-xs backdrop-blur-sm transition-colors",
|
|
isMine
|
|
? "bg-white/20 ring-1 ring-white/40"
|
|
: "bg-black/40 hover:bg-black/50",
|
|
)}
|
|
>
|
|
<span className="text-sm">{emoji}</span>
|
|
<span className="text-white/80">{reactors.length}</span>
|
|
</button>
|
|
</TooltipTrigger>
|
|
<TooltipContent side="top" className="text-xs">
|
|
{getReactorNames(reactors, humans)}
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
);
|
|
})}
|
|
|
|
{/* Expand / picker toggle */}
|
|
{expanded ? (
|
|
<div className="flex items-center gap-0.5 rounded-full bg-black/40 px-1.5 py-0.5 backdrop-blur-sm">
|
|
{REACTION_EMOJIS.map((emoji) => {
|
|
// Skip emojis that already have pills
|
|
if (activeEmojis.includes(emoji)) return null;
|
|
return (
|
|
<button
|
|
key={emoji}
|
|
onClick={(e) => { e.stopPropagation(); handleToggle(emoji); }}
|
|
className="rounded-full px-1 py-0.5 text-sm transition-colors hover:bg-white/15"
|
|
>
|
|
{emoji}
|
|
</button>
|
|
);
|
|
})}
|
|
<button
|
|
onClick={(e) => { e.stopPropagation(); setExpanded(false); }}
|
|
className="flex size-5 items-center justify-center rounded-full transition-colors hover:bg-white/15"
|
|
>
|
|
<X className="size-3 text-white/60" />
|
|
</button>
|
|
</div>
|
|
) : (
|
|
<button
|
|
onClick={(e) => { e.stopPropagation(); setExpanded(true); }}
|
|
className="flex size-6 items-center justify-center rounded-full bg-black/40 backdrop-blur-sm transition-colors hover:bg-black/50"
|
|
>
|
|
<Plus className="size-3 text-white/60" />
|
|
</button>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|