Fix promo panel icon picker, use TS, FC
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
parent
4c26ab3045
commit
6c8ed2ad65
4 changed files with 216 additions and 0 deletions
Binary file not shown.
|
@ -0,0 +1,85 @@
|
|||
import React, { useRef, useState } from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
// @ts-ignore
|
||||
import Overlay from 'react-overlays/lib/Overlay';
|
||||
|
||||
import Icon from 'soapbox/components/icon';
|
||||
|
||||
import IconPickerMenu from './icon-picker-menu';
|
||||
|
||||
const messages = defineMessages({
|
||||
emoji: { id: 'icon_button.label', defaultMessage: 'Select icon' },
|
||||
});
|
||||
|
||||
interface IIconPickerDropdown {
|
||||
value: string,
|
||||
onPickEmoji: React.ChangeEventHandler,
|
||||
}
|
||||
|
||||
const IconPickerDropdown: React.FC<IIconPickerDropdown> = ({ value, onPickEmoji }) => {
|
||||
const intl = useIntl();
|
||||
|
||||
const [active, setActive] = useState(false);
|
||||
const [placement, setPlacement] = useState<'bottom' | 'top'>();
|
||||
|
||||
const target = useRef(null);
|
||||
|
||||
const onShowDropdown: React.KeyboardEventHandler<HTMLDivElement> = ({ target }) => {
|
||||
setActive(true);
|
||||
|
||||
const { top } = (target as any).getBoundingClientRect();
|
||||
setPlacement(top * 2 < innerHeight ? 'bottom' : 'top');
|
||||
};
|
||||
|
||||
const onHideDropdown = () => {
|
||||
setActive(false);
|
||||
};
|
||||
|
||||
const onToggle: React.KeyboardEventHandler<HTMLDivElement> = (e) => {
|
||||
e.stopPropagation();
|
||||
if (!e.key || e.key === 'Enter') {
|
||||
if (active) {
|
||||
onHideDropdown();
|
||||
} else {
|
||||
onShowDropdown(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyDown: React.KeyboardEventHandler<HTMLDivElement> = (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
onHideDropdown();
|
||||
}
|
||||
};
|
||||
|
||||
const title = intl.formatMessage(messages.emoji);
|
||||
const forkAwesomeIcons = require('../forkawesome.json');
|
||||
|
||||
return (
|
||||
<div onKeyDown={handleKeyDown}>
|
||||
<div
|
||||
ref={target}
|
||||
className='h-[38px] w-[38px] text-lg flex items-center justify-center cursor-pointer'
|
||||
title={title}
|
||||
aria-label={title}
|
||||
aria-expanded={active}
|
||||
role='button'
|
||||
onClick={onToggle as any as React.MouseEventHandler<HTMLDivElement>}
|
||||
onKeyDown={onToggle}
|
||||
tabIndex={0}
|
||||
>
|
||||
<Icon id={value} />
|
||||
</div>
|
||||
|
||||
<Overlay show={active} placement={placement} target={target.current}>
|
||||
<IconPickerMenu
|
||||
customEmojis={forkAwesomeIcons}
|
||||
onClose={onHideDropdown}
|
||||
onPick={onPickEmoji}
|
||||
/>
|
||||
</Overlay>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default IconPickerDropdown;
|
Binary file not shown.
|
@ -0,0 +1,131 @@
|
|||
import classNames from 'clsx';
|
||||
import { supportsPassiveEvents } from 'detect-passive-events';
|
||||
// @ts-ignore
|
||||
import Picker from 'emoji-mart/dist-es/components/picker/picker';
|
||||
import React, { useCallback, useEffect, useRef } from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
const messages = defineMessages({
|
||||
emoji: { id: 'icon_button.label', defaultMessage: 'Select icon' },
|
||||
emoji_search: { id: 'emoji_button.search', defaultMessage: 'Search…' },
|
||||
emoji_not_found: { id: 'icon_button.not_found', defaultMessage: 'No icons!! (╯°□°)╯︵ ┻━┻' },
|
||||
custom: { id: 'icon_button.icons', defaultMessage: 'Icons' },
|
||||
search_results: { id: 'emoji_button.search_results', defaultMessage: 'Search results' },
|
||||
});
|
||||
|
||||
const backgroundImageFn = () => '';
|
||||
const listenerOptions = supportsPassiveEvents ? { passive: true } : false;
|
||||
|
||||
const categoriesSort = ['custom'];
|
||||
|
||||
interface IIconPickerMenu {
|
||||
customEmojis: Record<string, Array<string>>,
|
||||
onClose: () => void,
|
||||
onPick: any,
|
||||
style?: React.CSSProperties,
|
||||
}
|
||||
|
||||
const IconPickerMenu: React.FC<IIconPickerMenu> = ({ customEmojis, onClose, onPick, style }) => {
|
||||
const intl = useIntl();
|
||||
|
||||
const node = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const handleDocumentClick = useCallback((e: MouseEvent | TouchEvent) => {
|
||||
if (node.current && !node.current.contains(e.target as Node)) {
|
||||
onClose();
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener('click', handleDocumentClick, false);
|
||||
document.addEventListener('touchend', handleDocumentClick, listenerOptions);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('click', handleDocumentClick, false);
|
||||
document.removeEventListener('touchend', handleDocumentClick, listenerOptions as any);
|
||||
|
||||
};
|
||||
}, []);
|
||||
|
||||
const setRef = (c: HTMLDivElement) => {
|
||||
node.current = c;
|
||||
|
||||
if (!c) return;
|
||||
|
||||
// Nice and dirty hack to display the icons
|
||||
c.querySelectorAll('button.emoji-mart-emoji > img').forEach(elem => {
|
||||
const newIcon = document.createElement('span');
|
||||
newIcon.innerHTML = `<i class="fa fa-${(elem.parentNode as any).getAttribute('title')} fa-hack"></i>`;
|
||||
(elem.parentNode as any).replaceChild(newIcon, elem);
|
||||
});
|
||||
};
|
||||
|
||||
const getI18n = () => {
|
||||
|
||||
return {
|
||||
search: intl.formatMessage(messages.emoji_search),
|
||||
notfound: intl.formatMessage(messages.emoji_not_found),
|
||||
categories: {
|
||||
search: intl.formatMessage(messages.search_results),
|
||||
custom: intl.formatMessage(messages.custom),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const handleClick = (emoji: Record<string, any>) => {
|
||||
emoji.native = emoji.colons;
|
||||
|
||||
onClose();
|
||||
onPick(emoji);
|
||||
};
|
||||
|
||||
const buildIcons = () => {
|
||||
const emojis: Record<string, any> = [];
|
||||
|
||||
Object.values(customEmojis).forEach((category) => {
|
||||
category.forEach((icon) => {
|
||||
const name = icon.replace('fa fa-', '');
|
||||
if (icon !== 'email' && icon !== 'memo') {
|
||||
emojis.push({
|
||||
id: name,
|
||||
name,
|
||||
short_names: [name],
|
||||
emoticons: [],
|
||||
keywords: [name],
|
||||
imageUrl: '',
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return emojis;
|
||||
};
|
||||
|
||||
const data = { compressed: true, categories: [], aliases: [], emojis: [] };
|
||||
const title = intl.formatMessage(messages.emoji);
|
||||
|
||||
return (
|
||||
<div className={classNames('font-icon-picker emoji-picker-dropdown__menu')} style={style} ref={setRef}>
|
||||
<Picker
|
||||
perLine={8}
|
||||
emojiSize={22}
|
||||
include={categoriesSort}
|
||||
sheetSize={32}
|
||||
custom={buildIcons()}
|
||||
color=''
|
||||
emoji=''
|
||||
set=''
|
||||
title={title}
|
||||
i18n={getI18n()}
|
||||
onClick={handleClick}
|
||||
showPreview={false}
|
||||
backgroundImageFn={backgroundImageFn}
|
||||
emojiTooltip
|
||||
noShowAnchors
|
||||
data={data}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default IconPickerMenu;
|
Loading…
Reference in a new issue