Permalink
Browse files

Refactor icons in web UI to use Icon component (#9951)

* 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 1f9519020296c8c24a73d3f99d3c1ad94a627f3b
Showing with 147 additions and 82 deletions.
  1. +5 −0 .eslintrc.js
  2. +3 −2 app/javascript/mastodon/components/attachment_list.js
  3. +2 −1 app/javascript/mastodon/components/column_back_button.js
  4. +2 −1 app/javascript/mastodon/components/column_back_button_slim.js
  5. +8 −7 app/javascript/mastodon/components/column_header.js
  6. +21 −0 app/javascript/mastodon/components/icon.js
  7. +3 −2 app/javascript/mastodon/components/icon_button.js
  8. +2 −1 app/javascript/mastodon/components/load_gap.js
  9. +3 −2 app/javascript/mastodon/components/status.js
  10. +2 −1 app/javascript/mastodon/components/status_content.js
  11. +3 −2 app/javascript/mastodon/features/account/components/header.js
  12. +2 −1 app/javascript/mastodon/features/account_gallery/components/media_item.js
  13. +2 −1 app/javascript/mastodon/features/account_timeline/components/moved_note.js
  14. +2 −1 app/javascript/mastodon/features/compose/components/compose_form.js
  15. +2 −1 app/javascript/mastodon/features/compose/components/privacy_dropdown.js
  16. +3 −2 app/javascript/mastodon/features/compose/components/search.js
  17. +6 −5 app/javascript/mastodon/features/compose/components/search_results.js
  18. +3 −2 app/javascript/mastodon/features/compose/components/upload.js
  19. +2 −1 app/javascript/mastodon/features/compose/components/upload_progress.js
  20. +8 −7 app/javascript/mastodon/features/compose/index.js
  21. +2 −1 app/javascript/mastodon/features/getting_started/index.js
  22. +2 −1 app/javascript/mastodon/features/list_adder/components/list.js
  23. +3 −2 app/javascript/mastodon/features/list_editor/components/search.js
  24. +3 −2 app/javascript/mastodon/features/list_timeline/index.js
  25. +2 −1 app/javascript/mastodon/features/notifications/components/clear_column_button.js
  26. +5 −4 app/javascript/mastodon/features/notifications/components/filter_bar.js
  27. +4 −3 app/javascript/mastodon/features/notifications/components/notification.js
  28. +4 −3 app/javascript/mastodon/features/status/components/card.js
  29. +6 −5 app/javascript/mastodon/features/status/components/detailed_status.js
  30. +2 −1 app/javascript/mastodon/features/status/index.js
  31. +2 −1 app/javascript/mastodon/features/ui/components/boost_modal.js
  32. +2 −1 app/javascript/mastodon/features/ui/components/column_header.js
  33. +3 −2 app/javascript/mastodon/features/ui/components/column_link.js
  34. +2 −1 app/javascript/mastodon/features/ui/components/columns_area.js
  35. +3 −2 app/javascript/mastodon/features/ui/components/media_modal.js
  36. +7 −6 app/javascript/mastodon/features/ui/components/tabs_bar.js
  37. +7 −6 app/javascript/mastodon/features/video/index.js
  38. +4 −0 jest.config.js
@@ -41,6 +41,11 @@ module.exports = {
'node_modules',
'\\.(css|scss|json)$',
],
'import/resolver': {
node: {
paths: ['app/javascript'],
},
},
},

rules: {
@@ -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];

@@ -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>
);
})}
@@ -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'>
@@ -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 {

@@ -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>
);
@@ -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>
@@ -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' },
@@ -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>
);
@@ -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;
@@ -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>
)}
@@ -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} />
);
}

}
@@ -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 {

@@ -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>
);
}
@@ -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>
@@ -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' },
@@ -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>
);
}
@@ -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
@@ -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>
);
@@ -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>
);
@@ -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)

@@ -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>
);

@@ -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' },
@@ -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') };
@@ -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>
))}
@@ -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 {

@@ -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>
);
}
@@ -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 {

@@ -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>

@@ -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';

@@ -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);
}
@@ -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' },
@@ -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'>
@@ -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' },
@@ -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}>
Oops, something went wrong.

0 comments on commit 1f95190

Please sign in to comment.