Skip to content

Commit

Permalink
refactor: open emoji picker in place of common reaction tray (#2537)
Browse files Browse the repository at this point in the history
  • Loading branch information
domw30 authored Jan 4, 2025
1 parent bec8ede commit 7086d01
Show file tree
Hide file tree
Showing 4 changed files with 18 additions and 188 deletions.
6 changes: 3 additions & 3 deletions src/components/message-input/emoji-picker/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -494,7 +494,7 @@
.emoji-mart-dark {
@include glass-text-primary-color;
border-color: #000;
background-color: #0a0a0abf;
background-color: rgba(10, 10, 10, 1);
}

.emoji-mart-dark .emoji-mart-bar {
Expand All @@ -505,7 +505,7 @@
@include glass-text-primary-color;

border-color: #000;
background-color: #0a0a0abf;
background-color: rgba(10, 10, 10, 1);
}

.emoji-mart-dark .emoji-mart-search-icon svg {
Expand All @@ -519,7 +519,7 @@
.emoji-mart-dark .emoji-mart-category-label span {
@include glass-text-primary-color;

background-color: #0a0a0abf;
background-color: rgba(10, 10, 10, 1);
}

.emoji-mart-dark .emoji-mart-skin-swatches {
Expand Down
1 change: 1 addition & 0 deletions src/components/message/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,7 @@ export class Message extends React.Component<Properties, State> {
const reactionProps = {
onOpenChange: this.handleOpenReactionMenu,
onSelectReaction: this.onEmojiReaction,
isOwner: this.props.isOwner,
};

const onClose = () => {
Expand Down
145 changes: 10 additions & 135 deletions src/components/reaction-menu/index.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,30 @@
import React, { createRef } from 'react';
import { createPortal } from 'react-dom';
import { Emoji, Picker } from 'emoji-mart';
import { Picker } from 'emoji-mart';

import { IconButton } from '@zero-tech/zui/components';
import { IconDotsHorizontal, IconHeart } from '@zero-tech/zui/icons';
import { IconHeart } from '@zero-tech/zui/icons';
import { ViewModes } from '../../shared-components/theme-engine';

import './styles.scss';
import classNames from 'classnames';

export interface Properties {
isOwner: boolean;
onOpenChange?: (isOpen: boolean) => void;
onSelectReaction: (emoji) => void;
}

export interface State {
isReactionTrayOpen: boolean;
isEmojiPickerOpen: boolean;
isProcessing: boolean;
}

const commonEmojiMapping = {
thumbsup: '👍',
heart: '❤️',
joy: '😂',
cry: '😢',
astonished: '😲',
};

export class ReactionMenu extends React.Component<Properties, State> {
ref = createRef<HTMLDivElement>();
triggerRef = createRef<HTMLDivElement>();
emojiPickerRef = createRef<HTMLDivElement>();

state = {
isReactionTrayOpen: false,
isEmojiPickerOpen: false,
isProcessing: false,
};
Expand All @@ -51,14 +42,12 @@ export class ReactionMenu extends React.Component<Properties, State> {

if (
!(
(this.ref.current && this.ref.current.contains(event.target)) ||
(this.triggerRef.current && this.triggerRef.current.contains(event.target)) ||
(this.emojiPickerRef.current && this.emojiPickerRef.current.contains(event.target))
)
) {
this.setState(
{
isReactionTrayOpen: false,
isEmojiPickerOpen: false,
isProcessing: false,
},
Expand All @@ -69,126 +58,18 @@ export class ReactionMenu extends React.Component<Properties, State> {
}
};

onSelect = (emojiId) => {
console.log('Reaction clicked:', emojiId);
if (this.state.isProcessing) {
console.log('Processing state prevented click');
return;
}

this.setState({ isProcessing: true }, async () => {
console.log('Starting reaction processing');
try {
const emojiCharacter = commonEmojiMapping[emojiId];
if (emojiCharacter) {
console.log('Sending reaction:', emojiCharacter);
await this.props.onSelectReaction(emojiCharacter);
console.log('Reaction sent successfully');
this.setState(
{
isReactionTrayOpen: false,
isEmojiPickerOpen: false,
},
() => {
this.props.onOpenChange?.(false);
}
);
}
} catch (error) {
console.error('Error processing reaction:', error);
} finally {
console.log('Finishing reaction processing');
this.setState({ isProcessing: false });
}
});
};

toggleReactionTray = () => {
this.setState((prevState) => {
const isReactionTrayOpen = !prevState.isReactionTrayOpen;
this.props.onOpenChange?.(isReactionTrayOpen);
return { isReactionTrayOpen, isEmojiPickerOpen: false };
});
};

toggleEmojiPicker = () => {
this.setState((prevState) => ({
isEmojiPickerOpen: !prevState.isEmojiPickerOpen,
isReactionTrayOpen: false,
}));
};

renderCommonReactions() {
const commonReactions = [
'thumbsup',
'heart',
'joy',
'cry',
'astonished',
];

return commonReactions.map((emojiId) => (
<span
className='reaction-tray-item'
key={emojiId}
onClick={(e) => {
e.stopPropagation(); // Stop event from reaching the underlay
this.onSelect(emojiId);
}}
>
<Emoji emoji={emojiId} size={20} />
</span>
));
}

renderReactionTray() {
if (!this.triggerRef.current) {
return null;
}

const triggerRect = this.triggerRef.current.getBoundingClientRect();
const viewportHeight = window.innerHeight;
const trayHeight = 56;
const trayWidth = 256;

const shouldRenderAbove = triggerRect.bottom + trayHeight > viewportHeight;

const trayStyles = {
top: shouldRenderAbove
? `${triggerRect.top + window.scrollY - trayHeight - 8}px`
: `${triggerRect.bottom + window.scrollY + 8}px`,
left: `${triggerRect.left + window.scrollX + triggerRect.width / 2 - trayWidth / 2}px`,
transformOrigin: shouldRenderAbove ? 'bottom center' : 'top center',
transform: 'translateY(0)',
};

return createPortal(
<>
<div className='reaction-menu__underlay' onClick={this.toggleReactionTray}></div>

<div className='reaction-tray' style={trayStyles} ref={this.ref}>
{this.renderCommonReactions()}

<div className='reaction-tray-item'>
<IconButton
className='emoji-picker-trigger-icon'
Icon={IconDotsHorizontal}
size={20}
onClick={(e) => {
e.stopPropagation();
this.toggleEmojiPicker();
}}
/>
</div>
</div>
</>,
document.body
);
}

renderEmojiPicker() {
return createPortal(
<div className='emoji-picker' ref={this.emojiPickerRef}>
<div
className={classNames('emoji-picker', this.props.isOwner && 'emoji-picker--owner')}
ref={this.emojiPickerRef}
>
<Picker
emoji='mechanical_arm'
title='ZOS'
Expand All @@ -208,20 +89,14 @@ export class ReactionMenu extends React.Component<Properties, State> {
};

render() {
const { isReactionTrayOpen, isEmojiPickerOpen } = this.state;
const { isEmojiPickerOpen } = this.state;

return (
<div className='reaction-menu'>
<div ref={this.triggerRef}>
<IconButton
className={'reaction-menu-trigger'}
Icon={IconHeart}
size={32}
onClick={this.toggleReactionTray}
/>
<IconButton className={'reaction-menu-trigger'} Icon={IconHeart} size={32} onClick={this.toggleEmojiPicker} />
</div>

{isReactionTrayOpen && this.renderReactionTray()}
{isEmojiPickerOpen && this.renderEmojiPicker()}
</div>
);
Expand Down
54 changes: 4 additions & 50 deletions src/components/reaction-menu/styles.scss
Original file line number Diff line number Diff line change
@@ -1,66 +1,20 @@
@use '~@zero-tech/zui/styles/theme' as theme;
@import '../../glass';

.reaction-tray {
@include flat-thick;
@include glass-shadow-and-blur;

display: flex;
flex-direction: row;
align-items: center;
padding: 4px;
border-radius: 8px;
min-width: 128px;
max-width: 256px;
position: fixed;
z-index: 1002;
}

.reaction-tray-item {
@include glass-text-primary-color;

display: flex;
align-items: center;
outline: none;
border-radius: 8px;
user-select: none;
cursor: pointer;
padding: 8px;

&:hover {
@include glass-state-hover-color;
}

&:active {
background: rgba(163, 162, 163, 0.1);
}
}

.reaction-menu__underlay {
z-index: 1000;
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: block;
pointer-events: auto;
background: transparent;
}

.reaction-menu-trigger {
z-index: 1003;
outline: none;
}

.emoji-picker {
position: absolute;
right: 65px;
bottom: 75px;
left: 390px;
z-index: 1010;
color: var(--color-greyscale-11);
}

.emoji-picker-trigger-icon {
background: theme.$color-greyscale-transparency-3;
.emoji-picker--owner {
right: 65px;
left: auto;
}

0 comments on commit 7086d01

Please sign in to comment.