Rework actions modal to bring it closer to upstream and fix modal stacking issue
This commit is contained in:
parent
bc2eaf3581
commit
e1a4590bca
|
@ -14,12 +14,7 @@ const mapDispatchToProps = (dispatch, { status, items, scrollKey }) => ({
|
|||
onOpen(id, onItemClick, dropdownPlacement, keyboard) {
|
||||
dispatch(isUserTouching() ? openModal('ACTIONS', {
|
||||
status,
|
||||
actions: items.map(
|
||||
(item, i) => item ? {
|
||||
...item,
|
||||
name: `${item.text}-${i}`,
|
||||
} : null
|
||||
),
|
||||
actions: items,
|
||||
onClick: onItemClick,
|
||||
}) : openDropdownMenu(id, dropdownPlacement, keyboard, scrollKey));
|
||||
},
|
||||
|
|
|
@ -21,10 +21,9 @@ export default class ComposerOptionsDropdown extends React.PureComponent {
|
|||
icon: PropTypes.string,
|
||||
items: PropTypes.arrayOf(PropTypes.shape({
|
||||
icon: PropTypes.string,
|
||||
meta: PropTypes.node,
|
||||
meta: PropTypes.string,
|
||||
name: PropTypes.string.isRequired,
|
||||
on: PropTypes.bool,
|
||||
text: PropTypes.node,
|
||||
text: PropTypes.string,
|
||||
})).isRequired,
|
||||
onModalOpen: PropTypes.func,
|
||||
onModalClose: PropTypes.func,
|
||||
|
@ -32,10 +31,15 @@ export default class ComposerOptionsDropdown extends React.PureComponent {
|
|||
value: PropTypes.string,
|
||||
onChange: PropTypes.func,
|
||||
container: PropTypes.func,
|
||||
renderItemContents: PropTypes.func,
|
||||
closeOnChange: PropTypes.bool,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
closeOnChange: true,
|
||||
};
|
||||
|
||||
state = {
|
||||
needsModalUpdate: false,
|
||||
open: false,
|
||||
openedViaKeyboard: undefined,
|
||||
placement: 'bottom',
|
||||
|
@ -106,6 +110,23 @@ export default class ComposerOptionsDropdown extends React.PureComponent {
|
|||
this.setState({ open: false });
|
||||
}
|
||||
|
||||
handleItemClick = (e) => {
|
||||
const {
|
||||
items,
|
||||
onChange,
|
||||
onModalClose,
|
||||
closeOnChange,
|
||||
} = this.props;
|
||||
|
||||
const i = Number(e.currentTarget.getAttribute('data-index'));
|
||||
|
||||
const { name } = this.props.items[i];
|
||||
|
||||
e.preventDefault(); // Prevents focus from changing
|
||||
if (closeOnChange) onModalClose();
|
||||
onChange(name);
|
||||
};
|
||||
|
||||
// Creates an action modal object.
|
||||
handleMakeModal = () => {
|
||||
const component = this;
|
||||
|
@ -124,6 +145,8 @@ export default class ComposerOptionsDropdown extends React.PureComponent {
|
|||
|
||||
// The object.
|
||||
return {
|
||||
renderItemContents: this.props.renderItemContents,
|
||||
onClick: this.handleItemClick,
|
||||
actions: items.map(
|
||||
({
|
||||
name,
|
||||
|
@ -132,48 +155,11 @@ export default class ComposerOptionsDropdown extends React.PureComponent {
|
|||
...rest,
|
||||
active: value && name === value,
|
||||
name,
|
||||
onClick (e) {
|
||||
e.preventDefault(); // Prevents focus from changing
|
||||
onModalClose();
|
||||
onChange(name);
|
||||
},
|
||||
onPassiveClick (e) {
|
||||
e.preventDefault(); // Prevents focus from changing
|
||||
onChange(name);
|
||||
component.setState({ needsModalUpdate: true });
|
||||
},
|
||||
})
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
// If our modal is open and our props update, we need to also update
|
||||
// the modal.
|
||||
handleUpdate = () => {
|
||||
const { onModalOpen } = this.props;
|
||||
const { needsModalUpdate } = this.state;
|
||||
|
||||
// Gets our modal object.
|
||||
const modal = this.handleMakeModal();
|
||||
|
||||
// Reopens the modal with the new object.
|
||||
if (needsModalUpdate && modal && onModalOpen) {
|
||||
onModalOpen(modal);
|
||||
}
|
||||
}
|
||||
|
||||
// Updates our modal as necessary.
|
||||
componentDidUpdate (prevProps) {
|
||||
const { items } = this.props;
|
||||
const { needsModalUpdate } = this.state;
|
||||
if (needsModalUpdate && items.find(
|
||||
(item, i) => item.on !== prevProps.items[i].on
|
||||
)) {
|
||||
this.handleUpdate();
|
||||
this.setState({ needsModalUpdate: false });
|
||||
}
|
||||
}
|
||||
|
||||
// Rendering.
|
||||
render () {
|
||||
const {
|
||||
|
@ -185,6 +171,8 @@ export default class ComposerOptionsDropdown extends React.PureComponent {
|
|||
onChange,
|
||||
value,
|
||||
container,
|
||||
renderItemContents,
|
||||
closeOnChange,
|
||||
} = this.props;
|
||||
const { open, placement } = this.state;
|
||||
const computedClass = classNames('composer--options--dropdown', {
|
||||
|
@ -225,10 +213,12 @@ export default class ComposerOptionsDropdown extends React.PureComponent {
|
|||
>
|
||||
<DropdownMenu
|
||||
items={items}
|
||||
renderItemContents={renderItemContents}
|
||||
onChange={onChange}
|
||||
onClose={this.handleClose}
|
||||
value={value}
|
||||
openedViaKeyboard={this.state.openedViaKeyboard}
|
||||
closeOnChange={closeOnChange}
|
||||
/>
|
||||
</Overlay>
|
||||
</div>
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import spring from 'react-motion/lib/spring';
|
||||
import Toggle from 'react-toggle';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import classNames from 'classnames';
|
||||
|
||||
|
@ -28,18 +27,20 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent
|
|||
icon: PropTypes.string,
|
||||
meta: PropTypes.node,
|
||||
name: PropTypes.string.isRequired,
|
||||
on: PropTypes.bool,
|
||||
text: PropTypes.node,
|
||||
})),
|
||||
onChange: PropTypes.func.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
style: PropTypes.object,
|
||||
value: PropTypes.string,
|
||||
renderItemContents: PropTypes.func,
|
||||
openedViaKeyboard: PropTypes.bool,
|
||||
closeOnChange: PropTypes.bool,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
style: {},
|
||||
closeOnChange: true,
|
||||
};
|
||||
|
||||
state = {
|
||||
|
@ -83,12 +84,13 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent
|
|||
const {
|
||||
onChange,
|
||||
onClose,
|
||||
closeOnChange,
|
||||
items,
|
||||
} = this.props;
|
||||
|
||||
const { on, name } = this.props.items[i];
|
||||
const { name } = this.props.items[i];
|
||||
e.preventDefault(); // Prevents change in focus on click
|
||||
if ((on === null || typeof on === 'undefined')) {
|
||||
if (closeOnChange) {
|
||||
onClose();
|
||||
}
|
||||
onChange(name);
|
||||
|
@ -150,18 +152,25 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent
|
|||
}
|
||||
|
||||
renderItem = (item, i) => {
|
||||
const { name, icon, meta, on, text } = item;
|
||||
const { name, icon, meta, text } = item;
|
||||
|
||||
const active = (name === (this.props.value || this.state.value));
|
||||
|
||||
const computedClass = classNames('composer--options--dropdown--content--item', { active });
|
||||
|
||||
let prefix = null;
|
||||
let contents = this.props.renderItemContents && this.props.renderItemContents(item, i);
|
||||
|
||||
if (on !== null && typeof on !== 'undefined') {
|
||||
prefix = <Toggle checked={on} onChange={this.handleClick} />;
|
||||
} else if (icon) {
|
||||
prefix = <Icon className='icon' fixedWidth id={icon} />
|
||||
if (!contents) {
|
||||
contents = (
|
||||
<React.Fragment>
|
||||
{icon && <Icon className='icon' fixedWidth id={icon} />}
|
||||
|
||||
<div className='content'>
|
||||
<strong>{text}</strong>
|
||||
{meta}
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -175,12 +184,7 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent
|
|||
data-index={i}
|
||||
ref={active ? this.setFocusRef : null}
|
||||
>
|
||||
{prefix}
|
||||
|
||||
<div className='content'>
|
||||
<strong>{text}</strong>
|
||||
{meta}
|
||||
</div>
|
||||
{contents}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,8 +2,10 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import spring from 'react-motion/lib/spring';
|
||||
import Toggle from 'react-toggle';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
// Components.
|
||||
import IconButton from 'flavours/glitch/components/icon_button';
|
||||
|
@ -80,6 +82,36 @@ const messages = defineMessages({
|
|||
},
|
||||
});
|
||||
|
||||
@connect((state, { name }) => ({ checked: state.getIn(['compose', 'advanced_options', name]) }))
|
||||
class ToggleOption extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
checked: PropTypes.bool,
|
||||
onChangeAdvancedOption: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
handleChange = () => {
|
||||
this.props.onChangeAdvancedOption(this.props.name);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { name, meta, text, checked } = this.props;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Toggle checked={checked} onChange={this.handleChange} />
|
||||
|
||||
<div className='content'>
|
||||
<strong>{text}</strong>
|
||||
{meta}
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default @injectIntl
|
||||
class ComposerOptions extends ImmutablePureComponent {
|
||||
|
||||
|
@ -141,6 +173,13 @@ class ComposerOptions extends ImmutablePureComponent {
|
|||
this.fileElement = fileElement;
|
||||
}
|
||||
|
||||
renderToggleItemContents = (item, index) => {
|
||||
const { onChangeAdvancedOption } = this.props;
|
||||
const { name, meta, text } = item;
|
||||
|
||||
return <ToggleOption name={name} text={text} meta={meta} onChangeAdvancedOption={onChangeAdvancedOption} />;
|
||||
};
|
||||
|
||||
// Rendering.
|
||||
render () {
|
||||
const {
|
||||
|
@ -152,7 +191,6 @@ class ComposerOptions extends ImmutablePureComponent {
|
|||
hasMedia,
|
||||
allowPoll,
|
||||
hasPoll,
|
||||
intl,
|
||||
onChangeAdvancedOption,
|
||||
onChangeContentType,
|
||||
onChangeVisibility,
|
||||
|
@ -164,23 +202,24 @@ class ComposerOptions extends ImmutablePureComponent {
|
|||
resetFileKey,
|
||||
spoiler,
|
||||
showContentTypeChoice,
|
||||
intl: { formatMessage },
|
||||
} = this.props;
|
||||
|
||||
const contentTypeItems = {
|
||||
plain: {
|
||||
icon: 'file-text',
|
||||
name: 'text/plain',
|
||||
text: <FormattedMessage {...messages.plain} />,
|
||||
text: formatMessage(messages.plain),
|
||||
},
|
||||
html: {
|
||||
icon: 'code',
|
||||
name: 'text/html',
|
||||
text: <FormattedMessage {...messages.html} />,
|
||||
text: formatMessage(messages.html),
|
||||
},
|
||||
markdown: {
|
||||
icon: 'arrow-circle-down',
|
||||
name: 'text/markdown',
|
||||
text: <FormattedMessage {...messages.markdown} />,
|
||||
text: formatMessage(messages.markdown),
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -204,18 +243,18 @@ class ComposerOptions extends ImmutablePureComponent {
|
|||
{
|
||||
icon: 'cloud-upload',
|
||||
name: 'upload',
|
||||
text: <FormattedMessage {...messages.upload} />,
|
||||
text: formatMessage(messages.upload),
|
||||
},
|
||||
{
|
||||
icon: 'paint-brush',
|
||||
name: 'doodle',
|
||||
text: <FormattedMessage {...messages.doodle} />,
|
||||
text: formatMessage(messages.doodle),
|
||||
},
|
||||
]}
|
||||
onChange={this.handleClickAttach}
|
||||
onModalClose={onModalClose}
|
||||
onModalOpen={onModalOpen}
|
||||
title={intl.formatMessage(messages.attach)}
|
||||
title={formatMessage(messages.attach)}
|
||||
/>
|
||||
{!!pollLimits && (
|
||||
<IconButton
|
||||
|
@ -229,7 +268,7 @@ class ComposerOptions extends ImmutablePureComponent {
|
|||
height: null,
|
||||
lineHeight: null,
|
||||
}}
|
||||
title={intl.formatMessage(hasPoll ? messages.remove_poll : messages.add_poll)}
|
||||
title={formatMessage(hasPoll ? messages.remove_poll : messages.add_poll)}
|
||||
/>
|
||||
)}
|
||||
<hr />
|
||||
|
@ -252,7 +291,7 @@ class ComposerOptions extends ImmutablePureComponent {
|
|||
onChange={onChangeContentType}
|
||||
onModalClose={onModalClose}
|
||||
onModalOpen={onModalOpen}
|
||||
title={intl.formatMessage(messages.content_type)}
|
||||
title={formatMessage(messages.content_type)}
|
||||
value={contentType}
|
||||
/>
|
||||
)}
|
||||
|
@ -262,7 +301,7 @@ class ComposerOptions extends ImmutablePureComponent {
|
|||
ariaControls='glitch.composer.spoiler.input'
|
||||
label='CW'
|
||||
onClick={onToggleSpoiler}
|
||||
title={intl.formatMessage(messages.spoiler)}
|
||||
title={formatMessage(messages.spoiler)}
|
||||
/>
|
||||
)}
|
||||
<Dropdown
|
||||
|
@ -271,22 +310,22 @@ class ComposerOptions extends ImmutablePureComponent {
|
|||
icon='ellipsis-h'
|
||||
items={advancedOptions ? [
|
||||
{
|
||||
meta: <FormattedMessage {...messages.local_only_long} />,
|
||||
meta: formatMessage(messages.local_only_long),
|
||||
name: 'do_not_federate',
|
||||
on: advancedOptions.get('do_not_federate'),
|
||||
text: <FormattedMessage {...messages.local_only_short} />,
|
||||
text: formatMessage(messages.local_only_short),
|
||||
},
|
||||
{
|
||||
meta: <FormattedMessage {...messages.threaded_mode_long} />,
|
||||
meta: formatMessage(messages.threaded_mode_long),
|
||||
name: 'threaded_mode',
|
||||
on: advancedOptions.get('threaded_mode'),
|
||||
text: <FormattedMessage {...messages.threaded_mode_short} />,
|
||||
text: formatMessage(messages.threaded_mode_short),
|
||||
},
|
||||
] : null}
|
||||
onChange={onChangeAdvancedOption}
|
||||
renderItemContents={this.renderToggleItemContents}
|
||||
onModalClose={onModalClose}
|
||||
onModalOpen={onModalOpen}
|
||||
title={intl.formatMessage(messages.advanced_options_icon_title)}
|
||||
title={formatMessage(messages.advanced_options_icon_title)}
|
||||
closeOnChange={false}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -5,42 +5,15 @@ import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
|
|||
import Dropdown from './dropdown';
|
||||
|
||||
const messages = defineMessages({
|
||||
change_privacy: {
|
||||
defaultMessage: 'Adjust status privacy',
|
||||
id: 'privacy.change',
|
||||
},
|
||||
direct_long: {
|
||||
defaultMessage: 'Visible for mentioned users only',
|
||||
id: 'privacy.direct.long',
|
||||
},
|
||||
direct_short: {
|
||||
defaultMessage: 'Direct',
|
||||
id: 'privacy.direct.short',
|
||||
},
|
||||
private_long: {
|
||||
defaultMessage: 'Visible for followers only',
|
||||
id: 'privacy.private.long',
|
||||
},
|
||||
private_short: {
|
||||
defaultMessage: 'Followers-only',
|
||||
id: 'privacy.private.short',
|
||||
},
|
||||
public_long: {
|
||||
defaultMessage: 'Visible for all, shown in public timelines',
|
||||
id: 'privacy.public.long',
|
||||
},
|
||||
public_short: {
|
||||
defaultMessage: 'Public',
|
||||
id: 'privacy.public.short',
|
||||
},
|
||||
unlisted_long: {
|
||||
defaultMessage: 'Visible for all, but not in public timelines',
|
||||
id: 'privacy.unlisted.long',
|
||||
},
|
||||
unlisted_short: {
|
||||
defaultMessage: 'Unlisted',
|
||||
id: 'privacy.unlisted.short',
|
||||
},
|
||||
public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
|
||||
public_long: { id: 'privacy.public.long', defaultMessage: 'Visible for all, shown in public timelines' },
|
||||
unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' },
|
||||
unlisted_long: { id: 'privacy.unlisted.long', defaultMessage: 'Visible for all, but not in public timelines' },
|
||||
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers-only' },
|
||||
private_long: { id: 'privacy.private.long', defaultMessage: 'Visible for followers only' },
|
||||
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Direct' },
|
||||
direct_long: { id: 'privacy.direct.long', defaultMessage: 'Visible for mentioned users only' },
|
||||
change_privacy: { id: 'privacy.change', defaultMessage: 'Adjust status privacy' },
|
||||
});
|
||||
|
||||
export default @injectIntl
|
||||
|
@ -58,34 +31,34 @@ class PrivacyDropdown extends React.PureComponent {
|
|||
};
|
||||
|
||||
render () {
|
||||
const { value, onChange, onModalOpen, onModalClose, disabled, noDirect, container, intl } = this.props;
|
||||
const { value, onChange, onModalOpen, onModalClose, disabled, noDirect, container, intl: { formatMessage } } = this.props;
|
||||
|
||||
// We predefine our privacy items so that we can easily pick the
|
||||
// dropdown icon later.
|
||||
const privacyItems = {
|
||||
direct: {
|
||||
icon: 'envelope',
|
||||
meta: <FormattedMessage {...messages.direct_long} />,
|
||||
meta: formatMessage(messages.direct_long),
|
||||
name: 'direct',
|
||||
text: <FormattedMessage {...messages.direct_short} />,
|
||||
text: formatMessage(messages.direct_short),
|
||||
},
|
||||
private: {
|
||||
icon: 'lock',
|
||||
meta: <FormattedMessage {...messages.private_long} />,
|
||||
meta: formatMessage(messages.private_long),
|
||||
name: 'private',
|
||||
text: <FormattedMessage {...messages.private_short} />,
|
||||
text: formatMessage(messages.private_short),
|
||||
},
|
||||
public: {
|
||||
icon: 'globe',
|
||||
meta: <FormattedMessage {...messages.public_long} />,
|
||||
meta: formatMessage(messages.public_long),
|
||||
name: 'public',
|
||||
text: <FormattedMessage {...messages.public_short} />,
|
||||
text: formatMessage(messages.public_short),
|
||||
},
|
||||
unlisted: {
|
||||
icon: 'unlock',
|
||||
meta: <FormattedMessage {...messages.unlisted_long} />,
|
||||
meta: formatMessage(messages.unlisted_long),
|
||||
name: 'unlisted',
|
||||
text: <FormattedMessage {...messages.unlisted_short} />,
|
||||
text: formatMessage(messages.unlisted_short),
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -103,7 +76,7 @@ class PrivacyDropdown extends React.PureComponent {
|
|||
onChange={onChange}
|
||||
onModalClose={onModalClose}
|
||||
onModalOpen={onModalOpen}
|
||||
title={intl.formatMessage(messages.change_privacy)}
|
||||
title={formatMessage(messages.change_privacy)}
|
||||
container={container}
|
||||
value={value}
|
||||
/>
|
||||
|
|
|
@ -7,8 +7,7 @@ import Avatar from 'flavours/glitch/components/avatar';
|
|||
import RelativeTimestamp from 'flavours/glitch/components/relative_timestamp';
|
||||
import DisplayName from 'flavours/glitch/components/display_name';
|
||||
import classNames from 'classnames';
|
||||
import Icon from 'flavours/glitch/components/icon';
|
||||
import Toggle from 'react-toggle';
|
||||
import IconButton from 'flavours/glitch/components/icon_button';
|
||||
|
||||
export default class ActionsModal extends ImmutablePureComponent {
|
||||
|
||||
|
@ -19,12 +18,11 @@ export default class ActionsModal extends ImmutablePureComponent {
|
|||
active: PropTypes.bool,
|
||||
href: PropTypes.string,
|
||||
icon: PropTypes.string,
|
||||
meta: PropTypes.node,
|
||||
meta: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
on: PropTypes.bool,
|
||||
onPassiveClick: PropTypes.func,
|
||||
text: PropTypes.node,
|
||||
text: PropTypes.string,
|
||||
})),
|
||||
renderItemContents: PropTypes.func,
|
||||
};
|
||||
|
||||
renderAction = (action, i) => {
|
||||
|
@ -32,40 +30,25 @@ export default class ActionsModal extends ImmutablePureComponent {
|
|||
return <li key={`sep-${i}`} className='dropdown-menu__separator' />;
|
||||
}
|
||||
|
||||
const {
|
||||
active,
|
||||
href,
|
||||
icon,
|
||||
meta,
|
||||
name,
|
||||
on,
|
||||
onClick,
|
||||
onPassiveClick,
|
||||
text,
|
||||
} = action;
|
||||
const { icon = null, text, meta = null, active = false, href = '#' } = action;
|
||||
let contents = this.props.renderItemContents && this.props.renderItemContents(action, i);
|
||||
|
||||
if (!contents) {
|
||||
contents = (
|
||||
<React.Fragment>
|
||||
{icon && <IconButton title={text} icon={icon} role='presentation' tabIndex='-1' inverted />}
|
||||
<div>
|
||||
<div className={classNames({ 'actions-modal__item-label': !!meta })}>{text}</div>
|
||||
<div>{meta}</div>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<li key={name || i}>
|
||||
<a href={href} target='_blank' rel='noopener noreferrer' onClick={on !== null && typeof on !== 'undefined' && onPassiveClick || onClick || this.props.onClick} data-index={i} className={classNames('link', { active })}>
|
||||
{on !== null && typeof on !== 'undefined' && (
|
||||
<Toggle
|
||||
checked={on}
|
||||
onChange={onPassiveClick || onClick}
|
||||
/>
|
||||
)}
|
||||
{icon && (
|
||||
<Icon
|
||||
className='icon'
|
||||
fixedWidth
|
||||
id={icon}
|
||||
/>
|
||||
)}
|
||||
{meta ? (
|
||||
<div>
|
||||
<strong>{text}</strong>
|
||||
{meta}
|
||||
</div>
|
||||
) : <div>{text}</div>}
|
||||
<li key={`${text}-${i}`}>
|
||||
<a href={href} target='_blank' rel='noopener noreferrer' onClick={this.props.onClick} data-index={i} className={classNames('link', { active })}>
|
||||
{contents}
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
|
|
Reference in New Issue