From 01d57038e9d1bc45fb8fa4fbb84ea560bf63d63a Mon Sep 17 00:00:00 2001 From: marlon-erler Date: Tue, 7 Apr 2026 20:06:00 +0000 Subject: [PATCH] improve reaction interface on message bubbles --- dist/index.css | 21 ++++++++ dist/index.js | 51 ++++++++---------- dist/styles.css | 1 + src/View/ChatPages/messagePage.css | 26 +++++++++ src/View/Components/chatMessage.tsx | 5 +- src/View/Components/messageReactionButton.tsx | 53 +++++++------------ .../Components/messageReactionButtonRow.tsx | 2 +- 7 files changed, 92 insertions(+), 67 deletions(-) diff --git a/dist/index.css b/dist/index.css index 21b55b40..b9c5b62d 100644 --- a/dist/index.css +++ b/dist/index.css @@ -659,6 +659,7 @@ body[showing-chat] #chat-page-wrapper { } .message-bubble { display: flex; + flex-direction: column; width: 100%; max-width: var(--max-content-width); } @@ -728,6 +729,26 @@ body[showing-chat] #chat-page-wrapper { .message-bubble[sentbyuser] .tile.main span { text-align: right; } +.message-bubble > .message-reaction-row { + max-width: var(--max-input-width); + width: 100%; + padding: var(--gap-between-tiles); + background: var(--surface); +} +.message-bubble > .message-reaction-row button { + min-height: unset; + height: 100%; + width: 100%; + padding: var(--control-padding-compact); + gap: 0px; + flex: 1 !important; +} +.message-bubble > .message-reaction-row button[zero] { + display: none; +} +.message-reaction-row { + grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); +} /* src/View/ChatPages/taskPage.css */ #chat-page > div > #main > #task-page > .pane-wrapper > .pane > .main-content { diff --git a/dist/index.js b/dist/index.js index a7c639f8..12cd7ebe 100644 --- a/dist/index.js +++ b/dist/index.js @@ -3943,74 +3943,64 @@ // src/View/Components/messageReactionButton.tsx function MessageReactionButton(chatMessageViewModel, content) { + let audioLabel; + let count; + let reactionState; + let isActive = new State(false); + let isZero = new State(true); function sendReaction() { chatMessageViewModel.sendReaction(content, isActive.value); } function getUsername() { return chatMessageViewModel.messagePageViewModel.chatViewModel.settingsViewModel.username.value; } - function checkIsHighlighted(mapState) { - return mapState.value.has(getUsername()); + function checkIsHighlighted() { + return reactionState.value.has(getUsername()); } - let audioLabel; - let count; - let isActive; switch (content) { case "\u{1F44D}" /* ThumbsUp */: { audioLabel = translations.chatPage.message.thumbsUpReaction; count = chatMessageViewModel.reactionsThumbsUpCount; - isActive = createProxyState( - [chatMessageViewModel.reactionsThumbsUp], - () => checkIsHighlighted(chatMessageViewModel.reactionsThumbsUp) - ); + reactionState = chatMessageViewModel.reactionsThumbsUp; break; } case "\u2705" /* Check */: { audioLabel = translations.chatPage.message.checkReaction; count = chatMessageViewModel.reactionsCheckCount; - isActive = createProxyState( - [chatMessageViewModel.reactionsCheck], - () => checkIsHighlighted(chatMessageViewModel.reactionsCheck) - ); + reactionState = chatMessageViewModel.reactionsCheck; break; } case "\u2757\uFE0F" /* Attention */: { audioLabel = translations.chatPage.message.attentionReaction; count = chatMessageViewModel.reactionsAttentionCount; - isActive = createProxyState( - [chatMessageViewModel.reactionsAttention], - () => checkIsHighlighted(chatMessageViewModel.reactionsAttention) - ); + reactionState = chatMessageViewModel.reactionsAttention; break; } case "\u203C\uFE0F" /* DoubleAttention */: { audioLabel = translations.chatPage.message.doubleAttentionReaction; count = chatMessageViewModel.reactionsDoubleAttentionCount; - isActive = createProxyState( - [chatMessageViewModel.reactionsDoubleAttention], - () => checkIsHighlighted( - chatMessageViewModel.reactionsDoubleAttention - ) - ); + reactionState = chatMessageViewModel.reactionsDoubleAttention; break; } case "\u2753" /* Question */: { audioLabel = translations.chatPage.message.questionReaction; count = chatMessageViewModel.reactionsQuestionCount; - isActive = createProxyState( - [chatMessageViewModel.reactionsQuestion], - () => checkIsHighlighted(chatMessageViewModel.reactionsQuestion) - ); + reactionState = chatMessageViewModel.reactionsQuestion; break; } } + reactionState.subscribe(() => { + isActive.value = checkIsHighlighted(); + isZero.value = reactionState.value.size == 0; + }); return /* @__PURE__ */ createElement( "button", { class: "flex", "on:click": sendReaction, "aria-label": audioLabel, - "toggle:selected": isActive + "toggle:selected": isActive, + "toggle:zero": isZero }, content, /* @__PURE__ */ createElement("span", { "subscribe:innerText": count }) @@ -4019,7 +4009,7 @@ // src/View/Components/messageReactionButtonRow.tsx function MessageReactionButtonRow(chatMessageViewModel) { - return /* @__PURE__ */ createElement("div", { class: "flex-row gap" }, MessageReactionButton( + return /* @__PURE__ */ createElement("div", { class: "grid gap width-100 message-reaction-row" }, MessageReactionButton( chatMessageViewModel, "\u{1F44D}" /* ThumbsUp */ ), MessageReactionButton( @@ -4090,7 +4080,7 @@ class: "icon", "subscribe:innerText": statusIcon } - ), chatMessageViewModel.dateSent), MessageReactionButtonRow(chatMessageViewModel)), /* @__PURE__ */ createElement("div", { class: "button-container" }, /* @__PURE__ */ createElement( + ), chatMessageViewModel.dateSent)), /* @__PURE__ */ createElement("div", { class: "button-container" }, /* @__PURE__ */ createElement( "button", { "on:click": chatMessageViewModel.showInfoModal, @@ -4098,6 +4088,7 @@ }, /* @__PURE__ */ createElement("span", { class: "icon" }, "info") ))), + MessageReactionButtonRow(chatMessageViewModel), ChatMessageInfoModal(chatMessageViewModel) ); } diff --git a/dist/styles.css b/dist/styles.css index aefbf218..ca2245b8 100644 --- a/dist/styles.css +++ b/dist/styles.css @@ -288,6 +288,7 @@ SIZES /* padding */ --control-padding: 1rem; + --control-padding-compact: .4rem .8rem; --control-marker-padding: 0.7rem; --content-padding: 1rem; --modal-padding: 1.25rem; diff --git a/src/View/ChatPages/messagePage.css b/src/View/ChatPages/messagePage.css index cf532dee..47094227 100644 --- a/src/View/ChatPages/messagePage.css +++ b/src/View/ChatPages/messagePage.css @@ -75,6 +75,7 @@ .message-bubble { display: flex; + flex-direction: column; width: 100%; max-width: var(--max-content-width); } @@ -166,3 +167,28 @@ .message-bubble[sentbyuser] .tile.main span { text-align: right; } + +.message-bubble > .message-reaction-row { + max-width: var(--max-input-width); + width: 100%; + + padding: var(--gap-between-tiles); + background: var(--surface); +} + +.message-bubble > .message-reaction-row button { + min-height: unset; + height: 100%; + width: 100%; + padding: var(--control-padding-compact); + gap: 0px; + flex: 1 !important; +} + +.message-bubble > .message-reaction-row button[zero] { + display: none; +} + +.message-reaction-row { + grid-template-columns: repeat(auto-fit, minmax(80px, 1fr)); +} \ No newline at end of file diff --git a/src/View/Components/chatMessage.tsx b/src/View/Components/chatMessage.tsx index 39ebc15b..3248a475 100644 --- a/src/View/Components/chatMessage.tsx +++ b/src/View/Components/chatMessage.tsx @@ -44,8 +44,6 @@ export function ChatMessage(chatMessageViewModel: ChatMessageViewModel) { > {chatMessageViewModel.dateSent} - - {MessageReactionButtonRow(chatMessageViewModel)}
@@ -59,8 +57,9 @@ export function ChatMessage(chatMessageViewModel: ChatMessageViewModel) { info
- + + {MessageReactionButtonRow(chatMessageViewModel)} {ChatMessageInfoModal(chatMessageViewModel)} ); diff --git a/src/View/Components/messageReactionButton.tsx b/src/View/Components/messageReactionButton.tsx index 63dd9b76..3f7ce11b 100644 --- a/src/View/Components/messageReactionButton.tsx +++ b/src/View/Components/messageReactionButton.tsx @@ -1,5 +1,5 @@ import * as React from "bloatless-react"; -import { ReactionSymbols } from "../../Model/Chat/chatModel"; +import { ChatMessageReaction, ReactionSymbols } from "../../Model/Chat/chatModel"; import ChatMessageViewModel from "../../ViewModel/Chat/chatMessageViewModel"; import { translations } from "../translations"; @@ -7,6 +7,12 @@ export function MessageReactionButton( chatMessageViewModel: ChatMessageViewModel, content: ReactionSymbols, ) { + let audioLabel: string; + let count: React.State; + let reactionState: React.MapState; + let isActive = new React.State(false); + let isZero = new React.State(true); + function sendReaction() { chatMessageViewModel.sendReaction(content, isActive.value); } @@ -16,74 +22,55 @@ export function MessageReactionButton( .settingsViewModel.username.value; } - function checkIsHighlighted(mapState: React.MapState): boolean { - return mapState.value.has(getUsername()); + function checkIsHighlighted(): boolean { + return reactionState.value.has(getUsername()); } - let audioLabel: string; - let count: React.State; - let isActive: React.State; - switch (content) { case ReactionSymbols.ThumbsUp: { audioLabel = translations.chatPage.message.thumbsUpReaction; count = chatMessageViewModel.reactionsThumbsUpCount; - isActive = React.createProxyState( - [chatMessageViewModel.reactionsThumbsUp], - () => - checkIsHighlighted(chatMessageViewModel.reactionsThumbsUp), - ); + reactionState = chatMessageViewModel.reactionsThumbsUp; break; } case ReactionSymbols.Check: { audioLabel = translations.chatPage.message.checkReaction; count = chatMessageViewModel.reactionsCheckCount; - isActive = React.createProxyState( - [chatMessageViewModel.reactionsCheck], - () => checkIsHighlighted(chatMessageViewModel.reactionsCheck), - ); + reactionState = chatMessageViewModel.reactionsCheck; break; } case ReactionSymbols.Attention: { audioLabel = translations.chatPage.message.attentionReaction; count = chatMessageViewModel.reactionsAttentionCount; - isActive = React.createProxyState( - [chatMessageViewModel.reactionsAttention], - () => - checkIsHighlighted(chatMessageViewModel.reactionsAttention), - ); + reactionState = chatMessageViewModel.reactionsAttention; break; } case ReactionSymbols.DoubleAttention: { audioLabel = translations.chatPage.message.doubleAttentionReaction; count = chatMessageViewModel.reactionsDoubleAttentionCount; - isActive = React.createProxyState( - [chatMessageViewModel.reactionsDoubleAttention], - () => - checkIsHighlighted( - chatMessageViewModel.reactionsDoubleAttention, - ), - ); + reactionState = chatMessageViewModel.reactionsDoubleAttention; break; } case ReactionSymbols.Question: { audioLabel = translations.chatPage.message.questionReaction; count = chatMessageViewModel.reactionsQuestionCount; - isActive = React.createProxyState( - [chatMessageViewModel.reactionsQuestion], - () => - checkIsHighlighted(chatMessageViewModel.reactionsQuestion), - ); + reactionState = chatMessageViewModel.reactionsQuestion; break; } } + reactionState.subscribe(() => { + isActive.value = checkIsHighlighted(); + isZero.value = reactionState.value.size == 0; + }) + return (