CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | /**
* Reactions bar — displays current counts + a "+ react" picker.
* Works without JavaScript: each emoji is its own POST form.
*/
import type { FC } from "hono/jsx";
import {
ALLOWED_EMOJIS,
EMOJI_GLYPH,
type Emoji,
type ReactionSummary,
type TargetType,
} from "../lib/reactions";
export const ReactionsBar: FC<{
targetType: TargetType;
targetId: string;
summaries: ReactionSummary[];
canReact: boolean;
}> = ({ targetType, targetId, summaries, canReact }) => {
const byEmoji = new Map<Emoji, ReactionSummary>();
for (const s of summaries) byEmoji.set(s.emoji, s);
const action = (emoji: Emoji) =>
`/api/reactions/${targetType}/${targetId}/${emoji}/toggle`;
const visible = summaries.filter((s) => s.count > 0);
return (
<div class="reactions" data-target={`${targetType}:${targetId}`}>
{visible.map((s) => (
<form method="post" action={action(s.emoji)} style="display: inline">
<button
type="submit"
class={`reaction-btn ${s.reactedByMe ? "active" : ""}`}
title={s.emoji.replace(/_/g, " ")}
disabled={!canReact}
>
<span>{EMOJI_GLYPH[s.emoji]}</span>
<span class="reaction-count">{s.count}</span>
</button>
</form>
))}
{canReact && (
<details class="reaction-picker">
<summary class="reaction-btn" title="Add reaction">
{"\u271A"}
</summary>
<div style="display: flex; gap: 4px; padding: 4px">
{ALLOWED_EMOJIS.filter((e) => !byEmoji.get(e)?.reactedByMe).map(
(emoji) => (
<form method="post" action={action(emoji)} style="display: inline">
<button
type="submit"
class="reaction-btn"
title={emoji.replace(/_/g, " ")}
>
<span>{EMOJI_GLYPH[emoji]}</span>
</button>
</form>
)
)}
</div>
</details>
)}
</div>
);
};
|