From c3b4fd9013bc8f801b76f66edc8ae6f0a96580df Mon Sep 17 00:00:00 2001 From: Andrey Okonetchnikov Date: Wed, 26 Oct 2016 19:51:50 +0200 Subject: [PATCH] Cards typography (#139) * Fixed some ESLint errors * Better card's design for the editorial process. - Use Card component from react-toolbox - Added "Edit" buttons for cards - Cleaned up CSS and JS Fixes #125 * Better ImageCard and card list view. Fixes #125 * Use collection label instead of name on the CollectionPage --- src/components/Cards.js | 4 +- src/components/Cards/ImageCard.css | 16 +--- src/components/Cards/ImageCard.js | 56 ++++++++---- src/components/EntryListing.js | 78 +++++++++-------- src/components/UnpublishedListing.css | 62 +++++--------- src/components/UnpublishedListing.js | 117 ++++++++++++++++---------- src/components/Widgets.js | 4 +- src/containers/CollectionPage.js | 2 +- 8 files changed, 181 insertions(+), 158 deletions(-) diff --git a/src/components/Cards.js b/src/components/Cards.js index ab903e2b..ef5c4316 100644 --- a/src/components/Cards.js +++ b/src/components/Cards.js @@ -3,9 +3,9 @@ import ImageCard from './Cards/ImageCard'; import AlltypeCard from './Cards/AlltypeCard'; const Cards = { - _unknown: UnknownCard, + unknown: UnknownCard, image: ImageCard, - alltype: AlltypeCard + alltype: AlltypeCard, }; export default Cards; diff --git a/src/components/Cards/ImageCard.css b/src/components/Cards/ImageCard.css index b2020b48..72b8c3cf 100644 --- a/src/components/Cards/ImageCard.css +++ b/src/components/Cards/ImageCard.css @@ -1,18 +1,4 @@ .root { + width: 240px; cursor: pointer; } - -.root h1 { - font-size: 17px; -} - -.root h2 { - font-weight: 400; - font-size: 15px; -} - -.root p { - color: #555; - font-size: 14px; - margin-top: 5px; -} diff --git a/src/components/Cards/ImageCard.js b/src/components/Cards/ImageCard.js index 9473ca61..867b4257 100644 --- a/src/components/Cards/ImageCard.js +++ b/src/components/Cards/ImageCard.js @@ -1,31 +1,51 @@ import React, { PropTypes } from 'react'; -import { Card } from '../UI'; +import { Card, CardMedia, CardTitle, CardText } from 'react-toolbox/lib/card'; import styles from './ImageCard.css'; -export default class ImageCard extends React.Component { - - render() { - const { onClick, onImageLoaded, image, text, description } = this.props; - return ( - - -

{text}

- - {description ?

{description}

: null} -
- ); - } -} +const ImageCard = ( + { + author, + description, + image, + text, + onClick, + onImageLoaded, + }) => ( + + + { + image && + {text} + + } + { description && { description } } + +); ImageCard.propTypes = { + author: PropTypes.string, image: PropTypes.string, onClick: PropTypes.func, onImageLoaded: PropTypes.func, text: PropTypes.string.isRequired, - description: PropTypes.string + description: PropTypes.string, }; ImageCard.defaultProps = { - onClick: function() {}, - onImageLoaded: function() {} + onClick: () => { + }, + onImageLoaded: () => { + }, }; + +export default ImageCard; diff --git a/src/components/EntryListing.js b/src/components/EntryListing.js index fd9a7375..4464ded1 100644 --- a/src/components/EntryListing.js +++ b/src/components/EntryListing.js @@ -1,13 +1,24 @@ import React, { PropTypes } from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { Map } from 'immutable'; -import Bricks from 'bricks.js'; +import { throttle } from 'lodash'; +import bricks from 'bricks.js'; import Waypoint from 'react-waypoint'; import history from '../routing/history'; import Cards from './Cards'; -import _ from 'lodash'; export default class EntryListing extends React.Component { + static propTypes = { + children: PropTypes.node.isRequired, + collections: PropTypes.oneOfType([ + ImmutablePropTypes.map, + ImmutablePropTypes.iterable, + ]).isRequired, + entries: ImmutablePropTypes.list, + onPaginate: PropTypes.func.isRequired, + page: PropTypes.number, + }; + constructor(props) { super(props); this.bricksInstance = null; @@ -24,13 +35,13 @@ export default class EntryListing extends React.Component { ], }; - this.updateBricks = _.throttle(this.updateBricks.bind(this), 30); + this.updateBricks = throttle(this.updateBricks.bind(this), 30); this.handleLoadMore = this.handleLoadMore.bind(this); } componentDidMount() { - this.bricksInstance = Bricks({ - container: this._entries, + this.bricksInstance = bricks({ + container: this.containerNode, packed: this.bricksConfig.packed, sizes: this.bricksConfig.sizes, }); @@ -45,7 +56,8 @@ export default class EntryListing extends React.Component { } componentDidUpdate(prevProps) { - if ((prevProps.entries === undefined || prevProps.entries.size === 0) && this.props.entries.size === 0) { + if ((prevProps.entries === undefined || prevProps.entries.size === 0) + && this.props.entries.size === 0) { return; } @@ -61,16 +73,18 @@ export default class EntryListing extends React.Component { } cardFor(collection, entry, link) { - const cartType = collection.getIn(['card', 'type']) || 'alltype'; - const card = Cards[cartType] || Cards._unknown; + const cardType = collection.getIn(['card', 'type']) || 'alltype'; + const card = Cards[cardType] || Cards.unknown; return React.createElement(card, { key: entry.get('slug'), + author: entry.getIn(['data', 'author']), collection, - onClick: history.push.bind(this, link), - onImageLoaded: this.updateBricks, - text: entry.get('label') ? entry.get('label') : entry.getIn(['data', collection.getIn(['card', 'text'])]), description: entry.getIn(['data', collection.getIn(['card', 'description'])]), image: entry.getIn(['data', collection.getIn(['card', 'image'])]), + link, + text: entry.get('label') ? entry.get('label') : entry.getIn(['data', collection.getIn(['card', 'text'])]), + onClick: history.push.bind(this, link), + onImageLoaded: this.updateBricks, }); } @@ -78,6 +92,10 @@ export default class EntryListing extends React.Component { this.props.onPaginate(this.props.page + 1); } + handleRef = (node) => { + this.containerNode = node; + }; + renderCards = () => { const { collections, entries } = this.props; if (Map.isMap(collections)) { @@ -86,35 +104,25 @@ export default class EntryListing extends React.Component { const path = `/collections/${ collectionName }/entries/${ entry.get('slug') }`; return this.cardFor(collections, entry, path); }); - } else { - return entries.map((entry) => { - const collection = collections.filter(collection => collection.get('name') === entry.get('collection')).first(); - const path = `/collections/${ collection.get('name') }/entries/${ entry.get('slug') }`; - return this.cardFor(collection, entry, path); - }); } + return entries.map((entry) => { + const collection = collections + .filter(collection => collection.get('name') === entry.get('collection')).first(); + const path = `/collections/${ collection.get('name') }/entries/${ entry.get('slug') }`; + return this.cardFor(collection, entry, path); + }); }; render() { const { children } = this.props; - const cards = this.renderCards(); - return (
-

{children}

-
this._entries = c}> - {cards} - + return ( +
+

{children}

+
+ { this.renderCards() } + +
-
); + ); } } - -EntryListing.propTypes = { - children: PropTypes.node.isRequired, - collections: PropTypes.oneOfType([ - ImmutablePropTypes.map, - ImmutablePropTypes.iterable, - ]).isRequired, - entries: ImmutablePropTypes.list, - onPaginate: PropTypes.func.isRequired, - page: PropTypes.number, -}; diff --git a/src/components/UnpublishedListing.css b/src/components/UnpublishedListing.css index 8f70b85f..bbdbc05d 100644 --- a/src/components/UnpublishedListing.css +++ b/src/components/UnpublishedListing.css @@ -1,54 +1,34 @@ +:root { + --highlightColor: #e1eeea; + --defaultFontSize: 1em; +} + .container { - display: table; - width: 100%; + display: flex; + justify-content: space-between; } .column { - display: table-cell; - text-align: center; - width: 33%; - height: 100%; + flex: 1 33%; + margin: -10px; + padding: 10px; + max-width: 33%; transition: background-color .5s ease; - & h2 { - font-size: 16px; - } } -.highlighted { - background-color: #e1eeea; +.columnHovered { + composes: column; + background-color: var(--highlightColor); } -.column:not(:last-child) { - padding-right: 20px; +.columnHeading { + font-size: var(--defaultFontSize); +} + +.draggable { + cursor: move; } .card { - width: 100% !important; - margin: 7px 0 0 10px; - padding: 7px 0; -} - -.cardHeading { - font-size: 17px; - & small { - font-weight: normal; - } -} - -.cardText { - color: #555; - font-size: 12px; - margin-top: 5px; -} - -.button { - margin: 10px 10px 0 0; - float: right; -} - - -.clear::after { - content:""; - display:block; - clear:both; + margin-bottom: 10px; } diff --git a/src/components/UnpublishedListing.js b/src/components/UnpublishedListing.js index 7717ea71..acee5b3d 100644 --- a/src/components/UnpublishedListing.js +++ b/src/components/UnpublishedListing.js @@ -1,13 +1,20 @@ import React, { PropTypes } from 'react'; import { DragSource, DropTarget, HTML5DragDrop } from 'react-simple-dnd'; import ImmutablePropTypes from 'react-immutable-proptypes'; -import moment from 'moment'; -import { Card } from './UI'; import { Link } from 'react-router'; +import moment from 'moment'; +import { Card, CardTitle, CardText, CardActions } from 'react-toolbox/lib/card'; +import Button from 'react-toolbox/lib/button'; import { status, statusDescriptions } from '../constants/publishModes'; import styles from './UnpublishedListing.css'; class UnpublishedListing extends React.Component { + static propTypes = { + entries: ImmutablePropTypes.orderedMap, + handleChangeStatus: PropTypes.func.isRequired, + handlePublish: PropTypes.func.isRequired, + }; + handleChangeStatus = (newStatus, dragProps) => { const slug = dragProps.slug; const collection = dragProps.collection; @@ -23,56 +30,78 @@ class UnpublishedListing extends React.Component { }; renderColumns = (entries, column) => { - if (!entries) return; + if (!entries) return null; if (!column) { - /* eslint-disable */ return entries.entrySeq().map(([currColumn, currEntries]) => ( - - {(isOver) => ( -
-

{statusDescriptions.get(currColumn)}

+ + {isHovered => ( +
+

+ {statusDescriptions.get(currColumn)} +

{this.renderColumns(currEntries, currColumn)}
)}
- /* eslint-enable */ )); - } else { - return (
- {entries.map((entry) => { - // Look for an "author" field. Fallback to username on backend implementation; - const author = entry.getIn(['data', 'author'], entry.getIn(['metaData', 'user'])); - const timeStamp = moment(entry.getIn(['metaData', 'timeStamp'])).format('llll'); - const link = `/editorialworkflow/${ entry.getIn(['metaData', 'collection']) }/${ entry.getIn(['metaData', 'status']) }/${ entry.get('slug') }`; - const slug = entry.get('slug'); - const ownStatus = entry.getIn(['metaData', 'status']); - const collection = entry.getIn(['metaData', 'collection']); - return ( - /* eslint-disable */ - -
- - {entry.getIn(['data', 'title'])} by {author} -

Last updated: {timeStamp} by {entry.getIn(['metaData', 'user'])}

- {(ownStatus === status.last()) && - - } -
-
-
- /* eslint-enable */ - ); - } - )} -
); } - }; - - static propTypes = { - entries: ImmutablePropTypes.orderedMap, - handleChangeStatus: PropTypes.func.isRequired, - handlePublish: PropTypes.func.isRequired, + return ( +
+ { + entries.map((entry) => { + // Look for an "author" field. Fallback to username on backend implementation; + const author = entry.getIn(['data', 'author'], entry.getIn(['metaData', 'user'])); + const timeStamp = moment(entry.getIn(['metaData', 'timeStamp'])).format('llll'); + const link = `editorialworkflow/${ entry.getIn(['metaData', 'collection']) }/${ entry.getIn(['metaData', 'status']) }/${ entry.get('slug') }`; + const slug = entry.get('slug'); + const ownStatus = entry.getIn(['metaData', 'status']); + const collection = entry.getIn(['metaData', 'collection']); + return ( + +
+ + + + Last updated: {timeStamp} by {entry.getIn(['metaData', 'user'])} + + + + + + { + ownStatus === status.last() && + + } + + +
+
+ ); + }) + } +
+ ); }; render() { @@ -88,4 +117,4 @@ class UnpublishedListing extends React.Component { } } -export default HTML5DragDrop(UnpublishedListing); +export default HTML5DragDrop(UnpublishedListing); // eslint-disable-line diff --git a/src/components/Widgets.js b/src/components/Widgets.js index abb96b10..74bd97ec 100644 --- a/src/components/Widgets.js +++ b/src/components/Widgets.js @@ -20,8 +20,8 @@ registry.registerWidget('list', ListControl, ListPreview); registry.registerWidget('markdown', MarkdownControl, MarkdownPreview); registry.registerWidget('image', ImageControl, ImagePreview); registry.registerWidget('datetime', DateTimeControl, DateTimePreview); -registry.registerWidget('_unknown', UnknownControl, UnknownPreview); +registry.registerWidget('unknown', UnknownControl, UnknownPreview); export function resolveWidget(name) { - return registry.getWidget(name) || registry.getWidget('_unknown'); + return registry.getWidget(name) || registry.getWidget('unknown'); } diff --git a/src/containers/CollectionPage.js b/src/containers/CollectionPage.js index 07ab62e8..7f5d196e 100644 --- a/src/containers/CollectionPage.js +++ b/src/containers/CollectionPage.js @@ -50,7 +50,7 @@ class DashboardPage extends React.Component { page={page} onPaginate={this.handleLoadMore} > - {collection.get('name')} + {collection.get('label')} : {['Loading Entries', 'Caching Entries', 'This might take several minutes']}