From 710a54d43f1c5988a834941914a6f06f157388b6 Mon Sep 17 00:00:00 2001 From: Jamie <113370520+jamiebuilds-signal@users.noreply.github.com> Date: Wed, 19 Nov 2025 10:55:47 -0800 Subject: [PATCH] Init PinnedMessagesBar UI --- _locales/en/messages.json | 28 ++ ts/axo/AriaClickable.dom.tsx | 23 +- ts/axo/AxoContextMenu.dom.tsx | 4 +- ts/axo/AxoDropdownMenu.dom.tsx | 59 +++- ts/axo/AxoIconButton.dom.tsx | 9 +- .../PinnedMessagesBar.dom.stories.tsx | 81 +++++ .../pinned-messages/PinnedMessagesBar.dom.tsx | 277 ++++++++++++++++++ 7 files changed, 469 insertions(+), 12 deletions(-) create mode 100644 ts/components/conversation/pinned-messages/PinnedMessagesBar.dom.stories.tsx create mode 100644 ts/components/conversation/pinned-messages/PinnedMessagesBar.dom.tsx diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 53985bbeb2..38dd851735 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -1650,6 +1650,34 @@ "messageformat": "Pin", "description": "Message > Context Menu > Pin Message > Dialog > Pin Button" }, + "icu:PinnedMessagesBar__AccessibilityLabel": { + "messageformat": "{pinsCount, plural, one {Pinned message} other {Pinned messages}}", + "description": "Conversation > With pinned message(s) > Pinned messages bar > Accessibility label" + }, + "icu:PinnedMessagesBar__Tab__AccessibilityLabel": { + "messageformat": "Go to pin {pinNumber, number}", + "description": "Conversation > With *multiple* pinned messages > Pinned messages bar > Vertical tabs > Tab item > Accessibility Label" + }, + "icu:PinnedMessagesBar__GoToMessageClickableArea__AccessibilityLabel": { + "messageformat": "Go to message", + "description": "Conversation > With pinned message(s) > Pinned messages bar > Clickable area (Goes to pinned message) > Accessibility label" + }, + "icu:PinnedMessagesBar__ActionsMenu__Button__AccessibilityLabel": { + "messageformat": "More actions", + "description": "Conversation > With pinned message(s) > Pinned messages bar > More actions menu button > Accessibility label" + }, + "icu:PinnedMessagesBar__ActionsMenu__UnpinMessage": { + "messageformat": "Unpin message", + "description": "Conversation > With pinned message(s) > Pinned messages bar > More actions menu > Unpin message" + }, + "icu:PinnedMessagesBar__ActionsMenu__GoToMessage": { + "messageformat": "Go to message", + "description": "Conversation > With pinned message(s) > Pinned messages bar > More actions menu > Go to message" + }, + "icu:PinnedMessagesBar__ActionsMenu__SeeAllMessages": { + "messageformat": "See all messages", + "description": "Conversation > With pinned message(s) > Pinned messages bar > More actions menu > See all messages" + }, "icu:sessionEnded": { "messageformat": "Secure session reset", "description": "This is a past tense, informational message. In other words, your secure session has been reset." diff --git a/ts/axo/AriaClickable.dom.tsx b/ts/axo/AriaClickable.dom.tsx index 0d3057bcc3..7e97715100 100644 --- a/ts/axo/AriaClickable.dom.tsx +++ b/ts/axo/AriaClickable.dom.tsx @@ -1,8 +1,9 @@ // Copyright 2025 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import React, { memo, useCallback, useRef, useState } from 'react'; +import React, { memo, useCallback, useEffect, useRef, useState } from 'react'; import type { ReactNode, MouseEvent, FC } from 'react'; import { useLayoutEffect } from '@react-aria/utils'; +import { computeAccessibleName } from 'dom-accessibility-api'; import { tw } from './tw.dom.js'; import { assert } from './_internal/assert.dom.js'; import { @@ -146,6 +147,10 @@ export namespace AriaClickable { */ export type HiddenTriggerProps = Readonly<{ + /** + * Describe the action to be taken `onClick` + */ + 'aria-label'?: string; /** * This should reference the ID of an element that describes the action that * will be taken `onClick`, not the entire clickable root. @@ -156,10 +161,12 @@ export namespace AriaClickable { * * ``` */ - 'aria-labelledby': string; + 'aria-labelledby'?: string; onClick: (event: MouseEvent) => void; }>; + const hiddenTriggerDisplayName = `${Namespace}.HiddenTrigger`; + /** * Provides an invisible button that fills the entire area of * `` @@ -221,16 +228,26 @@ export namespace AriaClickable { }; }, []); + useEffect(() => { + if (process.env.NODE_ENV === 'development') { + assert( + computeAccessibleName(assert(ref.current)) !== '', + `${hiddenTriggerDisplayName} child must have an accessible name` + ); + } + }); + return (