Adds quick reactions to stories

This commit is contained in:
Josh Perez 2023-01-27 12:34:15 -05:00 committed by GitHub
parent e334490cf4
commit a88778b536
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 40 additions and 95 deletions

View file

@ -6065,6 +6065,10 @@
"message": "Reply", "message": "Reply",
"description": "Button label to reply to a story" "description": "Button label to reply to a story"
}, },
"icu:StoryViewer__reply-placeholder": {
"messageformat": "Reply to {firstName}",
"description": "Button label to reply to a story"
},
"StoryViewer__reply-group": { "StoryViewer__reply-group": {
"message": "Reply to Group", "message": "Reply to Group",
"description": "Button label to reply to a group story" "description": "Button label to reply to a group story"
@ -6134,7 +6138,7 @@
"description": "Title for replies tab" "description": "Title for replies tab"
}, },
"StoryViewsNRepliesModal__react": { "StoryViewsNRepliesModal__react": {
"message": "React to story", "message": "(deleted 01/25/23) React to story",
"description": "aria-label for reaction button" "description": "aria-label for reaction button"
}, },
"StoryViewsNRepliesModal__reacted": { "StoryViewsNRepliesModal__reacted": {

View file

@ -62,7 +62,7 @@ $footer-height: 36px;
&__composer { &__composer {
flex: 1; flex: 1;
margin-right: 16px; margin-top: 6px;
} }
&__emoji-button { &__emoji-button {
@ -114,16 +114,6 @@ $footer-height: 36px;
} }
} }
&__react {
@include button-reset;
@include color-svg(
'../images/icons/v2/add-reaction-outline-24.svg',
$color-white
);
height: 22px;
width: 22px;
}
&__view { &__view {
align-items: center; align-items: center;
display: flex; display: flex;
@ -246,6 +236,19 @@ $footer-height: 36px;
color: $color-gray-25; color: $color-gray-25;
margin: 160px 16px; margin: 160px 16px;
} }
.module-ReactionPickerPicker {
background: inherit;
border: none;
box-shadow: none;
justify-content: space-between;
width: 100%;
}
.module-emoji-picker {
bottom: 55px;
position: absolute;
}
} }
.Tabs.StoryViewsNRepliesModal__tabs { .Tabs.StoryViewsNRepliesModal__tabs {

View file

@ -601,7 +601,12 @@ export function StoryViewer({
/> />
)} )}
<div className="StoryViewer__protection StoryViewer__protection--top" /> <div className="StoryViewer__protection StoryViewer__protection--top" />
<div className="StoryViewer__container"> <div
className="StoryViewer__container"
onDoubleClick={() =>
setCurrentViewTarget(StoryViewTargetType.Replies)
}
>
<StoryImage <StoryImage
attachment={attachment} attachment={attachment}
firstName={firstName || title} firstName={firstName || title}
@ -908,7 +913,6 @@ export function StoryViewer({
{(currentViewTarget === StoryViewTargetType.Replies || {(currentViewTarget === StoryViewTargetType.Replies ||
currentViewTarget === StoryViewTargetType.Views) && ( currentViewTarget === StoryViewTargetType.Views) && (
<StoryViewsNRepliesModal <StoryViewsNRepliesModal
conversationTitle={group?.title ?? title}
authorTitle={firstName || title} authorTitle={firstName || title}
canReply={Boolean(canReply)} canReply={Boolean(canReply)}
getPreferredBadge={getPreferredBadge} getPreferredBadge={getPreferredBadge}
@ -942,7 +946,6 @@ export function StoryViewer({
replies={replies} replies={replies}
skinTone={skinTone} skinTone={skinTone}
sortedGroupMembers={group?.sortedGroupMembers} sortedGroupMembers={group?.sortedGroupMembers}
storyPreviewAttachment={attachment}
views={views} views={views}
viewTarget={currentViewTarget} viewTarget={currentViewTarget}
onChangeViewTarget={setCurrentViewTarget} onChangeViewTarget={setCurrentViewTarget}

View file

@ -8,11 +8,9 @@ import { useArgs } from '@storybook/addons';
import type { PropsType } from './StoryViewsNRepliesModal'; import type { PropsType } from './StoryViewsNRepliesModal';
import * as durations from '../util/durations'; import * as durations from '../util/durations';
import enMessages from '../../_locales/en/messages.json'; import enMessages from '../../_locales/en/messages.json';
import { IMAGE_JPEG } from '../types/MIME';
import { SendStatus } from '../messages/MessageSendState'; import { SendStatus } from '../messages/MessageSendState';
import { StoryViewsNRepliesModal } from './StoryViewsNRepliesModal'; import { StoryViewsNRepliesModal } from './StoryViewsNRepliesModal';
import { UUID } from '../types/UUID'; import { UUID } from '../types/UUID';
import { fakeAttachment } from '../test-both/helpers/fakeAttachment';
import { getDefaultConversation } from '../test-both/helpers/getDefaultConversation'; import { getDefaultConversation } from '../test-both/helpers/getDefaultConversation';
import { setupI18n } from '../util/setupI18n'; import { setupI18n } from '../util/setupI18n';
import { StoryViewTargetType } from '../types/Stories'; import { StoryViewTargetType } from '../types/Stories';
@ -54,17 +52,6 @@ export default {
replies: { replies: {
defaultValue: [], defaultValue: [],
}, },
storyPreviewAttachment: {
defaultValue: fakeAttachment({
thumbnail: {
contentType: IMAGE_JPEG,
height: 64,
objectUrl: '/fixtures/nathan-anderson-316188-unsplash.jpg',
path: '',
width: 40,
},
}),
},
views: { views: {
defaultValue: [], defaultValue: [],
}, },

View file

@ -9,10 +9,8 @@ import React, {
useState, useState,
} from 'react'; } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { usePopper } from 'react-popper';
import { noop } from 'lodash'; import { noop } from 'lodash';
import type { AttachmentType } from '../types/Attachment';
import type { DraftBodyRangesType, LocalizerType } from '../types/Util'; import type { DraftBodyRangesType, LocalizerType } from '../types/Util';
import type { ConversationType } from '../state/ducks/conversations'; import type { ConversationType } from '../state/ducks/conversations';
import type { EmojiPickDataType } from './emoji/EmojiPicker'; import type { EmojiPickDataType } from './emoji/EmojiPicker';
@ -29,14 +27,12 @@ import { Emojify } from './conversation/Emojify';
import { Message, TextDirection } from './conversation/Message'; import { Message, TextDirection } from './conversation/Message';
import { MessageTimestamp } from './conversation/MessageTimestamp'; import { MessageTimestamp } from './conversation/MessageTimestamp';
import { Modal } from './Modal'; import { Modal } from './Modal';
import { Quote } from './conversation/Quote';
import { ReactionPicker } from './conversation/ReactionPicker'; import { ReactionPicker } from './conversation/ReactionPicker';
import { Tabs } from './Tabs'; import { Tabs } from './Tabs';
import { Theme } from '../util/theme'; import { Theme } from '../util/theme';
import { ThemeType } from '../types/Util'; import { ThemeType } from '../types/Util';
import { WidthBreakpoint } from './_util'; import { WidthBreakpoint } from './_util';
import { getAvatarColor } from '../types/Colors'; import { getAvatarColor } from '../types/Colors';
import { getStoryReplyText } from '../util/getStoryReplyText';
import { shouldNeverBeCalled } from '../util/shouldNeverBeCalled'; import { shouldNeverBeCalled } from '../util/shouldNeverBeCalled';
import { ContextMenu } from './ContextMenu'; import { ContextMenu } from './ContextMenu';
import { ConfirmationDialog } from './ConfirmationDialog'; import { ConfirmationDialog } from './ConfirmationDialog';
@ -79,7 +75,6 @@ export enum StoryViewsNRepliesTab {
} }
export type PropsType = { export type PropsType = {
conversationTitle: string;
authorTitle: string; authorTitle: string;
canReply: boolean; canReply: boolean;
getPreferredBadge: PreferredBadgeSelectorType; getPreferredBadge: PreferredBadgeSelectorType;
@ -104,7 +99,6 @@ export type PropsType = {
replies: ReadonlyArray<ReplyType>; replies: ReadonlyArray<ReplyType>;
skinTone?: number; skinTone?: number;
sortedGroupMembers?: ReadonlyArray<ConversationType>; sortedGroupMembers?: ReadonlyArray<ConversationType>;
storyPreviewAttachment?: AttachmentType;
views: ReadonlyArray<StorySendStateType>; views: ReadonlyArray<StorySendStateType>;
viewTarget: StoryViewTargetType; viewTarget: StoryViewTargetType;
onChangeViewTarget: (target: StoryViewTargetType) => unknown; onChangeViewTarget: (target: StoryViewTargetType) => unknown;
@ -113,7 +107,6 @@ export type PropsType = {
}; };
export function StoryViewsNRepliesModal({ export function StoryViewsNRepliesModal({
conversationTitle,
authorTitle, authorTitle,
canReply, canReply,
getPreferredBadge, getPreferredBadge,
@ -134,7 +127,6 @@ export function StoryViewsNRepliesModal({
replies, replies,
skinTone, skinTone,
sortedGroupMembers, sortedGroupMembers,
storyPreviewAttachment,
views, views,
viewTarget, viewTarget,
onChangeViewTarget, onChangeViewTarget,
@ -153,7 +145,6 @@ export function StoryViewsNRepliesModal({
const shouldScrollToBottomRef = useRef(true); const shouldScrollToBottomRef = useRef(true);
const bottomRef = useRef<HTMLDivElement>(null); const bottomRef = useRef<HTMLDivElement>(null);
const [messageBodyText, setMessageBodyText] = useState(''); const [messageBodyText, setMessageBodyText] = useState('');
const [showReactionPicker, setShowReactionPicker] = useState(false);
const currentTab = useMemo<StoryViewsNRepliesTab>(() => { const currentTab = useMemo<StoryViewsNRepliesTab>(() => {
return viewTarget === StoryViewTargetType.Replies return viewTarget === StoryViewTargetType.Replies
@ -185,17 +176,6 @@ export function StoryViewsNRepliesModal({
[inputApiRef, onUseEmoji] [inputApiRef, onUseEmoji]
); );
const [referenceElement, setReferenceElement] =
useState<HTMLButtonElement | null>(null);
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(
null
);
const { styles, attributes } = usePopper(referenceElement, popperElement, {
placement: 'top-start',
strategy: 'fixed',
});
let composerElement: JSX.Element | undefined; let composerElement: JSX.Element | undefined;
useLayoutEffect(() => { useLayoutEffect(() => {
@ -218,22 +198,18 @@ export function StoryViewsNRepliesModal({
} else if (canReply) { } else if (canReply) {
composerElement = ( composerElement = (
<> <>
{!group && ( <ReactionPicker
<Quote i18n={i18n}
authorTitle={authorTitle} onPick={emoji => {
conversationColor="ultramarine" if (!group) {
conversationTitle={conversationTitle} onClose();
i18n={i18n} }
isFromMe={false} onReact(emoji);
isGiftBadge={false} }}
isStoryReply onSetSkinTone={onSetSkinTone}
isViewOnce={false} preferredReactionEmoji={preferredReactionEmoji}
moduleClassName="StoryViewsNRepliesModal__quote" renderEmojiPicker={renderEmojiPicker}
rawAttachment={storyPreviewAttachment} />
referencedMessageNotFound={false}
text={getStoryReplyText(i18n, storyPreviewAttachment)}
/>
)}
<div className="StoryViewsNRepliesModal__compose-container"> <div className="StoryViewsNRepliesModal__compose-container">
<div className="StoryViewsNRepliesModal__composer"> <div className="StoryViewsNRepliesModal__composer">
<CompositionInput <CompositionInput
@ -255,7 +231,9 @@ export function StoryViewsNRepliesModal({
placeholder={ placeholder={
group group
? i18n('StoryViewer__reply-group') ? i18n('StoryViewer__reply-group')
: i18n('StoryViewer__reply') : i18n('icu:StoryViewer__reply-placeholder', {
firstName: authorTitle,
})
} }
sortedGroupMembers={sortedGroupMembers} sortedGroupMembers={sortedGroupMembers}
theme={ThemeType.dark} theme={ThemeType.dark}
@ -271,36 +249,6 @@ export function StoryViewsNRepliesModal({
/> />
</CompositionInput> </CompositionInput>
</div> </div>
<button
aria-label={i18n('StoryViewsNRepliesModal__react')}
className="StoryViewsNRepliesModal__react"
onClick={() => {
setShowReactionPicker(!showReactionPicker);
}}
ref={setReferenceElement}
type="button"
/>
{showReactionPicker && (
<div
ref={setPopperElement}
style={styles.popper}
{...attributes.popper}
>
<ReactionPicker
i18n={i18n}
onClose={() => {
setShowReactionPicker(false);
}}
onPick={emoji => {
setShowReactionPicker(false);
onReact(emoji);
}}
onSetSkinTone={onSetSkinTone}
preferredReactionEmoji={preferredReactionEmoji}
renderEmojiPicker={renderEmojiPicker}
/>
</div>
)}
</div> </div>
</> </>
); );