Skip to content

Commit

Permalink
Change icons in web UI (mastodon#27385)
Browse files Browse the repository at this point in the history
Co-authored-by: Renaud Chaput <renchap@gmail.com>
  • Loading branch information
Gargron and renchap authored Oct 24, 2023
1 parent b188538 commit 134de73
Show file tree
Hide file tree
Showing 123 changed files with 928 additions and 726 deletions.
3 changes: 3 additions & 0 deletions app/javascript/__mocks__/svg.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// eslint-disable-next-line import/no-anonymous-default-export
export default 'SvgrURL';
export const ReactComponent = 'div';
14 changes: 2 additions & 12 deletions app/javascript/mastodon/components/account.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import { Avatar } from './avatar';
import { Button } from './button';
import { FollowersCounter } from './counters';
import { DisplayName } from './display_name';
import { IconButton } from './icon_button';
import { RelativeTimestamp } from './relative_timestamp';

const messages = defineMessages({
Expand All @@ -45,10 +44,7 @@ class Account extends ImmutablePureComponent {
intl: PropTypes.object.isRequired,
hidden: PropTypes.bool,
minimal: PropTypes.bool,
actionIcon: PropTypes.string,
actionTitle: PropTypes.string,
defaultAction: PropTypes.string,
onActionClick: PropTypes.func,
withBio: PropTypes.bool,
};

Expand Down Expand Up @@ -76,12 +72,8 @@ class Account extends ImmutablePureComponent {
this.props.onMuteNotifications(this.props.account, false);
};

handleAction = () => {
this.props.onActionClick(this.props.account);
};

render () {
const { account, intl, hidden, withBio, onActionClick, actionIcon, actionTitle, defaultAction, size, minimal } = this.props;
const { account, intl, hidden, withBio, defaultAction, size, minimal } = this.props;

if (!account) {
return <EmptyAccount size={size} minimal={minimal} />;
Expand All @@ -98,9 +90,7 @@ class Account extends ImmutablePureComponent {

let buttons;

if (actionIcon && onActionClick) {
buttons = <IconButton icon={actionIcon} title={actionTitle} onClick={this.handleAction} />;
} else if (!actionIcon && account.get('id') !== me && account.get('relationship', null) !== null) {
if (account.get('id') !== me && account.get('relationship', null) !== null) {
const following = account.getIn(['relationship', 'following']);
const requested = account.getIn(['relationship', 'requested']);
const blocking = account.getIn(['relationship', 'blocking']);
Expand Down
6 changes: 4 additions & 2 deletions app/javascript/mastodon/components/attachment_list.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import classNames from 'classnames';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';

import { ReactComponent as LinkIcon } from '@material-symbols/svg-600/outlined/link.svg';

import { Icon } from 'mastodon/components/icon';

const filename = url => url.split('/').pop().split('#')[0].split('?')[0];
Expand All @@ -25,7 +27,7 @@ export default class AttachmentList extends ImmutablePureComponent {
<div className={classNames('attachment-list', { compact })}>
{!compact && (
<div className='attachment-list__icon'>
<Icon id='link' />
<Icon id='link' icon={LinkIcon} />
</div>
)}

Expand All @@ -36,7 +38,7 @@ export default class AttachmentList extends ImmutablePureComponent {
return (
<li key={attachment.get('id')}>
<a href={displayUrl} target='_blank' rel='noopener noreferrer'>
{compact && <Icon id='link' />}
{compact && <Icon id='link' icon={LinkIcon} />}
{compact && ' ' }
{displayUrl ? filename(displayUrl) : <FormattedMessage id='attachments_list.unprocessed' defaultMessage='(unprocessed)' />}
</a>
Expand Down
6 changes: 3 additions & 3 deletions app/javascript/mastodon/components/badge.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import PropTypes from 'prop-types';

import { FormattedMessage } from 'react-intl';

import { ReactComponent as GroupsIcon } from '@material-design-icons/svg/outlined/group.svg';
import { ReactComponent as PersonIcon } from '@material-design-icons/svg/outlined/person.svg';
import { ReactComponent as SmartToyIcon } from '@material-design-icons/svg/outlined/smart_toy.svg';
import { ReactComponent as GroupsIcon } from '@material-symbols/svg-600/outlined/group.svg';
import { ReactComponent as PersonIcon } from '@material-symbols/svg-600/outlined/person.svg';
import { ReactComponent as SmartToyIcon } from '@material-symbols/svg-600/outlined/smart_toy.svg';


export const Badge = ({ icon, label, domain }) => (
Expand Down
13 changes: 0 additions & 13 deletions app/javascript/mastodon/components/check.tsx

This file was deleted.

4 changes: 3 additions & 1 deletion app/javascript/mastodon/components/column_back_button.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { FormattedMessage } from 'react-intl';

import { withRouter } from 'react-router-dom';

import { ReactComponent as ArrowBackIcon } from '@material-symbols/svg-600/outlined/arrow_back.svg';

import { Icon } from 'mastodon/components/icon';
import { WithRouterPropTypes } from 'mastodon/utils/react_router';

Expand Down Expand Up @@ -34,7 +36,7 @@ class ColumnBackButton extends PureComponent {

const component = (
<button onClick={this.handleClick} className='column-back-button'>
<Icon id='chevron-left' className='column-back-button__icon' fixedWidth />
<Icon id='chevron-left' icon={ArrowBackIcon} className='column-back-button__icon' />
<FormattedMessage id='column_back_button.label' defaultMessage='Back' />
</button>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { FormattedMessage } from 'react-intl';

import { ReactComponent as ArrowBackIcon } from '@material-symbols/svg-600/outlined/arrow_back.svg';

import { Icon } from 'mastodon/components/icon';

import ColumnBackButton from './column_back_button';
Expand All @@ -10,7 +12,7 @@ export default class ColumnBackButtonSlim extends ColumnBackButton {
return (
<div className='column-back-button--slim'>
<div role='button' tabIndex={0} onClick={this.handleClick} className='column-back-button column-back-button--slim-button'>
<Icon id='chevron-left' className='column-back-button__icon' fixedWidth />
<Icon id='chevron-left' icon={ArrowBackIcon} className='column-back-button__icon' />
<FormattedMessage id='column_back_button.label' defaultMessage='Back' />
</div>
</div>
Expand Down
26 changes: 17 additions & 9 deletions app/javascript/mastodon/components/column_header.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ import { FormattedMessage, injectIntl, defineMessages } from 'react-intl';
import classNames from 'classnames';
import { withRouter } from 'react-router-dom';

import { ReactComponent as AddIcon } from '@material-symbols/svg-600/outlined/add.svg';
import { ReactComponent as ArrowBackIcon } from '@material-symbols/svg-600/outlined/arrow_back.svg';
import { ReactComponent as ChevronLeftIcon } from '@material-symbols/svg-600/outlined/chevron_left.svg';
import { ReactComponent as ChevronRightIcon } from '@material-symbols/svg-600/outlined/chevron_right.svg';
import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg';
import { ReactComponent as TuneIcon } from '@material-symbols/svg-600/outlined/tune.svg';

import { Icon } from 'mastodon/components/icon';
import { WithRouterPropTypes } from 'mastodon/utils/react_router';

Expand All @@ -27,6 +34,7 @@ class ColumnHeader extends PureComponent {
intl: PropTypes.object.isRequired,
title: PropTypes.node,
icon: PropTypes.string,
iconComponent: PropTypes.func,
active: PropTypes.bool,
multiColumn: PropTypes.bool,
extraButton: PropTypes.node,
Expand Down Expand Up @@ -87,7 +95,7 @@ class ColumnHeader extends PureComponent {
};

render () {
const { title, icon, active, children, pinned, multiColumn, extraButton, showBackButton, intl: { formatMessage }, placeholder, appendContent, collapseIssues, history } = this.props;
const { title, icon, iconComponent, active, children, pinned, multiColumn, extraButton, showBackButton, intl: { formatMessage }, placeholder, appendContent, collapseIssues, history } = this.props;
const { collapsed, animating } = this.state;

const wrapperClassName = classNames('column-header__wrapper', {
Expand Down Expand Up @@ -118,22 +126,22 @@ class ColumnHeader extends PureComponent {
}

if (multiColumn && pinned) {
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>;
pinButton = <button key='pin-button' className='text-btn column-header__setting-btn' onClick={this.handlePin}><Icon id='times' icon={CloseIcon} /> <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='icon-button column-header__setting-btn' onClick={this.handleMoveLeft}><Icon id='chevron-left' /></button>
<button title={formatMessage(messages.moveRight)} aria-label={formatMessage(messages.moveRight)} className='icon-button column-header__setting-btn' onClick={this.handleMoveRight}><Icon id='chevron-right' /></button>
<button title={formatMessage(messages.moveLeft)} aria-label={formatMessage(messages.moveLeft)} className='icon-button column-header__setting-btn' onClick={this.handleMoveLeft}><Icon id='chevron-left' icon={ChevronLeftIcon} /></button>
<button title={formatMessage(messages.moveRight)} aria-label={formatMessage(messages.moveRight)} className='icon-button column-header__setting-btn' onClick={this.handleMoveRight}><Icon id='chevron-right' icon={ChevronRightIcon} /></button>
</div>
);
} else if (multiColumn && this.props.onPin) {
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>;
pinButton = <button key='pin-button' className='text-btn column-header__setting-btn' onClick={this.handlePin}><Icon id='plus' icon={AddIcon} /> <FormattedMessage id='column_header.pin' defaultMessage='Pin' /></button>;
}

if (!pinned && ((multiColumn && history.location?.state?.fromMastodon) || showBackButton)) {
backButton = (
<button onClick={this.handleBackClick} className='column-header__back-button'>
<Icon id='chevron-left' className='column-back-button__icon' fixedWidth />
<Icon id='chevron-left' icon={ArrowBackIcon} className='column-back-button__icon' />
<FormattedMessage id='column_back_button.label' defaultMessage='Back' />
</button>
);
Expand All @@ -157,21 +165,21 @@ class ColumnHeader extends PureComponent {
onClick={this.handleToggleClick}
>
<i className='icon-with-badge'>
<Icon id='sliders' />
<Icon id='sliders' icon={TuneIcon} />
{collapseIssues && <i className='icon-with-badge__issue-badge' />}
</i>
</button>
);
}

const hasTitle = icon && title;
const hasTitle = (icon || iconComponent) && title;

const component = (
<div className={wrapperClassName}>
<h1 className={buttonClassName}>
{hasTitle && (
<button onClick={this.handleTitleClick}>
<Icon id={icon} fixedWidth className='column-header__icon' />
<Icon id={icon} icon={iconComponent} className='column-header__icon' />
{title}
</button>
)}
Expand Down
3 changes: 3 additions & 0 deletions app/javascript/mastodon/components/dismissable_banner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { useCallback, useState } from 'react';

import { defineMessages, useIntl } from 'react-intl';

import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg';

import { bannerSettings } from 'mastodon/settings';

import { IconButton } from './icon_button';
Expand Down Expand Up @@ -36,6 +38,7 @@ export const DismissableBanner: React.FC<PropsWithChildren<Props>> = ({
<div className='dismissable-banner__action'>
<IconButton
icon='times'
iconComponent={CloseIcon}
title={intl.formatMessage(messages.dismiss)}
onClick={handleDismiss}
/>
Expand Down
3 changes: 3 additions & 0 deletions app/javascript/mastodon/components/domain.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { useCallback } from 'react';

import { defineMessages, useIntl } from 'react-intl';

import { ReactComponent as LockOpenIcon } from '@material-symbols/svg-600/outlined/lock_open.svg';

import { IconButton } from './icon_button';

const messages = defineMessages({
Expand Down Expand Up @@ -34,6 +36,7 @@ export const Domain: React.FC<Props> = ({ domain, onUnblockDomain }) => {
<IconButton
active
icon='unlock'
iconComponent={LockOpenIcon}
title={intl.formatMessage(messages.unblockDomain, { domain })}
onClick={handleDomainUnblock}
/>
Expand Down
13 changes: 9 additions & 4 deletions app/javascript/mastodon/components/dropdown_menu.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { withRouter } from 'react-router-dom';

import ImmutablePropTypes from 'react-immutable-proptypes';

import { ReactComponent as CloseIcon } from '@material-symbols/svg-600/outlined/close.svg';
import { supportsPassiveEvents } from 'detect-passive-events';
import Overlay from 'react-overlays/Overlay';

Expand Down Expand Up @@ -163,6 +164,7 @@ class Dropdown extends PureComponent {
static propTypes = {
children: PropTypes.node,
icon: PropTypes.string,
iconComponent: PropTypes.func,
items: PropTypes.oneOfType([PropTypes.array, ImmutablePropTypes.list]).isRequired,
loading: PropTypes.bool,
size: PropTypes.number,
Expand Down Expand Up @@ -255,7 +257,7 @@ class Dropdown extends PureComponent {
};

findTarget = () => {
return this.target;
return this.target?.buttonRef?.current;
};

componentWillUnmount = () => {
Expand All @@ -271,6 +273,7 @@ class Dropdown extends PureComponent {
render () {
const {
icon,
iconComponent,
items,
size,
title,
Expand All @@ -291,9 +294,11 @@ class Dropdown extends PureComponent {
onMouseDown: this.handleMouseDown,
onKeyDown: this.handleButtonKeyDown,
onKeyPress: this.handleKeyPress,
ref: this.setTargetRef,
}) : (
<IconButton
icon={!open ? icon : 'close'}
iconComponent={!open ? iconComponent : CloseIcon}
title={title}
active={open}
disabled={disabled}
Expand All @@ -302,14 +307,14 @@ class Dropdown extends PureComponent {
onMouseDown={this.handleMouseDown}
onKeyDown={this.handleButtonKeyDown}
onKeyPress={this.handleKeyPress}
ref={this.setTargetRef}
/>
);

return (
<>
<span ref={this.setTargetRef}>
{button}
</span>
{button}

<Overlay show={open} offset={[5, 5]} placement={'bottom'} flip target={this.findTarget} popperConfig={{ strategy: 'fixed' }}>
{({ props, arrowProps, placement }) => (
<div {...props}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { FormattedMessage, injectIntl } from 'react-intl';

import { connect } from 'react-redux';

import { ReactComponent as ArrowDropDownIcon } from '@material-symbols/svg-600/outlined/arrow_drop_down.svg';

import { openModal } from 'mastodon/actions/modal';
import { Icon } from 'mastodon/components/icon';
import InlineAccount from 'mastodon/components/inline_account';
Expand Down Expand Up @@ -66,7 +68,7 @@ class EditedTimestamp extends PureComponent {
return (
<DropdownMenu statusId={statusId} renderItem={this.renderItem} scrollable renderHeader={this.renderHeader} onItemClick={this.handleItemClick}>
<button className='dropdown-menu__text-button'>
<FormattedMessage id='status.edited' defaultMessage='Edited {date}' values={{ date: intl.formatDate(timestamp, { hour12: false, month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' }) }} /> <Icon id='caret-down' />
<FormattedMessage id='status.edited' defaultMessage='Edited {date}' values={{ date: intl.formatDate(timestamp, { hour12: false, month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' }) }} /> <Icon id='caret-down' icon={ArrowDropDownIcon} />
</button>
</DropdownMenu>
);
Expand Down
52 changes: 41 additions & 11 deletions app/javascript/mastodon/components/icon.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,50 @@
import classNames from 'classnames';

interface Props extends React.HTMLAttributes<HTMLImageElement> {
id: string;
className?: string;
fixedWidth?: boolean;
import { ReactComponent as CheckBoxOutlineBlankIcon } from '@material-symbols/svg-600/outlined/check_box_outline_blank.svg';

interface SVGPropsWithTitle extends React.SVGProps<SVGSVGElement> {
title?: string;
}

export type IconProp = React.FC<SVGPropsWithTitle>;

interface Props extends React.SVGProps<SVGSVGElement> {
children?: never;
id: string;
icon: IconProp;
title?: string;
}

export const Icon: React.FC<Props> = ({
id,
icon: IconComponent,
className,
fixedWidth,
title: titleProp,
...other
}) => (
<i
className={classNames('fa', `fa-${id}`, className, { 'fa-fw': fixedWidth })}
{...other}
/>
);
}) => {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (!IconComponent) {
if (process.env.NODE_ENV !== 'production') {
throw new Error(`<Icon id="${id}"> is missing an "icon" prop.`);
}

IconComponent = CheckBoxOutlineBlankIcon;
}

const ariaHidden = titleProp ? undefined : true;
const role = !ariaHidden ? 'img' : undefined;

// Set the title to an empty string to remove the built-in SVG one if any
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
const title = titleProp || '';

return (
<IconComponent
className={classNames('icon', `icon-${id}`, className)}
title={title}
aria-hidden={ariaHidden}
role={role}
{...other}
/>
);
};
Loading

0 comments on commit 134de73

Please sign in to comment.