Pinned messages UI fixes

This commit is contained in:
Jamie
2026-01-13 12:01:07 -08:00
committed by GitHub
parent a27a87a934
commit 560224f516
14 changed files with 133 additions and 187 deletions

View File

@@ -11,12 +11,9 @@ export default {
} satisfies Meta<Props>;
const createProps = (overrideProps: Partial<Props> = {}): Props => ({
direction: overrideProps.direction || 'outgoing',
expirationLength: overrideProps.expirationLength || 30 * 1000,
expirationTimestamp:
overrideProps.expirationTimestamp || Date.now() + 30 * 1000,
withImageNoCaption: overrideProps.withImageNoCaption || false,
withSticker: overrideProps.withSticker || false,
});
export const _30Seconds = (): React.JSX.Element => {
@@ -51,38 +48,6 @@ export function Expired(): React.JSX.Element {
return <ExpireTimer {...props} />;
}
export function Sticker(): React.JSX.Element {
const props = createProps({
withSticker: true,
});
return <ExpireTimer {...props} />;
}
export function ImageNoCaption(): React.JSX.Element {
const props = createProps({
withImageNoCaption: true,
});
return (
<div style={{ backgroundColor: 'darkgreen' }}>
<ExpireTimer {...props} />
</div>
);
}
export function Incoming(): React.JSX.Element {
const props = createProps({
direction: 'incoming',
});
return (
<div style={{ backgroundColor: 'darkgreen' }}>
<ExpireTimer {...props} />
</div>
);
}
export function ExpirationTooFarOut(): React.JSX.Element {
const props = createProps({
expirationTimestamp: Date.now() + 150 * 1000,

View File

@@ -6,22 +6,14 @@ import classNames from 'classnames';
import { getIncrement, getTimerBucket } from '../../util/timer.std.js';
export type Props = {
direction?: 'incoming' | 'outgoing';
export type Props = Readonly<{
expirationLength: number;
expirationTimestamp?: number;
isOutlineOnlyBubble?: boolean;
withImageNoCaption?: boolean;
withSticker?: boolean;
};
}>;
export function ExpireTimer({
direction,
expirationLength,
expirationTimestamp,
isOutlineOnlyBubble,
withImageNoCaption,
withSticker,
}: Props): React.JSX.Element {
const [, forceUpdate] = useReducer(() => ({}), {});
@@ -40,13 +32,7 @@ export function ExpireTimer({
<div
className={classNames(
'module-expire-timer',
`module-expire-timer--${bucket}`,
direction ? `module-expire-timer--${direction}` : null,
isOutlineOnlyBubble ? 'module-expire-timer--outline-only-bubble' : null,
direction && withImageNoCaption
? 'module-expire-timer--with-image-no-caption'
: null,
withSticker ? 'module-expire-timer--with-sticker' : null
`module-expire-timer--${bucket}`
)}
/>
);

View File

@@ -220,12 +220,8 @@ export const MessageMetadata = forwardRef<HTMLDivElement, Readonly<PropsType>>(
) : null}
{expirationLength ? (
<ExpireTimer
direction={metadataDirection}
isOutlineOnlyBubble={isOutlineOnlyBubble}
expirationLength={expirationLength}
expirationTimestamp={expirationTimestamp}
withImageNoCaption={withImageNoCaption}
withSticker={isSticker}
/>
) : null}
{textPending ? (

View File

@@ -77,7 +77,7 @@ const defaultMessageProps: TimelineMessagesProps = {
canEditMessage: true,
canEndPoll: false,
canForward: true,
canPinMessages: true,
canPinMessage: true,
canReact: true,
canReply: true,
canRetry: true,

View File

@@ -47,7 +47,7 @@ function mockMessageTimelineItem(
canEditMessage: true,
canEndPoll: false,
canForward: true,
canPinMessages: true,
canPinMessage: true,
canReact: true,
canReply: true,
canRetry: true,

View File

@@ -239,7 +239,7 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
canCopy: true,
canEditMessage: true,
canEndPoll: overrideProps.direction === 'outgoing',
canPinMessages: overrideProps.canPinMessages ?? true,
canPinMessage: overrideProps.canPinMessage ?? true,
canReact: true,
canReply: true,
canDownload: true,

View File

@@ -54,7 +54,7 @@ export type PropsData = {
canRetryDeleteForEveryone: boolean;
canReact: boolean;
canReply: boolean;
canPinMessages: boolean;
canPinMessage: boolean;
hasMaxPinnedMessages: boolean;
selectedReaction?: string;
isTargeted?: boolean;
@@ -114,7 +114,7 @@ export function TimelineMessage(props: Props): React.JSX.Element {
canReply,
canRetry,
canRetryDeleteForEveryone,
canPinMessages,
canPinMessage,
containerElementRef,
containerWidthBreakpoint,
conversationId,
@@ -368,11 +368,9 @@ export function TimelineMessage(props: Props): React.JSX.Element {
});
}}
onPinMessage={
canPinMessages && !isPinned ? handleOpenPinMessageDialog : null
}
onUnpinMessage={
canPinMessages && isPinned ? handleUnpinMessage : null
canPinMessage && !isPinned ? handleOpenPinMessageDialog : null
}
onUnpinMessage={canPinMessage && isPinned ? handleUnpinMessage : null}
onMoreInfo={() =>
pushPanelForConversation({
type: PanelType.MessageDetails,
@@ -388,7 +386,7 @@ export function TimelineMessage(props: Props): React.JSX.Element {
canCopy,
canEditMessage,
canForward,
canPinMessages,
canPinMessage,
canRetry,
canSelect,
canEndPoll,

View File

@@ -182,10 +182,28 @@ export function Variants(): React.JSX.Element {
title="Poll"
message={{ poll: { question: `${SHORT_TEXT}?` } }}
/>
<Variant title="Sticker" message={{ sticker: true }} />
<Variant title="Contact" message={{ contact: { name: 'Tyler' } }} />
<Variant
title="Sticker"
message={{
sticker: true,
attachment: { type: 'image', url: IMAGE_URL },
}}
/>
<Variant
title="Contact"
message={{
contact: { name: 'Tyler' },
attachment: { type: 'file', name: '' },
}}
/>
<Variant title="Payment" message={{ payment: true }} />
<Variant title="View-Once Media" message={{ viewOnceMedia: true }} />
<Variant
title="View-Once Media"
message={{
viewOnceMedia: true,
attachment: { type: 'image', url: IMAGE_URL },
}}
/>
</Stack>
);
}

View File

@@ -191,7 +191,7 @@ function TabsList(props: {
return (
<AriaClickable.SubWidget>
<Tabs.List className={tw('flex h-full flex-col')}>
{props.pins.toReversed().map((pin, pinIndex) => {
{props.pins.map((pin, pinIndex) => {
return (
<TabTrigger
key={pin.id}
@@ -395,7 +395,39 @@ function getMessagePreview(i18n: LocalizerType, message: PinMessage): Preview {
text ??= <MessageTextPreview i18n={i18n} text={message.text} />;
}
// 3. And everything else...
// 3. Check specific types of messages (before checking attachments)
if (message.payment) {
text ??= i18n('icu:PinnedMessagesBar__MessagePreview__Text--Payment');
icon ??= {
symbol: 'creditcard',
label: i18n(
'icu:PinnedMessagesBar__MessagePreview__SymbolLabel--Payment'
),
};
} else if (message.poll != null) {
text ??= <UserText text={message.poll.question} />;
icon ??= {
symbol: 'poll',
label: i18n('icu:PinnedMessagesBar__MessagePreview__SymbolLabel--Poll'),
};
} else if (message.sticker) {
text ??= i18n('icu:PinnedMessagesBar__MessagePreview__Text--Sticker');
icon ??= {
symbol: 'sticker',
label: i18n(
'icu:PinnedMessagesBar__MessagePreview__SymbolLabel--Sticker'
),
};
} else if (message.contact != null) {
const name = message.contact.name ?? i18n('icu:unknownContact');
text ??= <UserText text={name} />;
icon ??= {
symbol: 'person-circle',
label: name,
};
}
// 4. Check attachments (make sure to check types like sticker/contact first)
if (message.attachment != null) {
if (message.attachment.type === 'image') {
text ??= i18n('icu:PinnedMessagesBar__MessagePreview__Text--Photo');
@@ -426,35 +458,6 @@ function getMessagePreview(i18n: LocalizerType, message: PinMessage): Preview {
} else {
throw missingCaseError(message.attachment);
}
} else if (message.contact != null) {
const name = message.contact.name ?? i18n('icu:unknownContact');
text ??= <UserText text={name} />;
icon ??= {
symbol: 'person-circle',
label: name,
};
} else if (message.payment) {
text ??= i18n('icu:PinnedMessagesBar__MessagePreview__Text--Payment');
icon ??= {
symbol: 'creditcard',
label: i18n(
'icu:PinnedMessagesBar__MessagePreview__SymbolLabel--Payment'
),
};
} else if (message.poll != null) {
text ??= <UserText text={message.poll.question} />;
icon ??= {
symbol: 'poll',
label: i18n('icu:PinnedMessagesBar__MessagePreview__SymbolLabel--Poll'),
};
} else if (message.sticker) {
text ??= i18n('icu:PinnedMessagesBar__MessagePreview__Text--Sticker');
icon ??= {
symbol: 'sticker',
label: i18n(
'icu:PinnedMessagesBar__MessagePreview__SymbolLabel--Sticker'
),
};
}
return { icon, text };