From 3592bbf9f2d09ef07bc00d8a802354d4a71e6b5e Mon Sep 17 00:00:00 2001 From: yash-signal Date: Tue, 18 Nov 2025 14:34:22 -0600 Subject: [PATCH] Polls UI Enhancements --- _locales/en/messages.json | 4 + .../PollTerminateNotification.dom.tsx | 28 ++++-- .../poll-message/PollMessageContents.dom.tsx | 13 ++- .../poll-message/PollVotesModal.dom.tsx | 96 ++++++++++++------- 4 files changed, 98 insertions(+), 43 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 4e49397a89..d30e3f9fef 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -1518,6 +1518,10 @@ "messageformat": "No votes", "description": "Message shown when poll has no votes" }, + "icu:PollVotesModal__winningOption": { + "messageformat": "Winning", + "description": "Accessibility label for the star icon indicating the winning option in a poll" + }, "icu:Toast--PinnedMessageNotFound": { "messageformat": "Pinned message not found", "description": "Toast shown when user tries to view a pinned message that no longer exists" diff --git a/ts/components/conversation/PollTerminateNotification.dom.tsx b/ts/components/conversation/PollTerminateNotification.dom.tsx index 25ece70562..3ddc80e60f 100644 --- a/ts/components/conversation/PollTerminateNotification.dom.tsx +++ b/ts/components/conversation/PollTerminateNotification.dom.tsx @@ -6,6 +6,8 @@ import type { LocalizerType } from '../../types/Util.std.js'; import type { ConversationType } from '../../state/ducks/conversations.preload.js'; import { SystemMessage } from './SystemMessage.dom.js'; import { Button, ButtonVariant, ButtonSize } from '../Button.dom.js'; +import { UserText } from '../UserText.dom.js'; +import { I18n } from '../I18n.dom.js'; export type PropsType = { sender: ConversationType; @@ -24,17 +26,29 @@ export function PollTerminateNotification({ i18n, scrollToPollMessage, }: PropsType): JSX.Element { - const message = sender.isMe - ? i18n('icu:PollTerminate--you', { poll: pollQuestion }) - : i18n('icu:PollTerminate--other', { - name: sender.title, - poll: pollQuestion, - }); - const handleViewPoll = () => { scrollToPollMessage(pollMessageId, conversationId); }; + const message = sender.isMe ? ( + , + }} + /> + ) : ( + , + poll: , + }} + /> + ); + return ( -
{poll.question}
+
+ +
)} -
+
- {option} + + + {totalVotes > 0 && (
{poll.terminatedAt != null && weVotedForThis && ( diff --git a/ts/components/conversation/poll-message/PollVotesModal.dom.tsx b/ts/components/conversation/poll-message/PollVotesModal.dom.tsx index 25363fe345..0f84a04890 100644 --- a/ts/components/conversation/poll-message/PollVotesModal.dom.tsx +++ b/ts/components/conversation/poll-message/PollVotesModal.dom.tsx @@ -1,12 +1,14 @@ // Copyright 2025 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import React from 'react'; +import React, { useMemo } from 'react'; import { tw } from '../../../axo/tw.dom.js'; import { AxoButton } from '../../../axo/AxoButton.dom.js'; +import { AxoSymbol } from '../../../axo/AxoSymbol.dom.js'; import { Modal } from '../../Modal.dom.js'; import { Avatar, AvatarSize } from '../../Avatar.dom.js'; import { ContactName } from '../ContactName.dom.js'; +import { UserText } from '../../UserText.dom.js'; import type { LocalizerType } from '../../../types/Util.std.js'; import type { PollVoteWithUserType, @@ -30,6 +32,12 @@ export function PollVotesModal({ canEndPoll, messageId, }: PollVotesModalProps): JSX.Element { + const maxVoteCount = useMemo(() => { + return poll.votesByOption.values().reduce((max, voters) => { + return Math.max(max, voters.length); + }, 0); + }, [poll.votesByOption]); + return ( -
{poll.question}
+
+ +
{poll.options.map((option, index, array) => { const voters = poll.votesByOption.get(index) || []; const optionKey = `option-${index}`; const isLastOption = index === array.length - 1; + const isWinning = voters.length > 0 && voters.length === maxVoteCount; return ( @@ -62,45 +77,62 @@ export function PollVotesModal({ 'mb-3 flex items-start gap-3 text-label-primary' )} > -
{option}
+
+ +
- {i18n('icu:PollVotesModal__voteCount', { - count: voters.length, - })} + {isWinning && ( + + )} + {voters.length > 0 && + i18n('icu:PollVotesModal__voteCount', { + count: voters.length, + })}
{/* Voters List */} -
- {voters.map((vote: PollVoteWithUserType) => ( -
- -
- + {i18n('icu:PollVotesModal__noVotes')} +
+ ) : ( +
+ {voters.map((vote: PollVoteWithUserType) => ( +
+ +
+ +
-
- ))} -
+ ))} +
+ )}
{!isLastOption && (