feat: chat stickers

This commit is contained in:
gosangam
2026-08-30 02:19:31 +05:30
parent 8c7afe596d
commit 2597c6f2ec
9 changed files with 332 additions and 18 deletions
+35 -5
View File
@@ -14,8 +14,15 @@ import { MessageAttachments } from "@/components/chat/message-attachments";
import { MessageTicks } from "@/components/chat/message-ticks";
import { tickState } from "@/lib/chat-format";
import { HEART_EMOJI } from "@/components/chat/reaction-row";
import { StickerGrid } from "@/components/chat/sticker-grid";
import { cn } from "@/lib/utils";
import type { ChatMessage, ChatPeer, Receipts, ReplyPreview } from "@/lib/chat";
import type {
Attachment,
ChatMessage,
ChatPeer,
Receipts,
ReplyPreview,
} from "@/lib/chat";
/** Time only — the day is already established by the date separator above. */
function clockTime(iso: string): string {
@@ -33,6 +40,20 @@ function sharesAnEntity(m: ChatMessage): boolean {
return (m.attachments ?? []).some((a) => a.kind === "entity");
}
/** Stickers on a message, if any. */
function stickersOn(m: ChatMessage): Attachment[] {
return (m.attachments ?? []).filter((a) => a.kind === "sticker");
}
/**
* True when a message is nothing but stickers. Those render bare — artwork
* inside a coloured bubble reads as a pasted image rather than a sticker.
*/
function isStickerOnly(m: ChatMessage): boolean {
const atts = m.attachments ?? [];
return !m.body && atts.length > 0 && atts.every((a) => a.kind === "sticker");
}
/** Distinct emoji shown before the rest collapse into "+N". */
const MAX_TRAIL_EMOJI = 4;
@@ -208,7 +229,7 @@ export function MessageGroup({
// own border, rounding and background, so the bubble drops
// its chrome rather than drawing a second frame around it.
// Any caption gets the padding back on its own line below.
sharesAnEntity(m)
sharesAnEntity(m) || isStickerOnly(m)
? "overflow-hidden bg-transparent"
: cn(
"px-3 py-2",
@@ -227,8 +248,17 @@ export function MessageGroup({
{/* A shared post leads and its caption follows, the way
the post itself reads. Everything else keeps text
first, with attachments beneath it. */}
{sharesAnEntity(m) && (
<MessageAttachments attachments={m.attachments} />
{isStickerOnly(m) ? (
<StickerGrid
conversationId={m.conversation_id}
messageId={m.id}
stickers={stickersOn(m)}
mine={mine}
/>
) : (
sharesAnEntity(m) && (
<MessageAttachments attachments={m.attachments} />
)
)}
{m.body && (
<span
@@ -244,7 +274,7 @@ export function MessageGroup({
<MessageText body={m.body} entities={m.entities} />
</span>
)}
{!sharesAnEntity(m) && (
{!sharesAnEntity(m) && !isStickerOnly(m) && (
<MessageAttachments attachments={m.attachments} />
)}
{/* An optimistic bubble for a captionless attachment has