added the ability to select emojis via the Emoji Picker Overlay
This commit is contained in:
parent
a88ce39a48
commit
edc20a252f
@ -94,5 +94,27 @@
|
||||
"badges.error.cannot_create_type": "Failed to create type",
|
||||
"badges.error.cannot_update_badge": "Failed to update badge",
|
||||
"badges.error.cannot_delete_badge": "Failed to delete badge",
|
||||
"badges.error.cannot_delete_type": "Failed to delete type"
|
||||
"badges.error.cannot_delete_type": "Failed to delete type",
|
||||
|
||||
"emoji_picker.activities": "Activities",
|
||||
"emoji_picker.animals-nature": "Animals & Nature",
|
||||
"emoji_picker.close": "Close",
|
||||
"emoji_picker.custom": "Custom",
|
||||
"emoji_picker.custom_emoji": "Custom Emoji",
|
||||
"emoji_picker.emojiPicker.button.ariaLabel": "select an emoji",
|
||||
"emoji_picker.emojiPicker.previewPlaceholder": "Select an Emoji",
|
||||
"emoji_picker.flags": "Flags",
|
||||
"emoji_picker.food-drink": "Food & Drink",
|
||||
"emoji_picker.header": "Emoji Picker",
|
||||
"emoji_picker.objects": "Objects",
|
||||
"emoji_picker.people-body": "People & Body",
|
||||
"emoji_picker.recent": "Recently Used",
|
||||
"emoji_picker.search": "Search emojis",
|
||||
"emoji_picker.searchResults": "Search Results",
|
||||
"emoji_picker.search_emoji": "Search for an emoji",
|
||||
"emoji_picker.skin_tone": "Skin tone",
|
||||
"emoji_picker.smileys-emotion": "Smileys & Emotion",
|
||||
"emoji_picker.symbols": "Symbols",
|
||||
"emoji_picker.travel-places": "Travel Places",
|
||||
"emoji_picker_item.emoji_aria_label": "{emojiName} emoji"
|
||||
}
|
||||
|
||||
@ -94,5 +94,27 @@
|
||||
"badges.error.cannot_create_type": "Не удалось создать тип",
|
||||
"badges.error.cannot_update_badge": "Не удалось обновить значок",
|
||||
"badges.error.cannot_delete_badge": "Не удалось удалить значок",
|
||||
"badges.error.cannot_delete_type": "Не удалось удалить тип"
|
||||
"badges.error.cannot_delete_type": "Не удалось удалить тип",
|
||||
|
||||
"emoji_picker.activities": "Мероприятия",
|
||||
"emoji_picker.animals-nature": "Животные и природа",
|
||||
"emoji_picker.close": "Закрыть",
|
||||
"emoji_picker.custom": "Настраиваемое",
|
||||
"emoji_picker.custom_emoji": "Пользовательские смайлики",
|
||||
"emoji_picker.emojiPicker.button.ariaLabel": "выберите смайлик",
|
||||
"emoji_picker.emojiPicker.previewPlaceholder": "Выберите смайлик",
|
||||
"emoji_picker.flags": "Флаги",
|
||||
"emoji_picker.food-drink": "Еда и напитки",
|
||||
"emoji_picker.header": "Выбор смайликов",
|
||||
"emoji_picker.objects": "Объекты",
|
||||
"emoji_picker.people-body": "Люди и тело",
|
||||
"emoji_picker.recent": "Недавно использованные",
|
||||
"emoji_picker.search": "Поиск смайликов",
|
||||
"emoji_picker.searchResults": "Результаты поиска",
|
||||
"emoji_picker.search_emoji": "Поиск смайлика",
|
||||
"emoji_picker.skin_tone": "Цвет кожи",
|
||||
"emoji_picker.smileys-emotion": "Смайлы и эмоции",
|
||||
"emoji_picker.symbols": "Символы",
|
||||
"emoji_picker.travel-places": "Места путешествий",
|
||||
"emoji_picker_item.emoji_aria_label": "смайлик {emojiName}"
|
||||
}
|
||||
|
||||
@ -101,6 +101,57 @@
|
||||
resize: vertical;
|
||||
min-height: 60px;
|
||||
}
|
||||
|
||||
.emoji-input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: 1px solid rgba(var(--center-channel-color-rgb, 61, 60, 64), 0.16);
|
||||
border-radius: 4px;
|
||||
background: var(--center-channel-bg, #fff);
|
||||
|
||||
&:focus-within {
|
||||
border-color: var(--button-bg, #166de0);
|
||||
}
|
||||
|
||||
&__icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: none;
|
||||
color: rgba(var(--center-channel-color-rgb, 61, 60, 64), 0.56);
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: var(--center-channel-color, #3d3c40);
|
||||
}
|
||||
|
||||
.emoticon {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.emojisprite,
|
||||
.emoticon {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
input[type='text'] {
|
||||
flex: 1;
|
||||
border: none;
|
||||
padding: 8px 12px 8px 0;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.checkbox-group {
|
||||
|
||||
25
webapp/src/components/badge_modal/emoji_picker.tsx
Normal file
25
webapp/src/components/badge_modal/emoji_picker.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import React from 'react';
|
||||
|
||||
interface EmojiPickerOverlayProps {
|
||||
target: () => HTMLElement | null;
|
||||
container?: () => HTMLElement | null;
|
||||
show: boolean;
|
||||
onHide: () => void;
|
||||
onEmojiClick: (emoji: any) => void;
|
||||
rightOffset?: number;
|
||||
defaultHorizontalPosition?: 'left' | 'right';
|
||||
onExited?: () => void;
|
||||
hideCustomEmojiButton?: boolean;
|
||||
}
|
||||
|
||||
const EmojiPickerOverlay: React.FC<EmojiPickerOverlayProps> = (props) => {
|
||||
const Overlay = (window as any).Components?.EmojiPickerOverlay;
|
||||
|
||||
if (!Overlay) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <Overlay {...props}/>;
|
||||
};
|
||||
|
||||
export default EmojiPickerOverlay;
|
||||
@ -1,4 +1,4 @@
|
||||
import React, {useCallback, useEffect, useState} from 'react';
|
||||
import React, {useCallback, useEffect, useRef, useState} from 'react';
|
||||
|
||||
import {useDispatch, useSelector} from 'react-redux';
|
||||
import {FormattedMessage, useIntl} from 'react-intl';
|
||||
@ -12,7 +12,10 @@ import {BadgeTypeDefinition} from 'types/badges';
|
||||
import Client from 'client/api';
|
||||
import {getServerErrorId} from 'utils/helpers';
|
||||
import CloseIcon from 'components/icons/close_icon';
|
||||
import EmojiIcon from 'components/icons/emoji_icon';
|
||||
import RenderEmoji from 'components/utils/emoji';
|
||||
|
||||
import EmojiPickerOverlay from './emoji_picker';
|
||||
import TypeSelect from './type_select';
|
||||
|
||||
import './badge_modal.scss';
|
||||
@ -43,6 +46,14 @@ const BadgeModal: React.FC = () => {
|
||||
const [confirmDelete, setConfirmDelete] = useState(false);
|
||||
const [confirmDeleteTypeId, setConfirmDeleteTypeId] = useState<string | null>(null);
|
||||
const [typeDropdownOpen, setTypeDropdownOpen] = useState(false);
|
||||
const [showEmojiPicker, setShowEmojiPicker] = useState(false);
|
||||
const dialogRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const emojiData = (window as any)?.useGetEmojiSelectorData?.();
|
||||
const {
|
||||
emojiButtonRef,
|
||||
calculateRightOffSet,
|
||||
} = emojiData || {};
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
@ -81,6 +92,7 @@ const BadgeModal: React.FC = () => {
|
||||
setConfirmDelete(false);
|
||||
setConfirmDeleteTypeId(null);
|
||||
setTypeDropdownOpen(false);
|
||||
setShowEmojiPicker(false);
|
||||
setLoading(false);
|
||||
}, [isOpen, isEditMode]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
@ -105,6 +117,15 @@ const BadgeModal: React.FC = () => {
|
||||
setConfirmDeleteTypeId(null);
|
||||
}, []);
|
||||
|
||||
const handleEmojiSelect = (emoji: any) => {
|
||||
if (emoji.short_name) {
|
||||
setImage(emoji.short_name);
|
||||
} else if (emoji.name) {
|
||||
setImage(emoji.name);
|
||||
}
|
||||
setShowEmojiPicker(false);
|
||||
};
|
||||
|
||||
const handleDeleteType = useCallback(async (typeId: string) => {
|
||||
if (confirmDeleteTypeId !== typeId) {
|
||||
setConfirmDeleteTypeId(typeId);
|
||||
@ -210,7 +231,10 @@ const BadgeModal: React.FC = () => {
|
||||
className='BadgeModal__backdrop'
|
||||
onClick={handleClose}
|
||||
/>
|
||||
<div className='BadgeModal__dialog'>
|
||||
<div
|
||||
className='BadgeModal__dialog'
|
||||
ref={dialogRef}
|
||||
>
|
||||
<div className='BadgeModal__header'>
|
||||
<h4>{title}</h4>
|
||||
<button
|
||||
@ -257,6 +281,21 @@ const BadgeModal: React.FC = () => {
|
||||
defaultMessage='Эмодзи'
|
||||
/>
|
||||
</label>
|
||||
<div className='emoji-input'>
|
||||
<button
|
||||
type='button'
|
||||
className='emoji-input__icon'
|
||||
onClick={() => setShowEmojiPicker((prev) => !prev)}
|
||||
ref={emojiButtonRef}
|
||||
>
|
||||
<EmojiIcon/>
|
||||
</button>
|
||||
{image && (
|
||||
<RenderEmoji
|
||||
emojiName={image}
|
||||
size={20}
|
||||
/>
|
||||
)}
|
||||
<input
|
||||
type='text'
|
||||
value={image}
|
||||
@ -264,6 +303,19 @@ const BadgeModal: React.FC = () => {
|
||||
placeholder={intl.formatMessage({id: 'badges.modal.field_image_placeholder', defaultMessage: 'Название эмодзи (напр. star)'})}
|
||||
/>
|
||||
</div>
|
||||
{showEmojiPicker && (
|
||||
<EmojiPickerOverlay
|
||||
target={() => emojiButtonRef?.current}
|
||||
container={() => dialogRef.current}
|
||||
show={showEmojiPicker}
|
||||
onHide={() => setShowEmojiPicker(false)}
|
||||
onEmojiClick={handleEmojiSelect}
|
||||
rightOffset={calculateRightOffSet?.(emojiButtonRef?.current)}
|
||||
defaultHorizontalPosition='right'
|
||||
hideCustomEmojiButton={true}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className='form-group'>
|
||||
<label>
|
||||
<FormattedMessage
|
||||
|
||||
31
webapp/src/components/icons/emoji_icon.tsx
Normal file
31
webapp/src/components/icons/emoji_icon.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
|
||||
type Props = {
|
||||
size?: number;
|
||||
}
|
||||
|
||||
const EmojiIcon: React.FC<Props> = ({size = 20}) => (
|
||||
<svg
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox='0 0 24 24'
|
||||
fill='none'
|
||||
stroke='currentColor'
|
||||
strokeWidth='2'
|
||||
strokeLinecap='round'
|
||||
strokeLinejoin='round'
|
||||
>
|
||||
<path
|
||||
stroke='none'
|
||||
d='M0 0h24v24H0z'
|
||||
fill='none'
|
||||
/>
|
||||
<path d='M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0'/>
|
||||
<path d='M9 10l.01 0'/>
|
||||
<path d='M15 10l.01 0'/>
|
||||
<path d='M9.5 15a3.5 3.5 0 0 0 5 0'/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default EmojiIcon;
|
||||
Loading…
x
Reference in New Issue
Block a user