Update hoverable menu on Chat Messages
This commit is contained in:
parent
3ca491c03f
commit
17684571af
|
@ -74,7 +74,7 @@ const EmojiSelector: React.FC<IEmojiSelector> = ({
|
||||||
{
|
{
|
||||||
name: 'offset',
|
name: 'offset',
|
||||||
options: {
|
options: {
|
||||||
offset: [-10, 0],
|
offset: [-10, 12],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { defineMessages, useIntl } from 'react-intl';
|
||||||
|
|
||||||
import { openModal } from 'soapbox/actions/modals';
|
import { openModal } from 'soapbox/actions/modals';
|
||||||
import { initReport } from 'soapbox/actions/reports';
|
import { initReport } from 'soapbox/actions/reports';
|
||||||
import { HStack, Icon, IconButton, Stack, Text } from 'soapbox/components/ui';
|
import { HStack, Icon, Stack, Text } from 'soapbox/components/ui';
|
||||||
import DropdownMenuContainer from 'soapbox/containers/dropdown-menu-container';
|
import DropdownMenuContainer from 'soapbox/containers/dropdown-menu-container';
|
||||||
import emojify from 'soapbox/features/emoji/emoji';
|
import emojify from 'soapbox/features/emoji/emoji';
|
||||||
import Bundle from 'soapbox/features/ui/components/bundle';
|
import Bundle from 'soapbox/features/ui/components/bundle';
|
||||||
|
@ -189,181 +189,199 @@ const ChatMessage = (props: IChatMessage) => {
|
||||||
}, [chatMessage, chat]);
|
}, [chatMessage, chat]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='px-4 py-2'>
|
<div
|
||||||
<div key={chatMessage.id} className='group' data-testid='chat-message'>
|
className={
|
||||||
<Stack
|
clsx({
|
||||||
space={1.5}
|
'group relative px-4 py-2 hover:bg-gray-200/40 dark:hover:bg-gray-800/40': true,
|
||||||
|
'bg-gray-200/40 dark:bg-gray-800/40': isMenuOpen || isReactionSelectorOpen,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
data-testid='chat-message'
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
clsx({
|
||||||
|
'p-1 flex items-center space-x-0.5 z-10 absolute opacity-0 transition-opacity group-hover:opacity-100 focus:opacity-100 rounded-md shadow-lg bg-white dark:bg-gray-900 dark:ring-2 dark:ring-primary-700': true,
|
||||||
|
'top-2 right-2': !isMyMessage,
|
||||||
|
'top-2 left-2': isMyMessage,
|
||||||
|
'!opacity-100': isMenuOpen || isReactionSelectorOpen,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{!features.chatEmojiReactions ? (
|
||||||
|
<ChatMessageReactionWrapper
|
||||||
|
onOpen={setIsReactionSelectorOpen}
|
||||||
|
onSelect={(emoji) => createReaction.mutate({ emoji, messageId: chatMessage.id, chatMessage })}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
title={intl.formatMessage(messages.more)}
|
||||||
|
className={clsx({
|
||||||
|
'p-1.5 hover:bg-gray-200 dark:hover:bg-gray-800 rounded-md text-gray-600 dark:text-gray-600 hover:text-gray-700 dark:hover:text-gray-500 focus:text-gray-700 dark:focus:text-gray-500 focus:ring-0': true,
|
||||||
|
'!text-gray-700 dark:!text-gray-500': isReactionSelectorOpen,
|
||||||
|
})}
|
||||||
|
data-testid='chat-message-menu'
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
src={require('@tabler/icons/mood-smile.svg')}
|
||||||
|
className='h-4 w-4'
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</ChatMessageReactionWrapper>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{menu.length > 0 && (
|
||||||
|
<DropdownMenuContainer
|
||||||
|
items={menu}
|
||||||
|
onOpen={() => setIsMenuOpen(true)}
|
||||||
|
onClose={() => setIsMenuOpen(false)}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
title={intl.formatMessage(messages.more)}
|
||||||
|
className={clsx({
|
||||||
|
'p-1.5 hover:bg-gray-200 dark:hover:bg-gray-800 rounded-md text-gray-600 dark:text-gray-600 hover:text-gray-700 dark:hover:text-gray-500 focus:text-gray-700 dark:focus:text-gray-500 focus:ring-0': true,
|
||||||
|
'!text-gray-700 dark:!text-gray-500': isMenuOpen,
|
||||||
|
})}
|
||||||
|
data-testid='chat-message-menu'
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
src={require('@tabler/icons/dots.svg')}
|
||||||
|
className='h-4 w-4'
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</DropdownMenuContainer>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Stack
|
||||||
|
space={1.5}
|
||||||
|
className={clsx({
|
||||||
|
'ml-auto': isMyMessage,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<HStack
|
||||||
|
alignItems='center'
|
||||||
|
justifyContent={isMyMessage ? 'end' : 'start'}
|
||||||
|
className={clsx({
|
||||||
|
'opacity-50': chatMessage.pending,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<Stack
|
||||||
|
space={0.5}
|
||||||
|
className={clsx({
|
||||||
|
'max-w-[85%]': true,
|
||||||
|
'flex-1': !!chatMessage.media_attachments.size,
|
||||||
|
'order-3': isMyMessage,
|
||||||
|
'order-1': !isMyMessage,
|
||||||
|
})}
|
||||||
|
alignItems={isMyMessage ? 'end' : 'start'}
|
||||||
|
>
|
||||||
|
{maybeRenderMedia(chatMessage)}
|
||||||
|
|
||||||
|
{content && (
|
||||||
|
<HStack alignItems='bottom' className='max-w-full'>
|
||||||
|
<div
|
||||||
|
title={getFormattedTimestamp(chatMessage)}
|
||||||
|
className={
|
||||||
|
clsx({
|
||||||
|
'text-ellipsis break-words relative rounded-md py-2 px-3 max-w-full space-y-2 [&_.mention]:underline': true,
|
||||||
|
'rounded-tr-sm': (!!chatMessage.media_attachments.size) && isMyMessage,
|
||||||
|
'rounded-tl-sm': (!!chatMessage.media_attachments.size) && !isMyMessage,
|
||||||
|
'[&_.mention]:text-primary-600 dark:[&_.mention]:text-accent-blue': !isMyMessage,
|
||||||
|
'[&_.mention]:text-white dark:[&_.mention]:white': isMyMessage,
|
||||||
|
'bg-primary-500 text-white': isMyMessage,
|
||||||
|
'bg-gray-200 dark:bg-gray-800 text-gray-900 dark:text-gray-100': !isMyMessage,
|
||||||
|
'!bg-transparent !p-0 emoji-lg': isOnlyEmoji,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
ref={setBubbleRef}
|
||||||
|
tabIndex={0}
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
size='sm'
|
||||||
|
theme='inherit'
|
||||||
|
className='break-word-nested'
|
||||||
|
dangerouslySetInnerHTML={{ __html: content }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</HStack>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</HStack>
|
||||||
|
|
||||||
|
{(features.chatEmojiReactions && chatMessage.emoji_reactions) ? (
|
||||||
|
<div
|
||||||
|
className={clsx({
|
||||||
|
'space-y-1': true,
|
||||||
|
'ml-auto': isMyMessage,
|
||||||
|
'mr-auto': !isMyMessage,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{emojiReactionRows?.map((emojiReactionRow: any, idx: number) => (
|
||||||
|
<HStack
|
||||||
|
key={idx}
|
||||||
|
className={
|
||||||
|
clsx({
|
||||||
|
'flex items-center gap-1': true,
|
||||||
|
'flex-row-reverse': isMyMessage,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{emojiReactionRow.map((emojiReaction: any, idx: number) => (
|
||||||
|
<ChatMessageReaction
|
||||||
|
key={idx}
|
||||||
|
emojiReaction={emojiReaction}
|
||||||
|
onAddReaction={(emoji) => createReaction.mutate({ emoji, messageId: chatMessage.id, chatMessage })}
|
||||||
|
onRemoveReaction={(emoji) => deleteReaction.mutate({ emoji, messageId: chatMessage.id })}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</HStack>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<HStack
|
||||||
|
alignItems='center'
|
||||||
|
space={2}
|
||||||
className={clsx({
|
className={clsx({
|
||||||
'ml-auto': isMyMessage,
|
'ml-auto': isMyMessage,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<HStack
|
<div
|
||||||
alignItems='center'
|
|
||||||
justifyContent={isMyMessage ? 'end' : 'start'}
|
|
||||||
className={clsx({
|
className={clsx({
|
||||||
'opacity-50': chatMessage.pending,
|
'text-right': isMyMessage,
|
||||||
|
'order-2': !isMyMessage,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{menu.length > 0 && (
|
<span className='flex items-center space-x-1.5'>
|
||||||
<DropdownMenuContainer
|
<Text theme='muted' size='xs'>
|
||||||
items={menu}
|
{intl.formatTime(chatMessage.created_at)}
|
||||||
onOpen={() => setIsMenuOpen(true)}
|
</Text>
|
||||||
onClose={() => setIsMenuOpen(false)}
|
|
||||||
>
|
|
||||||
<IconButton
|
|
||||||
src={require('@tabler/icons/dots.svg')}
|
|
||||||
title={intl.formatMessage(messages.more)}
|
|
||||||
className={clsx({
|
|
||||||
'opacity-0 transition-opacity group-hover:opacity-100 focus:opacity-100 flex text-gray-600 dark:text-gray-600 hover:text-gray-700 dark:hover:text-gray-500 focus:text-gray-700 dark:focus:text-gray-500': true,
|
|
||||||
'mr-2 order-2': isMyMessage,
|
|
||||||
'ml-2 order-2': !isMyMessage,
|
|
||||||
'!text-gray-700 dark:!text-gray-500': isMenuOpen,
|
|
||||||
'!opacity-100': isMenuOpen || isReactionSelectorOpen,
|
|
||||||
})}
|
|
||||||
data-testid='chat-message-menu'
|
|
||||||
iconClassName='w-4 h-4'
|
|
||||||
/>
|
|
||||||
</DropdownMenuContainer>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{features.chatEmojiReactions ? (
|
{(isMyMessage && features.chatsReadReceipts) ? (
|
||||||
<ChatMessageReactionWrapper
|
<>
|
||||||
onOpen={setIsReactionSelectorOpen}
|
{isRead ? (
|
||||||
onSelect={(emoji) => createReaction.mutate({ emoji, messageId: chatMessage.id, chatMessage })}
|
<span className='flex flex-col items-center justify-center rounded-full border border-solid border-primary-500 bg-primary-500 p-0.5 text-white dark:border-primary-400 dark:bg-primary-400 dark:text-primary-900'>
|
||||||
>
|
<Icon
|
||||||
<IconButton
|
src={require('@tabler/icons/check.svg')}
|
||||||
src={require('@tabler/icons/mood-smile.svg')}
|
strokeWidth={3}
|
||||||
title={intl.formatMessage(messages.more)}
|
className='h-2.5 w-2.5'
|
||||||
className={clsx({
|
/>
|
||||||
'opacity-0 group-hover:opacity-100 focus:opacity-100 transition-opacity flex text-gray-600 dark:text-gray-600 hover:text-gray-700 dark:hover:text-gray-500 focus:text-gray-700 dark:focus:text-gray-500': true,
|
</span>
|
||||||
'mr-2 order-1': isMyMessage,
|
) : (
|
||||||
'ml-2 order-3': !isMyMessage,
|
<span className='flex flex-col items-center justify-center rounded-full border border-solid border-primary-500 bg-transparent p-0.5 text-primary-500 dark:border-primary-400 dark:text-primary-400'>
|
||||||
'!text-gray-700 dark:!text-gray-500': isReactionSelectorOpen,
|
<Icon
|
||||||
'!opacity-100': isMenuOpen || isReactionSelectorOpen,
|
src={require('@tabler/icons/check.svg')}
|
||||||
})}
|
strokeWidth={3}
|
||||||
iconClassName='w-5 h-5'
|
className='h-2.5 w-2.5'
|
||||||
/>
|
/>
|
||||||
</ChatMessageReactionWrapper>
|
</span>
|
||||||
) : null}
|
)}
|
||||||
|
</>
|
||||||
<Stack
|
) : null}
|
||||||
space={0.5}
|
</span>
|
||||||
className={clsx({
|
</div>
|
||||||
'max-w-[85%]': true,
|
</HStack>
|
||||||
'flex-1': !!chatMessage.media_attachments.size,
|
</Stack>
|
||||||
'order-3': isMyMessage,
|
|
||||||
'order-1': !isMyMessage,
|
|
||||||
})}
|
|
||||||
alignItems={isMyMessage ? 'end' : 'start'}
|
|
||||||
>
|
|
||||||
{maybeRenderMedia(chatMessage)}
|
|
||||||
|
|
||||||
{content && (
|
|
||||||
<HStack alignItems='bottom' className='max-w-full'>
|
|
||||||
<div
|
|
||||||
title={getFormattedTimestamp(chatMessage)}
|
|
||||||
className={
|
|
||||||
clsx({
|
|
||||||
'text-ellipsis break-words relative rounded-md py-2 px-3 max-w-full space-y-2 [&_.mention]:underline': true,
|
|
||||||
'rounded-tr-sm': (!!chatMessage.media_attachments.size) && isMyMessage,
|
|
||||||
'rounded-tl-sm': (!!chatMessage.media_attachments.size) && !isMyMessage,
|
|
||||||
'[&_.mention]:text-primary-600 dark:[&_.mention]:text-accent-blue': !isMyMessage,
|
|
||||||
'[&_.mention]:text-white dark:[&_.mention]:white': isMyMessage,
|
|
||||||
'bg-primary-500 text-white': isMyMessage,
|
|
||||||
'bg-gray-200 dark:bg-gray-800 text-gray-900 dark:text-gray-100': !isMyMessage,
|
|
||||||
'!bg-transparent !p-0 emoji-lg': isOnlyEmoji,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
ref={setBubbleRef}
|
|
||||||
tabIndex={0}
|
|
||||||
>
|
|
||||||
<Text
|
|
||||||
size='sm'
|
|
||||||
theme='inherit'
|
|
||||||
className='break-word-nested'
|
|
||||||
dangerouslySetInnerHTML={{ __html: content }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</HStack>
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
</HStack>
|
|
||||||
|
|
||||||
{(features.chatEmojiReactions && chatMessage.emoji_reactions) ? (
|
|
||||||
<div
|
|
||||||
className={clsx({
|
|
||||||
'space-y-1': true,
|
|
||||||
'ml-auto': isMyMessage,
|
|
||||||
'mr-auto': !isMyMessage,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{emojiReactionRows?.map((emojiReactionRow: any, idx: number) => (
|
|
||||||
<HStack
|
|
||||||
key={idx}
|
|
||||||
className={
|
|
||||||
clsx({
|
|
||||||
'flex items-center gap-1': true,
|
|
||||||
'flex-row-reverse': isMyMessage,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{emojiReactionRow.map((emojiReaction: any, idx: number) => (
|
|
||||||
<ChatMessageReaction
|
|
||||||
key={idx}
|
|
||||||
emojiReaction={emojiReaction}
|
|
||||||
onAddReaction={(emoji) => createReaction.mutate({ emoji, messageId: chatMessage.id, chatMessage })}
|
|
||||||
onRemoveReaction={(emoji) => deleteReaction.mutate({ emoji, messageId: chatMessage.id })}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</HStack>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
<HStack
|
|
||||||
alignItems='center'
|
|
||||||
space={2}
|
|
||||||
className={clsx({
|
|
||||||
'ml-auto': isMyMessage,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={clsx({
|
|
||||||
'text-right': isMyMessage,
|
|
||||||
'order-2': !isMyMessage,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<span className='flex items-center space-x-1.5'>
|
|
||||||
<Text theme='muted' size='xs'>
|
|
||||||
{intl.formatTime(chatMessage.created_at)}
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
{(isMyMessage && features.chatsReadReceipts) ? (
|
|
||||||
<>
|
|
||||||
{isRead ? (
|
|
||||||
<span className='flex flex-col items-center justify-center rounded-full border border-solid border-primary-500 bg-primary-500 p-0.5 text-white dark:border-primary-400 dark:bg-primary-400 dark:text-primary-900'>
|
|
||||||
<Icon
|
|
||||||
src={require('@tabler/icons/check.svg')}
|
|
||||||
strokeWidth={3}
|
|
||||||
className='h-2.5 w-2.5'
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
) : (
|
|
||||||
<span className='flex flex-col items-center justify-center rounded-full border border-solid border-primary-500 bg-transparent p-0.5 text-primary-500 dark:border-primary-400 dark:text-primary-400'>
|
|
||||||
<Icon
|
|
||||||
src={require('@tabler/icons/check.svg')}
|
|
||||||
strokeWidth={3}
|
|
||||||
className='h-2.5 w-2.5'
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
) : null}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</HStack>
|
|
||||||
</Stack>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue