From 22cdda441d19da1b15dd7db4d932d2f067cd87b3 Mon Sep 17 00:00:00 2001 From: morgmart <98432065+morgmart@users.noreply.github.com> Date: Wed, 26 Aug 2026 10:32:46 -0700 Subject: [PATCH] feat(desktop): restore message quick reactions (#6892) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - restore three learned quick-reaction shortcuts to the desktop message action rail - keep Add reaction (`SmilePlus`, `h-4 w-4`) immediately after shortcuts and left of the restored divider - preserve Reply, Copy link, More, custom emoji rendering, accessibility labels, and responsive behavior - hide shortcuts and the divider below `sm`, while keeping Add reaction available ## Tests At `24c4732c2341b846dccf4ae8fdc2455f700fc44d`: - `pnpm exec biome check src/features/messages/ui/MessageActionBar.tsx tests/e2e/message-copy-link.spec.ts tests/e2e/custom-emoji.spec.ts` - `pnpm exec tsc --noEmit` - `pnpm test` — 5,567 passed - `pnpm build:e2e` - `pnpm exec playwright test tests/e2e/message-copy-link.spec.ts tests/e2e/custom-emoji.spec.ts --project=smoke` — 19 passed - `pnpm exec playwright test tests/e2e/reaction-order.spec.ts --project=smoke` — 2 passed - pre-push: file-size-check, desktop-check, desktop-typecheck, desktop-test Vogue design review: **SHIP**. ## Screenshot Restored desktop message action rail: three learned quick reactions, Add reaction (`SmilePlus`), divider, Reply, Copy link, and More. ![Restored desktop message action rail](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/6892/restored-message-action-rail.png) Signed-off-by: Rivet Co-authored-by: Rivet --- .../features/messages/ui/MessageActionBar.tsx | 89 ++++++++++++++++++- desktop/tests/e2e/custom-emoji.spec.ts | 24 +++++ desktop/tests/e2e/message-copy-link.spec.ts | 28 +++++- 3 files changed, 136 insertions(+), 5 deletions(-) diff --git a/desktop/src/features/messages/ui/MessageActionBar.tsx b/desktop/src/features/messages/ui/MessageActionBar.tsx index 9c490edb07..02dcdcaf6d 100644 --- a/desktop/src/features/messages/ui/MessageActionBar.tsx +++ b/desktop/src/features/messages/ui/MessageActionBar.tsx @@ -18,6 +18,7 @@ import { toast } from "sonner"; import { buildMessageLink } from "@/features/messages/lib/messageLink"; import { EmojiPicker } from "@/features/custom-emoji/ui/EmojiPicker"; +import { useCustomEmoji } from "@/features/custom-emoji/hooks"; import { getThreadReference } from "@/features/messages/lib/threading"; import { ReportMessageDialog } from "@/features/moderation/ui/ReportMessageDialog"; import { MessageModerationMenuItems } from "@/features/moderation/ui/MessageModerationMenuItems"; @@ -25,9 +26,15 @@ import type { TimelineMessage, TimelineReaction, } from "@/features/messages/types"; -import { recordQuickReactionEmoji } from "@/features/messages/ui/useQuickReactionEmojis"; +import { + recordQuickReactionEmoji, + useQuickReactionEmojis, +} from "@/features/messages/ui/useQuickReactionEmojis"; +import { reactionEmojiUrl } from "@/shared/api/customEmoji"; import { cn } from "@/shared/lib/cn"; import { copyTextToClipboard } from "@/shared/lib/clipboard"; +import { emojiDisplayName } from "@/shared/lib/emojiName"; +import { rewriteRelayUrl } from "@/shared/lib/mediaUrl"; import { KIND_HUDDLE_STARTED } from "@/shared/constants/kinds"; import { Button } from "@/shared/ui/button"; import { HashArrowIn } from "@/shared/ui/icons"; @@ -329,6 +336,51 @@ function MoreActionsMenu({ ); } +function QuickReactionButton({ + customEmojiUrl, + emoji, + onSelect, +}: { + customEmojiUrl?: string; + emoji: string; + onSelect: (emoji: string) => void; +}) { + const displayName = emojiDisplayName(emoji); + const mediaUrl = customEmojiUrl ? rewriteRelayUrl(customEmojiUrl) : null; + + return ( + + + + + {displayName} + + ); +} + +function isCustomEmojiShortcode(emoji: string) { + return emoji.startsWith(":") && emoji.endsWith(":"); +} + export const MessageActionBar = React.memo(function MessageActionBar({ channelId, message, @@ -372,6 +424,20 @@ export const MessageActionBar = React.memo(function MessageActionBar({ }) { const [isReactionPickerOpen, setIsReactionPickerOpen] = React.useState(false); const [isDropdownOpen, setIsDropdownOpen] = React.useState(false); + const customEmoji = useCustomEmoji(); + const quickReactionEmojis = useQuickReactionEmojis(3, customEmoji); + const quickReactionItems = React.useMemo( + () => + quickReactionEmojis + .map((emoji) => ({ + customEmojiUrl: reactionEmojiUrl(emoji, customEmoji), + emoji, + })) + .filter( + (item) => !isCustomEmojiShortcode(item.emoji) || item.customEmojiUrl, + ), + [customEmoji, quickReactionEmojis], + ); const hasReplyAction = Boolean(onReply); const hasReactionAction = Boolean(onReactionSelect); @@ -436,6 +502,19 @@ export const MessageActionBar = React.memo(function MessageActionBar({ >
+ {hasReactionAction && quickReactionItems.length > 0 ? ( +
+ {quickReactionItems.map(({ customEmojiUrl, emoji }) => ( + + ))} +
+ ) : null} + {hasReactionAction ? ( ) : null} + {hasReactionAction && quickReactionItems.length > 0 ? ( +