Updated list components

Updated list edit/add modal to be more user friendly. Added titles where necessary. Spaced out components/divs. Added text buttons instead of checkmark and plus icons for list titles. Updated title where list is empty of accounts/posts where user can click to add people to list. Added add/remove to list from account action dropdown
This commit is contained in:
mgabdev 2019-07-19 15:31:55 -04:00
parent cfe4d7530c
commit 60c77a6ca3
8 changed files with 140 additions and 60 deletions

@ -43,6 +43,7 @@ const messages = defineMessages({
endorse: { id: 'account.endorse', defaultMessage: 'Feature on profile' },
unendorse: { id: 'account.unendorse', defaultMessage: 'Don\'t feature on profile' },
admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' },
add_or_remove_from_list: { id: 'account.add_or_remove_from_list', defaultMessage: 'Add or Remove from lists' },
});
const dateFormatOptions = {
@ -128,6 +129,7 @@ class Header extends ImmutablePureComponent {
menu.push({ text: intl.formatMessage(messages.showReblogs, { name: account.get('username') }), action: this.props.onReblogToggle });
}
menu.push({ text: intl.formatMessage(messages.add_or_remove_from_list), action: this.props.onAddToList });
menu.push({ text: intl.formatMessage(account.getIn(['relationship', 'endorsed']) ? messages.unendorse : messages.endorse), action: this.props.onEndorseToggle });
menu.push(null);
}

