Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
66a13eb
Change the props name 'breakpoint' and add an expected type
HoonBaek May 16, 2023
e75b0c0
Use browserSupportMimeType for recording to MediaRecorder
HoonBaek May 16, 2023
eecfaec
Allow ReactNode to the children props of MenuItems comp
HoonBaek May 16, 2023
6ef2855
Accept nullable type of parentRef of EmojiListItem comp
HoonBaek May 16, 2023
262494f
Add util func: getEmojiUrl, getting url from emojiContainer
HoonBaek May 16, 2023
0d7be6f
Add a menu option to MobileContextMenu: Reply in Thread
HoonBaek May 16, 2023
20e33a2
Prevent event of mobile menu in MessageContent comp
HoonBaek May 16, 2023
0df5ee6
Add props to UserListItem: avatarSize, to set avatar size
HoonBaek May 16, 2023
3ad6bff
Create a component: MobileEmojisBottmSheet
HoonBaek May 16, 2023
0260337
Add MobileEmojisBottomSheet to the EmojiReactions comp
HoonBaek May 16, 2023
cd096b8
fix: useLongPress inside loop
sravan-s May 16, 2023
e10eb68
fix: stop propogation to useLongPress
sravan-s May 16, 2023
6fd5bf3
Not prevent event when selecting menu from Thread comp
HoonBaek May 17, 2023
b6298aa
Fix lint errors
HoonBaek May 17, 2023
ba60f93
Gets channel through props in MobileEmojisBottomSheet, instead of con…
HoonBaek May 17, 2023
29b85cd
Stop propagation in the ReactionItem
HoonBaek May 17, 2023
300a75a
Prevent opening multiple emojis menu in Mobile reactions
HoonBaek May 17, 2023
1624bf2
Rename MobileEmojisBottomSheet to ReactedMembersBottomSheet
HoonBaek May 18, 2023
4a74183
Rename className text: emoji-list to reacted-members
HoonBaek May 18, 2023
091b30a
Activate onClick EmojiReaction in the DesktopLayout
HoonBaek May 18, 2023
488c4a8
Add isMobile condition for displaying ReacedMembersBottomSheet
HoonBaek May 18, 2023
952c5b4
Create AddReactionBadgeItem and MobileEmojisBottomSheet
HoonBaek May 18, 2023
0c3fcdb
Remove unused props
HoonBaek May 18, 2023
449c702
Create SpaceFromTriggerType and apply it
HoonBaek May 23, 2023
331c485
Prevent non-nullable type to parentRef of EmojiListItems comp
HoonBaek May 23, 2023
81d35dd
Improve the readability of util func: getEmojiUrl
HoonBaek May 23, 2023
9e54c0f
Not use memo ReactorList inside of the ReactedMembersBottomSheet comp
HoonBaek May 23, 2023
ee104d2
Not use memo browserSupportMeimeType
HoonBaek May 23, 2023
73cc063
Rename setShowEmojisBottomSheet to setSelectedEmojiKey
HoonBaek May 23, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions scripts/index_d_ts
Original file line number Diff line number Diff line change
Expand Up @@ -1864,6 +1864,7 @@ declare module '@sendbird/uikit-react/ui/Dropdown' {
declare module '@sendbird/uikit-react/ui/EmojiReactions' {
import type { EmojiCategory } from '@sendbird/chat';
import type { FileMessage, UserMessage } from '@sendbird/chat/message';
import type { GroupChannel } from '@sendbird/chat/groupChannel';

interface EmojiContainer {
emojiCategories: Array<EmojiCategory>;
Expand All @@ -1873,6 +1874,7 @@ declare module '@sendbird/uikit-react/ui/EmojiReactions' {
className?: string | Array<string>;
userId: string;
message: UserMessage | FileMessage;
channel: GroupChannel;
emojiContainer: EmojiContainer;
memberNicknamesMap: Map<string, string>;
spaceFromTrigger?: Record<string, unknown>;
Expand Down
9 changes: 7 additions & 2 deletions src/hooks/VoiceRecorder/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import React, { createContext, useCallback, useContext, useEffect, useState } from 'react';
import {
BROWSER_SUPPORT_MIME_TYPE_LIST,
VOICE_MESSAGE_FILE_NAME,
VOICE_MESSAGE_MIME_TYPE,
VOICE_RECORDER_AUDIO_BITS,
VOICE_RECORDER_MIME_TYPE,
} from '../../utils/consts';
import useSendbirdStateContext from '../useSendbirdStateContext';

Expand Down Expand Up @@ -37,6 +37,11 @@ export const VoiceRecorderProvider = (props: VoiceRecorderProps): React.ReactEle
const [mediaRecorder, setMediaRecorder] = useState<MediaRecorder>(null);
const [isRecordable, setIsRecordable] = useState<boolean>(false);

const browserSupportMimeType = BROWSER_SUPPORT_MIME_TYPE_LIST.find((mimeType) => MediaRecorder.isTypeSupported(mimeType)) ?? '';
if (!browserSupportMimeType) {
logger.error('VoiceRecorder: Browser does not support mimeType', { mimmeTypes: BROWSER_SUPPORT_MIME_TYPE_LIST });
}

const [webAudioUtils, setWebAudioUtils] = useState(null);
useEffect(() => {
if (isVoiceMessageEnabled && !webAudioUtils) {
Expand All @@ -62,7 +67,7 @@ export const VoiceRecorderProvider = (props: VoiceRecorderProps): React.ReactEle
logger.info('VoiceRecorder: Succeeded getting media stream.', stream);
setIsRecordable(true);
const mediaRecorder = new MediaRecorder(stream, {
mimeType: VOICE_RECORDER_MIME_TYPE,
mimeType: browserSupportMimeType,
audioBitsPerSecond: VOICE_RECORDER_AUDIO_BITS,
});
mediaRecorder.ondataavailable = (e) => { // when recording stops
Expand Down
7 changes: 6 additions & 1 deletion src/hooks/useLongPress.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ interface PressHandlers<T> {
interface Options {
delay?: number;
shouldPreventDefault?: boolean;
shouldStopPropagation?: boolean;
}

interface UseLongPressType<T> {
Expand All @@ -67,6 +68,7 @@ export default function useLongPress<T>({
}: PressHandlers<T>, {
delay = DEFAULT_DURATION,
shouldPreventDefault = true,
shouldStopPropagation = false,
}: Options = {}): UseLongPressType<T> {
const { isMobile } = useMediaQueryContext();
const [longPressTriggered, setLongPressTriggered] = useState(false);
Expand All @@ -81,6 +83,9 @@ export default function useLongPress<T>({
...e,
};
setDragTriggered(false);
if (shouldStopPropagation) {
e.stopPropagation();
}
if (shouldPreventDefault && e.target) {
e.target.addEventListener(
'touchend',
Expand All @@ -96,7 +101,7 @@ export default function useLongPress<T>({
onLongPress(clonedEvent);
setLongPressTriggered(true);
}, delay);
}, [onLongPress, delay, shouldPreventDefault, isMobile]);
}, [onLongPress, delay, shouldPreventDefault, shouldStopPropagation, isMobile]);

const clear = useCallback((
e: React.MouseEvent<T> | React.TouchEvent<T>,
Expand Down
2 changes: 1 addition & 1 deletion src/lib/MediaQueryContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const MediaQueryContext = React.createContext({

export interface MediaQueryProviderProps {
children: React.ReactElement;
mediaQueryBreakPoint?: string;
mediaQueryBreakPoint?: string | boolean;
logger?: Logger;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@ export default function ParentMessageInfoItem({
<EmojiReactions
userId={currentUserId}
message={message}
channel={currentChannel}
isByMe={false}
emojiContainer={emojiContainer}
memberNicknamesMap={nicknamesMap}
Expand Down
2 changes: 2 additions & 0 deletions src/modules/Thread/components/ParentMessageInfo/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ export default function ParentMessageInfo({
setShowMobileMenu(true);
}
},
}, {
shouldPreventDefault: false,
});

// Edit message
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ export default function ThreadListItemContent({
setShowMobileMenu(true);
}
},
}, {
shouldPreventDefault: false,
});

return (
Expand Down Expand Up @@ -267,6 +269,7 @@ export default function ThreadListItemContent({
<EmojiReactions
userId={userId}
message={message as UserMessage | FileMessage}
channel={channel}
isByMe={isByMe}
emojiContainer={emojiContainer}
memberNicknamesMap={nicknamesMap}
Expand Down
8 changes: 8 additions & 0 deletions src/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ import type {
export type ReplyType = 'NONE' | 'QUOTE_REPLY' | 'THREAD';
export type Nullable<T> = T | null;

export type SpaceFromTriggerType = {
x: number,
y: number,
top?: number,
left?: number,
height?: number,
};

export interface UserListQuery {
hasNext?: boolean;
next(): Promise<Array<User>>;
Expand Down
66 changes: 36 additions & 30 deletions src/ui/ContextMenu/EmojiListItems.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import React, { ReactElement, ReactNode, RefObject, useEffect, useRef, useState
import { createPortal } from 'react-dom';

import SortByRow from '../SortByRow';
import { Nullable } from '../../types';

const defaultParentRect = { x: 0, y: 0, left: 0, top: 0, height: 0 };
type SpaceFromTrigger = { x: number, y: number };
type ReactionStyle = { left: number, top: number };
export interface EmojiListItemsProps {
Expand All @@ -13,15 +15,15 @@ export interface EmojiListItemsProps {
spaceFromTrigger?: SpaceFromTrigger;
}

const EmojiListItems = ({
export const EmojiListItems = ({
children,
parentRef,
parentContainRef,
spaceFromTrigger = { x: 0, y: 0 },
closeDropdown,
}: EmojiListItemsProps): ReactElement => {
}: EmojiListItemsProps): Nullable<ReactElement> => {
const [reactionStyle, setReactionStyle] = useState<ReactionStyle>({ left: 0, top: 0 });
const reactionRef = useRef(null);
const reactionRef: RefObject<HTMLUListElement> = useRef(null);

/* showParent & hideParent */
useEffect(() => {
Expand Down Expand Up @@ -52,7 +54,7 @@ const EmojiListItems = ({
useEffect(() => {
const spaceFromTriggerX = spaceFromTrigger?.x || 0;
const spaceFromTriggerY = spaceFromTrigger?.y || 0;
const parentRect = parentRef?.current?.getBoundingClientRect();
const parentRect = parentRef?.current?.getBoundingClientRect() ?? defaultParentRect;
const x = parentRect.x || parentRect.left;
const y = parentRect.y || parentRect.top;
const reactionStyle = {
Expand Down Expand Up @@ -84,33 +86,37 @@ const EmojiListItems = ({
}
}, []);

return (
createPortal(
<>
<div className="sendbird-dropdown__menu-backdrop" />
<ul
className="sendbird-dropdown__reaction-bar"
ref={reactionRef}
style={{
display: 'inline-block',
position: 'fixed',
left: `${Math.round(reactionStyle.left)}px`,
top: `${Math.round(reactionStyle.top)}px`,
}}
>
<SortByRow
className="sendbird-dropdown__reaction-bar__row"
maxItemCount={8}
itemWidth={44}
itemHeight={40}
const rootElement = document.getElementById('sendbird-emoji-list-portal');
if (rootElement) {
return (
createPortal(
<>
<div className="sendbird-dropdown__menu-backdrop" />
<ul
className="sendbird-dropdown__reaction-bar"
ref={reactionRef}
style={{
display: 'inline-block',
position: 'fixed',
left: `${Math.round(reactionStyle.left)}px`,
top: `${Math.round(reactionStyle.top)}px`,
}}
>
{children}
</SortByRow>
</ul>
</>,
document.getElementById('sendbird-emoji-list-portal'),
)
);
<SortByRow
className="sendbird-dropdown__reaction-bar__row"
maxItemCount={8}
itemWidth={44}
itemHeight={40}
>
{children}
</SortByRow>
</ul>
</>,
rootElement,
)
);
}
return null;
};

export default EmojiListItems;
2 changes: 1 addition & 1 deletion src/ui/ContextMenu/MenuItems.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ interface MenuItemsProps {
className?: string;
style?: Record<string, string>;
openLeft?: boolean;
children: React.ReactElement | Array<React.ReactElement>;
children: React.ReactElement | Array<React.ReactElement> | React.ReactNode;
parentRef: React.RefObject<HTMLElement>;
parentContainRef: React.RefObject<HTMLElement>;
closeDropdown: () => void;
Expand Down
42 changes: 42 additions & 0 deletions src/ui/EmojiReactions/AddReactionBadgeItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React, {
KeyboardEvent,
MouseEvent,
TouchEvent,
} from 'react';
import ReactionBadge from '../ReactionBadge';
import Icon, { IconColors, IconTypes } from '../Icon';
import useLongPress from '../../hooks/useLongPress';

export interface AddReactionBadgeItemProps {
onClick: (e: MouseEvent<HTMLDivElement> | KeyboardEvent<HTMLDivElement> | TouchEvent<HTMLDivElement>) => void;
}

export const AddReactionBadgeItem = ({
onClick,
}: AddReactionBadgeItemProps): React.ReactElement => {
const onlyClick = useLongPress({
onLongPress: () => { /* noop */ },
onClick,
}, {
shouldPreventDefault: true,
shouldStopPropagation: true,
});

return (
<div
className="sendbird-emoji-reactions__add-reaction-badge"
{...onlyClick}
>
<ReactionBadge
isAdd
>
<Icon
type={IconTypes.EMOJI_MORE}
fillColor={IconColors.ON_BACKGROUND_3}
width="20px"
height="20px"
/>
</ReactionBadge>
</div>
);
};
89 changes: 89 additions & 0 deletions src/ui/EmojiReactions/ReactionItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import React, { useContext } from 'react';

import { FileMessage, Reaction, UserMessage } from '@sendbird/chat/message';

import Tooltip from '../Tooltip';
import TooltipWrapper from '../TooltipWrapper';
import ReactionBadge from '../ReactionBadge';
import ImageRenderer from '../ImageRenderer';
import Icon, { IconTypes } from '../Icon';
import { useMediaQueryContext } from '../../lib/MediaQueryContext';
import useLongPress from '../../hooks/useLongPress';
import { LocalizationContext } from '../../lib/LocalizationContext';
import useSendbirdStateContext from '../../hooks/useSendbirdStateContext';
import { getEmojiTooltipString, isReactedBy } from '../../utils';
import { useMessageContext } from '../../modules/Message/context/MessageProvider';
import { Emoji } from '@sendbird/chat';

type Props = {
reaction: Reaction;
memberNicknamesMap: Map<string, string>;
setEmojiKey: React.Dispatch<React.SetStateAction<string>>;
toggleReaction?: (message: UserMessage | FileMessage, key: string, byMe: boolean) => void;
emojisMap: Map<string, Emoji>;
};

export default function ReactionItem({
reaction,
memberNicknamesMap,
setEmojiKey,
toggleReaction,
emojisMap,
}: Props) {
const store = useSendbirdStateContext();
const { isMobile } = useMediaQueryContext();
const messageStore = useMessageContext();
const message = messageStore?.message as UserMessage;
const { stringSet } = useContext(LocalizationContext);

const userId = store.config.userId;
const reactedByMe = isReactedBy(userId, reaction);

const handleOnClick = () => {
setEmojiKey('');
toggleReaction?.((message), reaction.key, reactedByMe);
};
const longPress = useLongPress({
onLongPress: () => {
setEmojiKey(reaction.key);
},
onClick: handleOnClick,
}, {
shouldPreventDefault: true,
shouldStopPropagation: true,
});

return (
<TooltipWrapper
className="sendbird-emoji-reactions__reaction-badge"
hoverTooltip={(reaction.userIds.length > 0) ? (
<Tooltip>
{getEmojiTooltipString(reaction, userId, memberNicknamesMap, stringSet)}
</Tooltip>
) : <></>}
>
<div
{...(
isMobile
? longPress
: { onClick: handleOnClick }
)}
>
<ReactionBadge
count={reaction.userIds.length}
selected={reactedByMe}
>
<ImageRenderer
circle
url={emojisMap.get(reaction?.key)?.url || ''}
width="20px"
height="20px"
defaultComponent={(
<Icon width="20px" height="20px" type={IconTypes.QUESTION} />
)}
/>
</ReactionBadge>
</div>
</TooltipWrapper>
);
}
Loading