Skip to content

Commit

Permalink
Refactor icons in web UI to use Icon component (#9951)
Browse files Browse the repository at this point in the history
* Refactor uses of icons to an Icon component in web UI

* Refactor options passed to the Icon component

* Make tests work with absolute component paths
  • Loading branch information
Gargron committed Jan 31, 2019
1 parent 3383ed7 commit 1f95190
Show file tree
Hide file tree
Showing 38 changed files with 147 additions and 82 deletions.
5 changes: 5 additions & 0 deletions .eslintrc.js
Expand Up @@ -41,6 +41,11 @@ module.exports = {
'node_modules',
'\\.(css|scss|json)$',
],
'import/resolver': {
node: {
paths: ['app/javascript'],
},
},
},

rules: {
Expand Down
5 changes: 3 additions & 2 deletions app/javascript/mastodon/components/attachment_list.js
Expand Up @@ -2,6 +2,7 @@ import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import ImmutablePureComponent from 'react-immutable-pure-component';
import Icon from 'mastodon/components/icon';

const filename = url => url.split('/').pop().split('#')[0].split('?')[0];

Expand All @@ -24,7 +25,7 @@ export default class AttachmentList extends ImmutablePureComponent {

return (
<li key={attachment.get('id')}>
<a href={displayUrl} target='_blank' rel='noopener'><i className='fa fa-link' /> {filename(displayUrl)}</a>
<a href={displayUrl} target='_blank' rel='noopener'><Icon id='link' /> {filename(displayUrl)}</a>
</li>
);
})}
Expand All @@ -36,7 +37,7 @@ export default class AttachmentList extends ImmutablePureComponent {
return (
<div className='attachment-list'>
<div className='attachment-list__icon'>
<i className='fa fa-link' />
<Icon id='link' />
</div>

<ul className='attachment-list__list'>
Expand Down
3 changes: 2 additions & 1 deletion app/javascript/mastodon/components/column_back_button.js
@@ -1,6 +1,7 @@
import React from 'react';
import { FormattedMessage } from 'react-intl';
import PropTypes from 'prop-types';
import Icon from 'mastodon/components/icon';

export default class ColumnBackButton extends React.PureComponent {

Expand All @@ -19,7 +20,7 @@ export default class ColumnBackButton extends React.PureComponent {
render () {
return (
<button onClick={this.handleClick} className='column-back-button'>
<i className='fa fa-fw fa-chevron-left column-back-button__icon' />
<Icon id='chevron-left' className='column-back-button__icon' fixedWidth />
<FormattedMessage id='column_back_button.label' defaultMessage='Back' />
</button>
);
Expand Down
@@ -1,14 +1,15 @@
import React from 'react';
import { FormattedMessage } from 'react-intl';
import ColumnBackButton from './column_back_button';
import Icon from 'mastodon/components/icon';

export default class ColumnBackButtonSlim extends ColumnBackButton {

render () {
return (
<div className='column-back-button--slim'>
<div role='button' tabIndex='0' onClick={this.handleClick} className='column-back-button column-back-button--slim-button'>
<i className='fa fa-fw fa-chevron-left column-back-button__icon' />
<Icon id='chevron-left' className='column-back-button__icon' fixedWidth />
<FormattedMessage id='column_back_button.label' defaultMessage='Back' />
</div>
</div>
Expand Down
15 changes: 8 additions & 7 deletions app/javascript/mastodon/components/column_header.js
Expand Up @@ -2,6 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { FormattedMessage, injectIntl, defineMessages } from 'react-intl';
import Icon from 'mastodon/components/icon';

const messages = defineMessages({
show: { id: 'column_header.show_settings', defaultMessage: 'Show settings' },
Expand Down Expand Up @@ -109,22 +110,22 @@ class ColumnHeader extends React.PureComponent {
}

if (multiColumn && pinned) {
pinButton = <button key='pin-button' className='text-btn column-header__setting-btn' onClick={this.handlePin}><i className='fa fa fa-times' /> <FormattedMessage id='column_header.unpin' defaultMessage='Unpin' /></button>;
pinButton = <button key='pin-button' className='text-btn column-header__setting-btn' onClick={this.handlePin}><Icon id='times' /> <FormattedMessage id='column_header.unpin' defaultMessage='Unpin' /></button>;

moveButtons = (
<div key='move-buttons' className='column-header__setting-arrows'>
<button title={formatMessage(messages.moveLeft)} aria-label={formatMessage(messages.moveLeft)} className='text-btn column-header__setting-btn' onClick={this.handleMoveLeft}><i className='fa fa-chevron-left' /></button>
<button title={formatMessage(messages.moveRight)} aria-label={formatMessage(messages.moveRight)} className='text-btn column-header__setting-btn' onClick={this.handleMoveRight}><i className='fa fa-chevron-right' /></button>
<button title={formatMessage(messages.moveLeft)} aria-label={formatMessage(messages.moveLeft)} className='text-btn column-header__setting-btn' onClick={this.handleMoveLeft}><Icon id='chevron-left' /></button>
<button title={formatMessage(messages.moveRight)} aria-label={formatMessage(messages.moveRight)} className='text-btn column-header__setting-btn' onClick={this.handleMoveRight}><Icon id='chevron-right' /></button>
</div>
);
} else if (multiColumn) {
pinButton = <button key='pin-button' className='text-btn column-header__setting-btn' onClick={this.handlePin}><i className='fa fa fa-plus' /> <FormattedMessage id='column_header.pin' defaultMessage='Pin' /></button>;
pinButton = <button key='pin-button' className='text-btn column-header__setting-btn' onClick={this.handlePin}><Icon id='plus' /> <FormattedMessage id='column_header.pin' defaultMessage='Pin' /></button>;
}

if (!pinned && (multiColumn || showBackButton)) {
backButton = (
<button onClick={this.handleBackClick} className='column-header__back-button'>
<i className='fa fa-fw fa-chevron-left column-back-button__icon' />
<Icon id='chevron-left' className='column-back-button__icon' fixedWidth />
<FormattedMessage id='column_back_button.label' defaultMessage='Back' />
</button>
);
Expand All @@ -140,7 +141,7 @@ class ColumnHeader extends React.PureComponent {
}

if (children || multiColumn) {
collapseButton = <button className={collapsibleButtonClassName} title={formatMessage(collapsed ? messages.show : messages.hide)} aria-label={formatMessage(collapsed ? messages.show : messages.hide)} aria-pressed={collapsed ? 'false' : 'true'} onClick={this.handleToggleClick}><i className='fa fa-sliders' /></button>;
collapseButton = <button className={collapsibleButtonClassName} title={formatMessage(collapsed ? messages.show : messages.hide)} aria-label={formatMessage(collapsed ? messages.show : messages.hide)} aria-pressed={collapsed ? 'false' : 'true'} onClick={this.handleToggleClick}><Icon id='sliders' /></button>;
}

const hasTitle = icon && title;
Expand All @@ -150,7 +151,7 @@ class ColumnHeader extends React.PureComponent {
<h1 className={buttonClassName}>
{hasTitle && (
<button onClick={this.handleTitleClick}>
<i className={`fa fa-fw fa-${icon} column-header__icon`} />
<Icon id={icon} fixedWidth className='column-header__icon' />
{title}
</button>
)}
Expand Down
21 changes: 21 additions & 0 deletions app/javascript/mastodon/components/icon.js
@@ -0,0 +1,21 @@
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';

export default class Icon extends React.PureComponent {

static propTypes = {
id: PropTypes.string.isRequired,
className: PropTypes.string,
fixedWidth: PropTypes.bool,
};

render () {
const { id, className, fixedWidth, ...other } = this.props;

return (
<i role='img' className={classNames('fa', `fa-${id}`, className, { 'fa-fw': fixedWidth })} {...other} />
);
}

}
5 changes: 3 additions & 2 deletions app/javascript/mastodon/components/icon_button.js
Expand Up @@ -3,6 +3,7 @@ import Motion from '../features/ui/util/optional_motion';
import spring from 'react-motion/lib/spring';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import Icon from 'mastodon/components/icon';

export default class IconButton extends React.PureComponent {

Expand Down Expand Up @@ -86,7 +87,7 @@ export default class IconButton extends React.PureComponent {
style={style}
tabIndex={tabIndex}
>
<i className={`fa fa-fw fa-${icon}`} aria-hidden='true' />
<Icon id={icon} fixedWidth aria-hidden='true' />
</button>
);
}
Expand All @@ -104,7 +105,7 @@ export default class IconButton extends React.PureComponent {
style={style}
tabIndex={tabIndex}
>
<i style={{ transform: `rotate(${rotate}deg)` }} className={`fa fa-fw fa-${icon}`} aria-hidden='true' />
<Icon id={icon} style={{ transform: `rotate(${rotate}deg)` }} fixedWidth aria-hidden='true' />
</button>
)}
</Motion>
Expand Down
3 changes: 2 additions & 1 deletion app/javascript/mastodon/components/load_gap.js
@@ -1,6 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { injectIntl, defineMessages } from 'react-intl';
import Icon from 'mastodon/components/icon';

const messages = defineMessages({
load_more: { id: 'status.load_more', defaultMessage: 'Load more' },
Expand All @@ -25,7 +26,7 @@ class LoadGap extends React.PureComponent {

return (
<button className='load-more load-gap' disabled={disabled} onClick={this.handleClick} aria-label={intl.formatMessage(messages.load_more)}>
<i className='fa fa-ellipsis-h' />
<Icon id='ellipsis-h' />
</button>
);
}
Expand Down
5 changes: 3 additions & 2 deletions app/javascript/mastodon/components/status.js
Expand Up @@ -15,6 +15,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
import { MediaGallery, Video } from '../features/ui/util/async-components';
import { HotKeys } from 'react-hotkeys';
import classNames from 'classnames';
import Icon from 'mastodon/components/icon';

// We use the component (and not the container) since we do not want
// to use the progress bar to show download progress
Expand Down Expand Up @@ -204,7 +205,7 @@ class Status extends ImmutablePureComponent {
if (featured) {
prepend = (
<div className='status__prepend'>
<div className='status__prepend-icon-wrapper'><i className='fa fa-fw fa-thumb-tack status__prepend-icon' /></div>
<div className='status__prepend-icon-wrapper'><Icon id='thumb-tack' className='status__prepend-icon' fixedWidth /></div>
<FormattedMessage id='status.pinned' defaultMessage='Pinned toot' />
</div>
);
Expand All @@ -213,7 +214,7 @@ class Status extends ImmutablePureComponent {

prepend = (
<div className='status__prepend'>
<div className='status__prepend-icon-wrapper'><i className='fa fa-fw fa-retweet status__prepend-icon' /></div>
<div className='status__prepend-icon-wrapper'><Icon id='retweet' className='status__prepend-icon' fixedWidth /></div>
<FormattedMessage id='status.reblogged_by' defaultMessage='{name} boosted' values={{ name: <a onClick={this.handleAccountClick} data-id={status.getIn(['account', 'id'])} href={status.getIn(['account', 'url'])} className='status__display-name muted'><bdi><strong dangerouslySetInnerHTML={display_name_html} /></bdi></a> }} />
</div>
);
Expand Down
3 changes: 2 additions & 1 deletion app/javascript/mastodon/components/status_content.js
Expand Up @@ -5,6 +5,7 @@ import { isRtl } from '../rtl';
import { FormattedMessage } from 'react-intl';
import Permalink from './permalink';
import classnames from 'classnames';
import Icon from 'mastodon/components/icon';

const MAX_HEIGHT = 642; // 20px * 32 (+ 2px padding at the top)

Expand Down Expand Up @@ -160,7 +161,7 @@ export default class StatusContent extends React.PureComponent {

const readMoreButton = (
<button className='status__content__read-more-button' onClick={this.props.onClick} key='read-more'>
<FormattedMessage id='status.read_more' defaultMessage='Read more' /><i className='fa fa-fw fa-angle-right' />
<FormattedMessage id='status.read_more' defaultMessage='Read more' /><Icon id='angle-right' fixedWidth />
</button>
);

Expand Down
5 changes: 3 additions & 2 deletions app/javascript/mastodon/features/account/components/header.js
Expand Up @@ -8,6 +8,7 @@ import spring from 'react-motion/lib/spring';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { autoPlayGif, me } from '../../../initial_state';
import classNames from 'classnames';
import Icon from 'mastodon/components/icon';

const messages = defineMessages({
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
Expand Down Expand Up @@ -149,7 +150,7 @@ class Header extends ImmutablePureComponent {
}

if (account.get('locked')) {
lockedIcon = <i className='fa fa-lock' title={intl.formatMessage(messages.account_locked)} />;
lockedIcon = <Icon id='lock' title={intl.formatMessage(messages.account_locked)} />;
}

const content = { __html: account.get('note_emojified') };
Expand All @@ -176,7 +177,7 @@ class Header extends ImmutablePureComponent {
<dt dangerouslySetInnerHTML={{ __html: pair.get('name_emojified') }} title={pair.get('name')} />

<dd className={pair.get('verified_at') && 'verified'} title={pair.get('value_plain')}>
{pair.get('verified_at') && <span title={intl.formatMessage(messages.linkVerifiedOn, { date: intl.formatDate(pair.get('verified_at'), dateFormatOptions) })}><i className='fa fa-check verified__mark' /></span>} <span dangerouslySetInnerHTML={{ __html: pair.get('value_emojified') }} />
{pair.get('verified_at') && <span title={intl.formatMessage(messages.linkVerifiedOn, { date: intl.formatDate(pair.get('verified_at'), dateFormatOptions) })}><Icon id='check' className='verified__mark' /></span>} <span dangerouslySetInnerHTML={{ __html: pair.get('value_emojified') }} />
</dd>
</dl>
))}
Expand Down
Expand Up @@ -3,6 +3,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import Permalink from '../../../components/permalink';
import { displayMedia } from '../../../initial_state';
import Icon from 'mastodon/components/icon';

export default class MediaItem extends ImmutablePureComponent {

Expand Down Expand Up @@ -45,7 +46,7 @@ export default class MediaItem extends ImmutablePureComponent {
} else {
icon = (
<span className='account-gallery__item__icons'>
<i className='fa fa-eye-slash' />
<Icon id='eye-slash' />
</span>
);
}
Expand Down
Expand Up @@ -5,6 +5,7 @@ import { FormattedMessage } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import AvatarOverlay from '../../../components/avatar_overlay';
import DisplayName from '../../../components/display_name';
import Icon from 'mastodon/components/icon';

export default class MovedNote extends ImmutablePureComponent {

Expand Down Expand Up @@ -33,7 +34,7 @@ export default class MovedNote extends ImmutablePureComponent {
return (
<div className='account__moved-note'>
<div className='account__moved-note__message'>
<div className='account__moved-note__icon-wrapper'><i className='fa fa-fw fa-suitcase account__moved-note__icon' /></div>
<div className='account__moved-note__icon-wrapper'><Icon id='suitcase' className='account__moved-note__icon' fixedWidth /></div>
<FormattedMessage id='account.moved_to' defaultMessage='{name} has moved to:' values={{ name: <bdi><strong dangerouslySetInnerHTML={displayNameHtml} /></bdi> }} />
</div>

Expand Down
Expand Up @@ -17,6 +17,7 @@ import { isMobile } from '../../../is_mobile';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { length } from 'stringz';
import { countableText } from '../util/counter';
import Icon from 'mastodon/components/icon';

const allowedAroundShortCode = '><\u0085\u0020\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029\u0009\u000a\u000b\u000c\u000d';

Expand Down Expand Up @@ -165,7 +166,7 @@ class ComposeForm extends ImmutablePureComponent {
let publishText = '';

if (this.props.privacy === 'private' || this.props.privacy === 'direct') {
publishText = <span className='compose-form__publish-private'><i className='fa fa-lock' /> {intl.formatMessage(messages.publish)}</span>;
publishText = <span className='compose-form__publish-private'><Icon id='lock' /> {intl.formatMessage(messages.publish)}</span>;
} else {
publishText = this.props.privacy !== 'unlisted' ? intl.formatMessage(messages.publishLoud, { publish: intl.formatMessage(messages.publish) }) : intl.formatMessage(messages.publish);
}
Expand Down
Expand Up @@ -7,6 +7,7 @@ import Motion from '../../ui/util/optional_motion';
import spring from 'react-motion/lib/spring';
import detectPassiveEvents from 'detect-passive-events';
import classNames from 'classnames';
import Icon from 'mastodon/components/icon';

const messages = defineMessages({
public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
Expand Down Expand Up @@ -132,7 +133,7 @@ class PrivacyDropdownMenu extends React.PureComponent {
{items.map(item => (
<div role='option' tabIndex='0' key={item.value} data-index={item.value} onKeyDown={this.handleKeyDown} onClick={this.handleClick} className={classNames('privacy-dropdown__option', { active: item.value === value })} aria-selected={item.value === value} ref={item.value === value ? this.setFocusRef : null}>
<div className='privacy-dropdown__option__icon'>
<i className={`fa fa-fw fa-${item.icon}`} />
<Icon id={item.icon} fixedWidth />
</div>

<div className='privacy-dropdown__option__content'>
Expand Down
5 changes: 3 additions & 2 deletions app/javascript/mastodon/features/compose/components/search.js
Expand Up @@ -5,6 +5,7 @@ import Overlay from 'react-overlays/lib/Overlay';
import Motion from '../../ui/util/optional_motion';
import spring from 'react-motion/lib/spring';
import { searchEnabled } from '../../../initial_state';
import Icon from 'mastodon/components/icon';

const messages = defineMessages({
placeholder: { id: 'search.placeholder', defaultMessage: 'Search' },
Expand Down Expand Up @@ -116,8 +117,8 @@ class Search extends React.PureComponent {
</label>

<div role='button' tabIndex='0' className='search__icon' onClick={this.handleClear}>
<i className={`fa fa-search ${hasValue ? '' : 'active'}`} />
<i aria-label={intl.formatMessage(messages.placeholder)} className={`fa fa-times-circle ${hasValue ? 'active' : ''}`} />
<Icon id='search' className={hasValue ? '' : 'active'} />
<Icon id='times-circle' className={hasValue ? 'active' : ''} aria-label={intl.formatMessage(messages.placeholder)} />
</div>

<Overlay show={expanded && !hasValue} placement='bottom' target={this}>
Expand Down

0 comments on commit 1f95190

Please sign in to comment.