diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 353a78e56f..9208b09b6d 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -1682,6 +1682,10 @@ "messageformat": "Pinned messages", "description": "Conversation > Pinned messages panel (view all) > Title" }, + "icu:PinnedMessagesPanel__UnpinAllMessages": { + "messageformat": "Unpin all messages", + "description": "Conversation > Pinned messages panel (view all) > Unpin all messages button" + }, "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/stylesheets/components/ConversationPanel.scss b/stylesheets/components/ConversationPanel.scss index a03a297abd..c98e1ebfe7 100644 --- a/stylesheets/components/ConversationPanel.scss +++ b/stylesheets/components/ConversationPanel.scss @@ -10,7 +10,6 @@ height: 100%; inset-inline-start: 0; - overflow-y: auto; position: absolute; top: 0; width: 100%; @@ -28,11 +27,12 @@ // Used for centering EmptyState in All Media view position: relative; + overflow-y: auto; flex-grow: 1; - padding-top: calc( - #{variables.$header-height} + var(--title-bar-drag-area-height) - ); - padding-inline: 24px; + + &--padding { + padding-inline: 24px; + } } &__header { @@ -44,7 +44,6 @@ #{variables.$header-height} + var(--title-bar-drag-area-height) ); padding-top: var(--title-bar-drag-area-height); - position: fixed; width: 100%; z-index: variables.$z-index-base; diff --git a/ts/components/conversation/pinned-messages/PinnedMessagesPanel.dom.tsx b/ts/components/conversation/pinned-messages/PinnedMessagesPanel.dom.tsx index ce7740af8f..44f4ac36f5 100644 --- a/ts/components/conversation/pinned-messages/PinnedMessagesPanel.dom.tsx +++ b/ts/components/conversation/pinned-messages/PinnedMessagesPanel.dom.tsx @@ -1,6 +1,14 @@ // Copyright 2025 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import React, { Fragment, memo, useMemo, useRef, useState } from 'react'; +import type { ForwardedRef, ReactNode } from 'react'; +import React, { + forwardRef, + Fragment, + memo, + useMemo, + useRef, + useState, +} from 'react'; import { useLayoutEffect } from '@react-aria/utils'; import type { LocalizerType } from '../../../types/I18N.std.js'; import type { ConversationType } from '../../../state/ducks/conversations.preload.js'; @@ -16,6 +24,8 @@ import { getWidthBreakpoint } from '../../../util/timelineUtil.std.js'; import { strictAssert } from '../../../util/assert.std.js'; import { useSizeObserver } from '../../../hooks/useSizeObserver.dom.js'; import { MessageInteractivity } from '../Message.dom.js'; +import { tw } from '../../../axo/tw.dom.js'; +import { AxoButton } from '../../../axo/AxoButton.dom.js'; export type PinnedMessagesPanelProps = Readonly<{ i18n: LocalizerType; @@ -27,6 +37,7 @@ export type PinnedMessagesPanelProps = Readonly<{ export const PinnedMessagesPanel = memo(function PinnedMessagesPanel( props: PinnedMessagesPanelProps ) { + const { i18n } = props; const containerElementRef = useRef(null); const [containerWidthBreakpoint, setContainerWidthBreakpoint] = useState( WidthBreakpoint.Wide @@ -42,6 +53,44 @@ export const PinnedMessagesPanel = memo(function PinnedMessagesPanel( setContainerWidthBreakpoint(getWidthBreakpoint(size.width)); }); + return ( +
+ + {props.pinnedMessages.map((pinnedMessage, pinnedMessageIndex) => { + const next = props.pinnedMessages[pinnedMessageIndex + 1]; + const prev = props.pinnedMessages[pinnedMessageIndex - 1]; + return ( + + {props.renderTimelineItem({ + containerElementRef, + containerWidthBreakpoint, + conversationId: props.conversation.id, + interactivity: MessageInteractivity.Embed, + isBlocked: props.conversation.isBlocked ?? false, + isGroup: props.conversation.type === 'group', + isOldestTimelineItem: pinnedMessageIndex === 0, + messageId: pinnedMessage.messageId, + nextMessageId: next?.messageId, + previousMessageId: prev?.messageId, + unreadIndicatorPlacement: undefined, + })} + + ); + })} + +
+ + {i18n('icu:PinnedMessagesPanel__UnpinAllMessages')} + +
+
+ ); +}); + +const ScrollArea = forwardRef(function ScrollArea( + props: { children: ReactNode }, + ref: ForwardedRef +) { const scrollerLock = useMemo(() => { return createScrollerLock('PinnedMessagesPanel', () => { // noop - we probably don't need to do anything here because the only @@ -51,31 +100,13 @@ export const PinnedMessagesPanel = memo(function PinnedMessagesPanel( return ( + + -
+
- {props.pinnedMessages.map((pinnedMessage, pinnedMessageIndex) => { - const next = props.pinnedMessages[pinnedMessageIndex + 1]; - const prev = props.pinnedMessages[pinnedMessageIndex - 1]; - return ( - - {props.renderTimelineItem({ - containerElementRef, - containerWidthBreakpoint, - conversationId: props.conversation.id, - interactivity: MessageInteractivity.Embed, - isBlocked: props.conversation.isBlocked ?? false, - isGroup: props.conversation.type === 'group', - isOldestTimelineItem: pinnedMessageIndex === 0, - messageId: pinnedMessage.messageId, - nextMessageId: next?.messageId, - previousMessageId: prev?.messageId, - unreadIndicatorPlacement: undefined, - })} - - ); - })} + {props.children}
diff --git a/ts/state/smart/ConversationPanel.preload.tsx b/ts/state/smart/ConversationPanel.preload.tsx index 90e45301e8..db223b5972 100644 --- a/ts/state/smart/ConversationPanel.preload.tsx +++ b/ts/state/smart/ConversationPanel.preload.tsx @@ -11,6 +11,7 @@ import React, { useState, } from 'react'; import { useSelector } from 'react-redux'; +import classNames from 'classnames'; import type { PanelRenderType } from '../../types/Panels.std.js'; import { createLogger } from '../../logging/log.std.js'; import { PanelType } from '../../types/Panels.std.js'; @@ -322,7 +323,14 @@ const PanelContainer = forwardRef<
)} -
+