import { useState } from "react";
import { toggleReaction } from "~/fns/reactions";
import { useOptimisticMutation } from "~/hooks/use-optimistic-mutation";
import { REACTION_CONTENTS, REACTION_EMOJI } from "~/lib/reactions";
import type { ReactionContent, ReactionSubject, ReactionSummary } from "~/lib/reactions";
import { cn } from "~/utils/cn";
import { useDismiss } from "~/hooks/use-dismiss";
type Props = {
owner: string;
repo: string;
subjectType: ReactionSubject;
subjectId: string;
reactions: ReactionSummary[];
canReact: boolean;
};
function toggleLocally(current: ReactionSummary[], content: ReactionContent): ReactionSummary[] {
const existing = current.find((reaction) => reaction.content === content);
if (!existing) return [...current, { content, count: 1, viewerReacted: true }];
const next = existing.viewerReacted
? { ...existing, count: existing.count - 1, viewerReacted: false }
: { ...existing, count: existing.count + 1, viewerReacted: true };
return current.flatMap((reaction) => (reaction.content !== content ? [reaction] : next.count > 0 ? [next] : []));
}
/** Reaction pills plus an emoji picker; reusable on issues, PRs, comments, review comments and discussions. */
export function ReactionBar({ owner, repo, subjectType, subjectId, reactions, canReact }: Props) {
const [open, setOpen] = useState(false);
const toggle = useOptimisticMutation(reactions, toggleLocally, (content: ReactionContent) =>
toggleReaction({ data: { owner, repo, subjectType, subjectId, content } }),
);
const ref = useDismiss<HTMLDivElement>(open, () => setOpen(false));
const react = (content: ReactionContent) => {
setOpen(false);
toggle.run(content);
};
if (!canReact && reactions.length === 0) return null;
return (
<div ref={ref} className="relative flex flex-wrap items-center gap-1.5">