Popover component to add sketches to collection from SketchList

This commit is contained in:
Andrew Nicolaou 2019-10-20 21:59:16 +02:00
parent edfddcc75f
commit af8d25f142
6 changed files with 285 additions and 19 deletions

View file

@ -0,0 +1,105 @@
import PropTypes from 'prop-types';
import React from 'react';
import InlineSVG from 'react-inlinesvg';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as CollectionsActions from '../../actions/collections';
import getSortedCollections from '../../selectors/collections';
// import { Link } from 'react-router';
import exitUrl from '../../../../images/exit.svg';
import { Searchbar } from '../Searchbar';
import Item from './Item';
// const reducer = () => {
// switch ()
// case 'noItems':
// return 'NoCollections';
// case
// }
const NoCollections = () => (
<div>
<p>No collections</p>
{/* <p>
<Link
to="/andrewn/collections/create"
className="searchbar__clear-button"
onClick={() => {}}
>Create
</Link>
</p> */}
</div>);
const CollectionPopover = ({
onClose, project, collections, addToCollection, getCollections, user
}) => {
const [searchTerm, setSearchTerm] = React.useState('');
React.useEffect(() => {
getCollections(user.username);
}, [user]);
const handleAddToCollection = (collectionId) => {
addToCollection(collectionId, project.id);
};
return (
<div className="collection-popover">
<div className="collection-popover__header">
<h4>Add to collection</h4>
<button className="collection-popover__exit-button" onClick={onClose}>
<InlineSVG src={exitUrl} alt="Close Add to Collection" />
</button>
</div>
<div className="collection-popover__filter">
<Searchbar searchLabel="Search collections..." searchTerm={searchTerm} setSearchTerm={setSearchTerm} resetSearchTerm={setSearchTerm} />
</div>
<div className="collection-popover__items">
<ul>
{
collections.map(collection => <Item key={collection.id} collection={collection} onSelect={() => handleAddToCollection(collection.id)} />)
}
</ul>
</div>
</div>
);
};
CollectionPopover.propTypes = {
onClose: PropTypes.func.isRequired,
getCollections: PropTypes.func.isRequired,
addToCollection: PropTypes.func.isRequired,
user: PropTypes.shape({
username: PropTypes.string.isRequired,
}).isRequired,
collections: PropTypes.arrayOf(PropTypes.shape({
name: PropTypes.string,
})).isRequired,
project: PropTypes.shape({
id: PropTypes.string,
}).isRequired,
};
function mapStateToProps(state, ownProps) {
return {
user: state.user,
collections: getSortedCollections(state),
loading: state.loading,
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(
Object.assign({}, CollectionsActions),
dispatch
);
}
export default connect(mapStateToProps, mapDispatchToProps)(CollectionPopover);

View file

@ -0,0 +1,26 @@
import PropTypes from 'prop-types';
import React from 'react';
import { Link } from 'react-router';
const CollectionItem = ({ collection, onSelect }) => (
<li className="collection-popover__item">
<div className="collection-popover__item__info">
<button onClick={onSelect}>
{collection.name}
</button>
</div>
<div className="collection-popover__item__view">
<Link className="collection-popover__item__view-button" to={`/${collection.owner.username}/collections/${collection.id}`}>View</Link>
</div>
</li>
);
CollectionItem.propTypes = {
onSelect: PropTypes.func.isRequired,
collection: PropTypes.shape({
name: PropTypes.string,
}).isRequired,
};
export default CollectionItem;

View file

@ -0,0 +1 @@
export { default } from './CollectionPopover.jsx';

View file

@ -17,6 +17,7 @@ import * as IdeActions from '../actions/ide';
import getSortedSketches from '../selectors/projects'; import getSortedSketches from '../selectors/projects';
import Loader from '../../App/components/loader'; import Loader from '../../App/components/loader';
import AddRemoveButton from '../../../components/AddRemoveButton'; import AddRemoveButton from '../../../components/AddRemoveButton';
import CollectionPopover from './CollectionPopover';
const arrowUp = require('../../../images/sort-arrow-up.svg'); const arrowUp = require('../../../images/sort-arrow-up.svg');
const arrowDown = require('../../../images/sort-arrow-down.svg'); const arrowDown = require('../../../images/sort-arrow-down.svg');
@ -29,7 +30,8 @@ class SketchListRowBase extends React.Component {
optionsOpen: false, optionsOpen: false,
renameOpen: false, renameOpen: false,
renameValue: props.sketch.name, renameValue: props.sketch.name,
isFocused: false isFocused: false,
showPopover: false,
}; };
} }
@ -119,6 +121,18 @@ class SketchListRowBase extends React.Component {
this.props.exportProjectAsZip(this.props.sketch.id); this.props.exportProjectAsZip(this.props.sketch.id);
} }
handleShowCollectionPopover = () => {
this.setState({
showPopover: true
});
}
handleCloseCollectionPopover = () => {
this.setState({
showPopover: false
});
}
handleSketchDuplicate = () => { handleSketchDuplicate = () => {
this.closeAll(); this.closeAll();
this.props.cloneProject(this.props.sketch.id); this.props.cloneProject(this.props.sketch.id);
@ -186,6 +200,17 @@ class SketchListRowBase extends React.Component {
Duplicate Duplicate
</button> </button>
</li>} </li>}
{this.props.user.authenticated &&
<li>
<button
className="sketch-list__action-option"
onClick={this.handleShowCollectionPopover}
onBlur={this.onBlurComponent}
onFocus={this.onFocusComponent}
>
Add to collection
</button>
</li>}
{ /* <li> { /* <li>
<button <button
className="sketch-list__action-option" className="sketch-list__action-option"
@ -208,6 +233,7 @@ class SketchListRowBase extends React.Component {
</button> </button>
</li>} </li>}
</ul>} </ul>}
{this.state.showPopover && <CollectionPopover onClose={this.handleCloseCollectionPopover} project={this.props.sketch} />}
</td> </td>
); );
} }
@ -231,15 +257,16 @@ class SketchListRowBase extends React.Component {
url = `/${username}/sketches/${slugify(sketch.name, '_')}`; url = `/${username}/sketches/${slugify(sketch.name, '_')}`;
} }
return ( return (
<tr <React.Fragment>
className="sketches-table__row" <tr
key={sketch.id} className="sketches-table__row"
> key={sketch.id}
<th scope="row"> >
<Link to={url}> <th scope="row">
{renameOpen ? '' : sketch.name} <Link to={url}>
</Link> {renameOpen ? '' : sketch.name}
{renameOpen </Link>
{renameOpen
&& &&
<input <input
value={renameValue} value={renameValue}
@ -248,12 +275,13 @@ class SketchListRowBase extends React.Component {
onBlur={this.resetSketchName} onBlur={this.resetSketchName}
onClick={e => e.stopPropagation()} onClick={e => e.stopPropagation()}
/> />
} }
</th> </th>
<td>{format(new Date(sketch.createdAt), 'MMM D, YYYY h:mm A')}</td> <td>{format(new Date(sketch.createdAt), 'MMM D, YYYY h:mm A')}</td>
<td>{format(new Date(sketch.updatedAt), 'MMM D, YYYY h:mm A')}</td> <td>{format(new Date(sketch.updatedAt), 'MMM D, YYYY h:mm A')}</td>
{this.renderActions()} {this.renderActions()}
</tr>); </tr>
</React.Fragment>);
} }
} }
@ -290,6 +318,18 @@ class SketchList extends React.Component {
this.props.getProjects(this.props.username); this.props.getProjects(this.props.username);
this.props.resetSorting(); this.props.resetSorting();
this._renderFieldHeader = this._renderFieldHeader.bind(this); this._renderFieldHeader = this._renderFieldHeader.bind(this);
this.state = {
isInitialDataLoad: true,
};
}
componentWillReceiveProps(nextProps) {
if (this.props.sketches !== nextProps.sketches && Array.isArray(nextProps.sketches)) {
this.setState({
isInitialDataLoad: false,
});
}
} }
getSketchesTitle() { getSketchesTitle() {
@ -300,16 +340,20 @@ class SketchList extends React.Component {
} }
hasSketches() { hasSketches() {
return !this.props.loading && this.props.sketches.length > 0; return !this.isLoading() && this.props.sketches.length > 0;
}
isLoading() {
return this.props.loading && this.state.isInitialDataLoad;
} }
_renderLoader() { _renderLoader() {
if (this.props.loading) return <Loader />; if (this.isLoading()) return <Loader />;
return null; return null;
} }
_renderEmptyTable() { _renderEmptyTable() {
if (!this.props.loading && this.props.sketches.length === 0) { if (!this.isLoading() && this.props.sketches.length === 0) {
return (<p className="sketches-table__empty">No sketches.</p>); return (<p className="sketches-table__empty">No sketches.</p>);
} }
return null; return null;

View file

@ -0,0 +1,89 @@
.collection-popover {
position: absolute;
height: auto;
width: #{400 / $base-font-size}rem;
top: 63%;
right: calc(100% - 26px);
z-index: 9999;
display: flex;
flex-direction: column;
border-radius: #{6 / $base-font-size}rem;
@include themify() {
background-color: map-get($theme-map, 'modal-background-color');
border: 1px solid map-get($theme-map, 'modal-border-color');
box-shadow: 0 0 18px 0 getThemifyVariable('shadow-color');
color: getThemifyVariable('dropdown-color');
}
text-align: left;
}
.collection-popover__header {
display: flex;
margin-left: #{17 / $base-font-size}rem;
margin-right: #{17 / $base-font-size}rem;
}
.collection-popover__filter {
display: flex;
margin-bottom: #{8 / $base-font-size}rem;
}
.collection-popover__exit-button {
@include icon();
margin-left: auto;
}
.collection-popover__items {
height: #{70 * 4 / $base-font-size}rem;
overflow: auto;
}
.collection-popover__item {
display: flex;
align-items: center;
justify-content: space-between;
height: #{60 / $base-font-size}rem;
margin: 5px;
padding: 5px;
}
.collection-popover__item:nth-child(odd) {
@include themify() {
background: getThemifyVariable('table-row-stripe-color');
}
}
.collection-popover__item__info {
flex: 1;
width: 100%;
height: 100%;
display: flex;
justify-content: stretch;
align-items: stretch;
}
.collection-popover__item__info button,
.collection-popover__item__info button:hover {
flex: 1;
width: 100%;
height: 100%;
text-align: left;
color: black;
}
.collection-popover__item__view {
}
.collection-popover__item__view-button {
@extend %button;
}

View file

@ -51,6 +51,7 @@
@import 'components/editable-input'; @import 'components/editable-input';
@import 'components/collection'; @import 'components/collection';
@import 'components/collection-create'; @import 'components/collection-create';
@import 'components/collection-popover';
@import 'layout/dashboard'; @import 'layout/dashboard';
@import 'layout/ide'; @import 'layout/ide';