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:
parent
cfe4d7530c
commit
60c77a6ca3
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user