diff --git a/app/application.js b/app/application.js
index 910d2af8d..c0f54ff1d 100644
--- a/app/application.js
+++ b/app/application.js
@@ -4,9 +4,6 @@ require('fork-awesome/css/fork-awesome.css');
require.context('./images/', true);
-require('@fonticonpicker/react-fonticonpicker/dist/fonticonpicker.base-theme.react.css');
-require('@fonticonpicker/react-fonticonpicker/dist/fonticonpicker.material-theme.react.css');
-
loadPolyfills().then(() => {
require('./soapbox/main').default();
}).catch(e => {
diff --git a/app/soapbox/features/soapbox_config/components/icon_picker.js b/app/soapbox/features/soapbox_config/components/icon_picker.js
new file mode 100644
index 000000000..0b16e1792
--- /dev/null
+++ b/app/soapbox/features/soapbox_config/components/icon_picker.js
@@ -0,0 +1,5 @@
+import Picker from 'emoji-mart/dist-es/components/picker/picker';
+
+export {
+ Picker,
+};
diff --git a/app/soapbox/features/soapbox_config/components/icon_picker_dropdown.js b/app/soapbox/features/soapbox_config/components/icon_picker_dropdown.js
new file mode 100644
index 000000000..57bff9934
--- /dev/null
+++ b/app/soapbox/features/soapbox_config/components/icon_picker_dropdown.js
@@ -0,0 +1,237 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { defineMessages, injectIntl } from 'react-intl';
+import { IconPicker as IconPickerAsync } from '../utils/async';
+import Overlay from 'react-overlays/lib/Overlay';
+import classNames from 'classnames';
+import detectPassiveEvents from 'detect-passive-events';
+import forkAwesomeIcons from '../forkawesome.json';
+import Icon from 'soapbox/components/icon';
+
+const messages = defineMessages({
+ emoji: { id: 'icon_button.label', defaultMessage: 'Select icon' },
+ emoji_search: { id: 'emoji_button.search', defaultMessage: 'Search...' },
+ emoji_not_found: { id: 'emoji_button.not_found', defaultMessage: 'No emojos!! (╯°□°)╯︵ ┻━┻' },
+ custom: { id: 'icon_button.icons', defaultMessage: 'Icons' },
+ search_results: { id: 'emoji_button.search_results', defaultMessage: 'Search results' },
+});
+
+let IconPicker; // load asynchronously
+
+const backgroundImageFn = () => '';
+const listenerOptions = detectPassiveEvents.hasSupport ? { passive: true } : false;
+
+const categoriesSort = 'custom';
+
+@injectIntl
+class IconPickerMenu extends React.PureComponent {
+
+ static propTypes = {
+ custom_emojis: PropTypes.object,
+ loading: PropTypes.bool,
+ onClose: PropTypes.func.isRequired,
+ onPick: PropTypes.func.isRequired,
+ style: PropTypes.object,
+ placement: PropTypes.string,
+ arrowOffsetLeft: PropTypes.string,
+ arrowOffsetTop: PropTypes.string,
+ intl: PropTypes.object.isRequired,
+ };
+
+ static defaultProps = {
+ style: {},
+ loading: true,
+ };
+
+ state = {
+ modifierOpen: false,
+ placement: null,
+ };
+
+ handleDocumentClick = e => {
+ if (this.node && !this.node.contains(e.target)) {
+ this.props.onClose();
+ }
+ }
+
+ componentDidMount() {
+ document.addEventListener('click', this.handleDocumentClick, false);
+ document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
+ }
+
+ componentWillUnmount() {
+ document.removeEventListener('click', this.handleDocumentClick, false);
+ document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions);
+ }
+
+ setRef = c => {
+ this.node = c;
+ }
+
+ getI18n = () => {
+ const { intl } = this.props;
+
+ 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),
+ },
+ };
+ }
+
+ handleClick = emoji => {
+ emoji.native = emoji.colons;
+
+ this.props.onClose();
+ this.props.onPick(emoji);
+ }
+
+ buildIcons = (customEmojis, autoplay = false) => {
+ const emojis = [];
+
+ Object.values(customEmojis).forEach(category => {
+ category.forEach(function(icon) {
+ const name = icon.replace('fa fa-', '');
+ emojis.push({
+ id: icon,
+ name,
+ short_names: [name],
+ emoticons: [],
+ keywords: [name],
+ imageUrl: '',
+ custom: true,
+ });
+ });
+ });
+
+ return emojis;
+ };
+
+ render() {
+ const { loading, style, intl, custom_emojis } = this.props;
+
+ if (loading) {
+ return
;
+ }
+
+ const title = intl.formatMessage(messages.emoji);
+ const { modifierOpen } = this.state;
+
+ return (
+
+
+
+ );
+ }
+
+}
+
+export default @injectIntl
+class IconPickerDropdown extends React.PureComponent {
+
+ static propTypes = {
+ frequentlyUsedEmojis: PropTypes.arrayOf(PropTypes.string),
+ intl: PropTypes.object.isRequired,
+ onPickEmoji: PropTypes.func.isRequired,
+ value: PropTypes.string,
+ };
+
+ state = {
+ icons: forkAwesomeIcons,
+ active: false,
+ loading: false,
+ };
+
+ setRef = (c) => {
+ this.dropdown = c;
+ }
+
+ onShowDropdown = ({ target }) => {
+ this.setState({ active: true });
+
+ if (!IconPicker) {
+ this.setState({ loading: true });
+
+ IconPickerAsync().then(EmojiMart => {
+ IconPicker = EmojiMart.Picker;
+
+ this.setState({ loading: false });
+ }).catch(() => {
+ this.setState({ loading: false });
+ });
+ }
+
+ const { top } = target.getBoundingClientRect();
+ this.setState({ placement: top * 2 < innerHeight ? 'bottom' : 'top' });
+ }
+
+ onHideDropdown = () => {
+ this.setState({ active: false });
+ }
+
+ onToggle = (e) => {
+ if (!this.state.loading && (!e.key || e.key === 'Enter')) {
+ if (this.state.active) {
+ this.onHideDropdown();
+ } else {
+ this.onShowDropdown(e);
+ }
+ }
+ }
+
+ handleKeyDown = e => {
+ if (e.key === 'Escape') {
+ this.onHideDropdown();
+ }
+ }
+
+ setTargetRef = c => {
+ this.target = c;
+ }
+
+ findTarget = () => {
+ return this.target;
+ }
+
+ render() {
+ const { intl, onPickEmoji, value } = this.props;
+ const title = intl.formatMessage(messages.emoji);
+ const { active, loading, placement, icons } = this.state;
+
+ return (
+
+ );
+ }
+
+}
diff --git a/app/soapbox/features/soapbox_config/containers/icon_picker_dropdown_container.js b/app/soapbox/features/soapbox_config/containers/icon_picker_dropdown_container.js
new file mode 100644
index 000000000..abd8bd9cf
--- /dev/null
+++ b/app/soapbox/features/soapbox_config/containers/icon_picker_dropdown_container.js
@@ -0,0 +1,20 @@
+import { connect } from 'react-redux';
+import IconPickerDropdown from '../components/icon_picker_dropdown';
+import { useEmoji } from '../../../actions/emojis';
+
+const mapStateToProps = state => ({
+ frequentlyUsedEmojis: '',
+});
+
+const mapDispatchToProps = (dispatch, { onPickEmoji }) => ({
+
+ onPickEmoji: emoji => {
+ dispatch(useEmoji(emoji)); // eslint-disable-line react-hooks/rules-of-hooks
+
+ if (onPickEmoji) {
+ onPickEmoji(emoji);
+ }
+ },
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(IconPickerDropdown);
diff --git a/app/soapbox/features/soapbox_config/index.js b/app/soapbox/features/soapbox_config/index.js
index cb10bb268..d4c360140 100644
--- a/app/soapbox/features/soapbox_config/index.js
+++ b/app/soapbox/features/soapbox_config/index.js
@@ -26,8 +26,8 @@ import Accordion from '../ui/components/accordion';
import SitePreview from './components/site_preview';
import ThemeToggle from 'soapbox/features/ui/components/theme_toggle';
import { defaultSettings } from 'soapbox/actions/settings';
-import FontIconPicker from '@fonticonpicker/react-fonticonpicker';
import forkAwesomeIcons from './forkawesome.json';
+import IconPickerDropdown from './components/icon_picker_dropdown';
const messages = defineMessages({
heading: { id: 'column.soapbox_config', defaultMessage: 'Soapbox config' },
@@ -249,9 +249,8 @@ class SoapboxConfig extends ImmutablePureComponent {
val.substring(6))}
+ onChange={this.handlePromoItemChange(i, 'icon', field, val => val.id)}
/>
-
+
+
diff --git a/app/soapbox/features/soapbox_config/utils/async.js b/app/soapbox/features/soapbox_config/utils/async.js
new file mode 100644
index 000000000..42866579b
--- /dev/null
+++ b/app/soapbox/features/soapbox_config/utils/async.js
@@ -0,0 +1,3 @@
+export function IconPicker() {
+ return import(/* webpackChunkName: "icon_picker" */'../components/icon_picker');
+}
diff --git a/app/styles/forms.scss b/app/styles/forms.scss
index bab1f33c6..c1ca83470 100644
--- a/app/styles/forms.scss
+++ b/app/styles/forms.scss
@@ -179,6 +179,10 @@ code {
}
}
+ .input.font_icon_picker {
+ width: 52px;
+ }
+
.input.with_block_label {
max-width: none;
@@ -255,10 +259,6 @@ code {
}
}
- .input.popup {
- overflow: visible;
- }
-
.input.radio_buttons .radio label {
margin-bottom: 5px;
font-family: inherit;
@@ -511,92 +511,18 @@ code {
}
}
- &__icon_picker {
+ &__font_icon_picker {
font-size: 14px;
- .rfipbtn {
- display: inline-flex;
- line-height: 19px;
- min-height: 19px;
- padding: 0;
+ .font-icon-button {
+ padding: 9px;
+ border: 1px solid var(--highlight-text-color);
+ border-radius: 4px;
- &__icon {
- margin: 3.5px 0 3.5px 10px;
+ .fa {
+ font-size: 18px;
color: var(--primary-text-color);
}
-
- &--default {
- .rfipbtn__icon {
- border-color: var(--primary-text-color);
- }
-
- .rfipbtn__del {
- background-color: var(--primary-text-color--faint);
- color: var(--foreground-color);
- }
-
- .rfipbtn__button {
- border-left: 1px solid var(--highlight-text-color);
- background-color: var(--foreground-color);
- color: var(--primary-text-color);
- }
- }
-
- &__icon--empty {
- text-transform: none;
- font-style: normal;
- color: var(--primary-text-color--faint);
- font-size: 16px;
- line-height: 15px;
- padding: 10px;
- }
-
- &__current {
- flex: 0;
- }
-
- &--default:active,
- &--default:focus {
- box-shadow: none;
- }
-
- &__button {
- border-top-right-radius: inherit;
- border-bottom-right-radius: inherit;
- }
- }
-
- .rfipdropdown {
- &--default {
- background-color: var(--background-color);
- border: none;
- color: var(--primary-text-color);
- width: 325px;
-
- input,
- select {
- color: var(--primary-text-color);
- background-color: var(--foreground-color);
- }
-
- .rfipsearch input:focus,
- .rfipsearch input:active {
- box-shadow: none;
- }
-
- .rfipicons__ibox,
- .rfipicons__left,
- .rfipicons__right {
- border: none;
- background-color: var(--foreground-color);
- color: var(--primary-text-color);
- }
-
- .rfipicons__cp,
- .rfipcategory select {
- border-bottom: 1px solid var(--highlight-text-color);
- }
- }
}
}
diff --git a/package.json b/package.json
index 48ca85def..f19985841 100644
--- a/package.json
+++ b/package.json
@@ -45,7 +45,6 @@
"@babel/preset-env": "^7.3.4",
"@babel/preset-react": "^7.0.0",
"@babel/runtime": "^7.3.4",
- "@fonticonpicker/react-fonticonpicker": "^1.2.0",
"@popperjs/core": "^2.4.4",
"array-includes": "^3.0.3",
"autoprefixer": "^10.0.0",
@@ -114,7 +113,6 @@
"react-swipeable-views": "^0.13.0",
"react-textarea-autosize": "^8.0.0",
"react-toggle": "^4.0.1",
- "react-transition-group": "^2.3.0",
"redux": "^4.0.5",
"redux-immutable": "^4.0.0",
"redux-thunk": "^2.2.0",
diff --git a/yarn.lock b/yarn.lock
index b2c70a69e..6076f50ef 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1345,11 +1345,6 @@
minimatch "^3.0.4"
strip-json-comments "^3.1.1"
-"@fonticonpicker/react-fonticonpicker@^1.2.0":
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/@fonticonpicker/react-fonticonpicker/-/react-fonticonpicker-1.2.0.tgz#cbcb898f3d788ef50d7090fe623afe2e8e5eb1d7"
- integrity sha512-I7U4VNIpwJHnOaWq1v2MSuaUTxPiur/MYG4B1hr+FWI1O7k+tTym1ysdAyfp7YbEdF4Q8TYoY5Q2hbTmhQeTSQ==
-
"@formatjs/ecma402-abstract@^1.2.3":
version "1.2.3"
resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-1.2.3.tgz#ca94911dd8e9c89eeaabba0f00e2f692979fc27b"
@@ -4342,7 +4337,7 @@ dom-converter@^0.2:
dependencies:
utila "~0.4"
-dom-helpers@^3.2.1, dom-helpers@^3.3.1, dom-helpers@^3.4.0:
+dom-helpers@^3.2.1, dom-helpers@^3.3.1:
version "3.4.0"
resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.4.0.tgz#e9b369700f959f62ecde5a6babde4bccd9169af8"
integrity sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==
@@ -10074,16 +10069,6 @@ react-transition-group@^2.2.1:
prop-types "^15.6.2"
react-lifecycles-compat "^3.0.4"
-react-transition-group@^2.3.0:
- version "2.9.0"
- resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.9.0.tgz#df9cdb025796211151a436c69a8f3b97b5b07c8d"
- integrity sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==
- dependencies:
- dom-helpers "^3.4.0"
- loose-envify "^1.4.0"
- prop-types "^15.6.2"
- react-lifecycles-compat "^3.0.4"
-
react@^16.13.1:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e"