Adds quick reactions to stories
This commit is contained in:
parent
e334490cf4
commit
a88778b536
5 changed files with 40 additions and 95 deletions
|
@ -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": {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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: [],
|
||||||
},
|
},
|
||||||
|
|
|
@ -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>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
Loading…
Add table
Reference in a new issue