80 lines
2.2 KiB
TypeScript
80 lines
2.2 KiB
TypeScript
"use client";
|
|
|
|
/**
|
|
* Stickers on a message: a 2-column grid, rendered without a chat bubble.
|
|
*
|
|
* Unlike the media grid, this never collapses into "+N more" — every sticker
|
|
* sent is drawn. They are small and deliberately playful, and hiding some
|
|
* behind a counter would make a wall of them read as an attachment list
|
|
* rather than what it is.
|
|
*
|
|
* Long-pressing or right-clicking one offers to keep it, which is the only
|
|
* way a sticker spreads beyond the person who earned it.
|
|
*/
|
|
|
|
import { useState } from "react";
|
|
import Image from "next/image";
|
|
import { toast } from "sonner";
|
|
import { chat, type Attachment } from "@/lib/chat";
|
|
|
|
export function StickerGrid({
|
|
conversationId,
|
|
messageId,
|
|
stickers,
|
|
/** Own stickers are already in your collection — no point offering to save. */
|
|
mine,
|
|
}: {
|
|
conversationId: string;
|
|
messageId: string;
|
|
stickers: Attachment[];
|
|
mine: boolean;
|
|
}) {
|
|
const [saving, setSaving] = useState<string | null>(null);
|
|
|
|
if (stickers.length === 0) return null;
|
|
|
|
const save = async (a: Attachment) => {
|
|
const key = a.entity_id;
|
|
if (!key || mine || saving) return;
|
|
setSaving(key);
|
|
try {
|
|
await chat.saveSticker(conversationId, messageId, key);
|
|
toast.success(`Saved ${a.title ?? "sticker"} to your collection`);
|
|
} catch {
|
|
toast.error("Could not save that sticker");
|
|
} finally {
|
|
setSaving(null);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="grid w-fit grid-cols-2 gap-1">
|
|
{stickers.map((a) => (
|
|
<button
|
|
key={a.id}
|
|
type="button"
|
|
disabled={mine}
|
|
onContextMenu={(e) => {
|
|
if (mine) return;
|
|
e.preventDefault();
|
|
void save(a);
|
|
}}
|
|
title={mine ? (a.title ?? "") : `${a.title ?? "Sticker"} — hold to save`}
|
|
aria-label={a.title ?? "Sticker"}
|
|
className="relative size-20 disabled:cursor-default sm:size-24"
|
|
>
|
|
{a.image_url && (
|
|
<Image
|
|
src={a.image_url}
|
|
alt={a.title ?? ""}
|
|
fill
|
|
sizes="96px"
|
|
className="object-contain"
|
|
/>
|
|
)}
|
|
</button>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|