@ -3,12 +3,14 @@ import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { injectIntl } from 'react-intl';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { setupListAdder, resetListAdder } from '../../actions/lists';
import { createSelector } from 'reselect';
import List from './components/list';
import Account from './components/account';
import IconButton from 'gabsocial/components/icon_button';
import NewListForm from '../lists/components/new_list_form';
import ColumnSubheading from '../ui/components/column_subheading';
// hack
const getOrderedLists = createSelector([state => state.get('lists')], lists => {
@ -19,8 +21,9 @@ const getOrderedLists = createSelector([state => state.get('lists')], lists => {
return lists.toList().filter(item => !!item).sort((a, b) => a.get('title').localeCompare(b.get('title')));
});
const mapStateToProps = state => ({
const mapStateToProps = (state, {accountId}) => ({
listIds: getOrderedLists(state).map(list=>list.get('id')),
account: state.getIn(['accounts', accountId]),
});
const mapDispatchToProps = dispatch => ({
@ -28,6 +31,12 @@ const mapDispatchToProps = dispatch => ({
onReset: () => dispatch(resetListAdder()),
});
const messages = defineMessages({
close: { id: 'lightbox.close', defaultMessage: 'Close' },
subheading: { id: 'lists.subheading', defaultMessage: 'Your lists' },
add: { id: 'lists.new.create', defaultMessage: 'Add List' },
});
export default @connect(mapStateToProps, mapDispatchToProps)
@injectIntl
class ListAdder extends ImmutablePureComponent {
@ -39,6 +48,7 @@ class ListAdder extends ImmutablePureComponent {
onInitialize: PropTypes.func.isRequired,
onReset: PropTypes.func.isRequired,
listIds: ImmutablePropTypes.list.isRequired,
account: ImmutablePropTypes.map,
};
componentDidMount () {
@ -51,20 +61,41 @@ class ListAdder extends ImmutablePureComponent {
onReset();
}
onClickClose = () => {
this.props.onClose('LIST_ADDER');
};
render () {
const { accountId, listIds } = this.props;
const { accountId, listIds, intl, onClose, account } = this.props;
const displayNameHtml = account ? { __html: account.get('display_name_html') } : '';
return (
<div className='modal-root__modal list-adder'>
<div className='list-adder__account'>
<Account accountId={accountId} />
<div className='modal-root__modal compose-modal'>
<div className='compose-modal__header'>
<h3 className='compose-modal__header__title'>
<FormattedMessage id='list_adder.header_title' defaultMessage='Add or Remove from Lists' />
</h3>
<IconButton className='compose-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={this.onClickClose} size={20} />
</div>
<div className='compose-modal__content'>
<div className='list-adder'>
<div className='list-adder__account'>
<Account accountId={accountId} />
</div>
<NewListForm />
<br/>
<ColumnSubheading text={intl.formatMessage(messages.add)} />
<NewListForm />
<div className='list-adder__lists'>
{listIds.map(ListId => <List key={ListId} listId={ListId} />)}
<br/>
<ColumnSubheading text={intl.formatMessage(messages.subheading)} />
<div className='list-adder__lists'>
{listIds.map(ListId => <List key={ListId} listId={ListId} />)}
</div>
</div>
</div>
</div>
);

@ -2,11 +2,12 @@ import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { changeListEditorTitle, submitListEditor } from '../../../actions/lists';
import IconButton from '../../../components/icon_button';
import Button from '../../../components/button';
import { defineMessages, injectIntl } from 'react-intl';
const messages = defineMessages({
title: { id: 'lists.edit.submit', defaultMessage: 'Change title' },
save: { id: 'lists.new.save_title', defaultMessage: 'Save' },
});
const mapStateToProps = state => ({
@ -48,21 +49,23 @@ class ListForm extends React.PureComponent {
const { value, disabled, intl } = this.props;
const title = intl.formatMessage(messages.title);
const save = intl.formatMessage(messages.save);
return (
<form className='column-inline-form' onSubmit={this.handleSubmit}>
<input
className='setting-text'
className='setting-text new-list-form__input'
value={value}
onChange={this.handleChange}
/>
<IconButton
<Button
className='new-list-form__btn'
disabled={disabled}
icon='check'
title={title}
onClick={this.handleClick}
/>
>
{save}
</Button>
</form>
);
}

@ -3,13 +3,13 @@ import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { injectIntl } from 'react-intl';
import { injectIntl, defineMessages } from 'react-intl';
import { setupListEditor, clearListSuggestions, resetListEditor } from '../../actions/lists';
import Account from './components/account';
import Search from './components/search';
import EditListForm from './components/edit_list_form';
import Motion from '../ui/util/optional_motion';
import spring from 'react-motion/lib/spring';
import ColumnSubheading from '../ui/components/column_subheading';
import IconButton from 'gabsocial/components/icon_button';
const mapStateToProps = state => ({
accountIds: state.getIn(['listEditor', 'accounts', 'items']),
@ -22,6 +22,14 @@ const mapDispatchToProps = dispatch => ({
onReset: () => dispatch(resetListEditor()),
});
const messages = defineMessages({
close: { id: 'lightbox.close', defaultMessage: 'Close' },
changeTitle: { id: 'lists.edit.submit', defaultMessage: 'Change title' },
addToList: { id: 'lists.account.add', defaultMessage: 'Add to list' },
removeFromList: { id: 'lists.account.remove', defaultMessage: 'Remove from list' },
editList: { id: 'lists.edit', defaultMessage: 'Edit list' },
});
export default @connect(mapStateToProps, mapDispatchToProps)
@injectIntl
class ListEditor extends ImmutablePureComponent {
@ -48,29 +56,40 @@ class ListEditor extends ImmutablePureComponent {
}
render () {
const { accountIds, searchAccountIds, onClear } = this.props;
const { accountIds, searchAccountIds, onClear, intl } = this.props;
const showSearch = searchAccountIds.size > 0;
return (
<div className='modal-root__modal list-editor'>
<EditListForm />
<div className='modal-root__modal compose-modal'>
<div className='compose-modal__header'>
<h3 className='compose-modal__header__title'>
{intl.formatMessage(messages.editList)}
</h3>
<IconButton className='compose-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={this.onClickClose} size={20} />
</div>
<div className='compose-modal__content'>
<div className='list-editor'>
<ColumnSubheading text={intl.formatMessage(messages.changeTitle)} />
<EditListForm />
<br/>
<Search />
<div className='drawer__pager'>
<div className='drawer__inner list-editor__accounts'>
{accountIds.map(accountId => <Account key={accountId} accountId={accountId} added />)}
</div>
{showSearch && <div role='button' tabIndex='-1' className='drawer__backdrop' onClick={onClear} />}
<Motion defaultStyle={{ x: -100 }} style={{ x: spring(showSearch ? 0 : -100, { stiffness: 210, damping: 20 }) }}>
{({ x }) => (
<div className='drawer__inner backdrop' style={{ transform: x === 0 ? null : `translateX(${x}%)`, visibility: x === -100 ? 'hidden' : 'visible' }}>
{searchAccountIds.map(accountId => <Account key={accountId} accountId={accountId} />)}
{
accountIds.size > 0 &&
<div>
<ColumnSubheading text={intl.formatMessage(messages.removeFromList)} />
<div className='list-editor__accounts'>
{accountIds.map(accountId => <Account key={accountId} accountId={accountId} added />)}
</div>
</div>
)}
</Motion>
}
<br/>
<ColumnSubheading text={intl.formatMessage(messages.addToList)} />
<Search />
<div className='list-editor__accounts'>
{searchAccountIds.map(accountId => <Account key={accountId} accountId={accountId} />)}
</div>
</div>
</div>
</div>
);

@ -14,6 +14,7 @@ import LoadingIndicator from '../../components/loading_indicator';
import Icon from 'gabsocial/components/icon';
import HomeColumnHeader from '../../components/home_column_header';
import { Link } from 'react-router-dom';
import Button from 'gabsocial/components/button';
const messages = defineMessages({
deleteMessage: { id: 'confirmations.delete_list.message', defaultMessage: 'Are you sure you want to permanently delete this list?' },
@ -102,6 +103,14 @@ class ListTimeline extends React.PureComponent {
);
}
const emptyMessage = (
<div>
<FormattedMessage id='empty_column.list' defaultMessage='There is nothing in this list yet. When members of this list post new statuses, they will appear here.' />
<br/><br/>
<Button onClick={this.handleEditClick}><FormattedMessage id='list.click_to_add' defaultMessage='Click here to add people'/></Button>
</div>
);
return (
<Column label={title}>
<HomeColumnHeader activeItem='lists' activeSubItem={id} active={hasUnread}>
@ -127,7 +136,7 @@ class ListTimeline extends React.PureComponent {
scrollKey='list_timeline'
timelineId={`list:${id}`}
onLoadMore={this.handleLoadMore}
emptyMessage={<FormattedMessage id='empty_column.list' defaultMessage='There is nothing in this list yet. When members of this list post new statuses, they will appear here.' />}
emptyMessage={emptyMessage}
/>
</Column>
);

@ -2,12 +2,13 @@ import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { changeListEditorTitle, submitListEditor } from '../../../actions/lists';
import IconButton from '../../../components/icon_button';
import Button from '../../../components/button';
import { defineMessages, injectIntl } from 'react-intl';
const messages = defineMessages({
label: { id: 'lists.new.title_placeholder', defaultMessage: 'New list title' },
title: { id: 'lists.new.create', defaultMessage: 'Add list' },
create: { id: 'lists.new.create_title', defaultMessage: 'Create' },
});
const mapStateToProps = state => ({
@ -50,6 +51,7 @@ class NewListForm extends React.PureComponent {
const label = intl.formatMessage(messages.label);
const title = intl.formatMessage(messages.title);
const create = intl.formatMessage(messages.create);
return (
<form className='column-inline-form' onSubmit={this.handleSubmit}>
@ -57,7 +59,7 @@ class NewListForm extends React.PureComponent {
<span style={{ display: 'none' }}>{label}</span>
<input
className='setting-text'
className='setting-text new-list-form__input'
value={value}
disabled={disabled}
onChange={this.handleChange}
@ -65,12 +67,13 @@ class NewListForm extends React.PureComponent {
/>
</label>
<IconButton
<Button
className='new-list-form__btn'
disabled={disabled}
icon='plus'
title={title}
onClick={this.handleClick}
/>
>
{create}
</Button>
</form>
);
}

@ -16,6 +16,7 @@ import ScrollableList from '../../components/scrollable_list';
const messages = defineMessages({
heading: { id: 'column.lists', defaultMessage: 'Lists' },
subheading: { id: 'lists.subheading', defaultMessage: 'Your lists' },
add: { id: 'lists.new.create', defaultMessage: 'Add List' },
});
const getOrderedLists = createSelector([state => state.get('lists')], lists => {
@ -60,8 +61,10 @@ class Lists extends ImmutablePureComponent {
return (
<Column icon='list-ul' heading={intl.formatMessage(messages.heading)} backBtnSlim>
<br/>
<ColumnSubheading text={intl.formatMessage(messages.add)} />
<NewListForm />
<br/>
<ColumnSubheading text={intl.formatMessage(messages.subheading)} />
<ScrollableList
scrollKey='lists'

@ -4380,12 +4380,11 @@ noscript {
}
.list-editor {
background: $ui-base-color;
flex-direction: column;
border-radius: 8px;
box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4);
width: 380px;
width: 100%;
overflow: hidden;
height: 100%;
overflow-y: scroll;
@media screen and (max-width: 420px) {
width: 90%;
@ -4400,10 +4399,6 @@ noscript {
border-radius: 8px 8px 0 0;
}
.drawer__pager {
height: 50vh;
}
.drawer__inner {
border-radius: 0 0 8px 8px;
@ -4415,7 +4410,9 @@ noscript {
}
&__accounts {
background: lighten($ui-base-color, 13%);
overflow-y: auto;
max-height: 200px;
}
.account__display-name {
@ -4434,12 +4431,11 @@ noscript {
}
.list-adder {
background: $ui-base-color;
flex-direction: column;
border-radius: 8px;
box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4);
width: 380px;
width: 100%;
overflow: hidden;
height: 100%;
overflow-y: scroll;
@media screen and (max-width: 420px) {
width: 90%;
@ -4447,22 +4443,24 @@ noscript {
&__account {
background: lighten($ui-base-color, 13%);
border-radius: 4px;
}
&__lists {
background: lighten($ui-base-color, 13%);
height: 50vh;
border-radius: 0 0 8px 8px;
overflow-y: auto;
}
.list {
padding: 10px;
padding: 4px;
border-bottom: 1px solid lighten($ui-base-color, 8%);
}
.list__wrapper {
display: flex;
.account__relationship {
padding: 8px 5px 0 5px;
}
}
.list__display-name {
@ -4474,6 +4472,18 @@ noscript {
}
}
.new-list-form,
.edit-list-form {
&__btn {
margin-left: 6px;
width: 80px;
}
&__input {
height: 36px;
}
}
.focal-point-modal {
max-width: 80vw;
max-height: 80vh;