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
This commit is contained in:
parent
434f45c97c
commit
c3b4fd9013
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 (
|
||||
<Card onClick={onClick} className={styles.root}>
|
||||
<img src={image} onLoad={onImageLoaded} />
|
||||
<h2>{text}</h2>
|
||||
|
||||
{description ? <p>{description}</p> : null}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
const ImageCard = (
|
||||
{
|
||||
author,
|
||||
description,
|
||||
image,
|
||||
text,
|
||||
onClick,
|
||||
onImageLoaded,
|
||||
}) => (
|
||||
<Card
|
||||
onClick={onClick}
|
||||
className={styles.root}
|
||||
>
|
||||
<CardTitle
|
||||
title={text}
|
||||
subtitle={`by ${ author }`}
|
||||
/>
|
||||
{
|
||||
image && <CardMedia aspectRatio="wide">
|
||||
<img
|
||||
src={image}
|
||||
alt={text}
|
||||
onLoad={onImageLoaded}
|
||||
/>
|
||||
</CardMedia>
|
||||
}
|
||||
{ description && <CardText>{ description }</CardText> }
|
||||
</Card>
|
||||
);
|
||||
|
||||
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;
|
||||
|
@ -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 (<div>
|
||||
<h1>{children}</h1>
|
||||
<div ref={c => this._entries = c}>
|
||||
{cards}
|
||||
<Waypoint onEnter={this.handleLoadMore} />
|
||||
return (
|
||||
<div>
|
||||
<h1>{children}</h1>
|
||||
<div ref={this.handleRef}>
|
||||
{ this.renderCards() }
|
||||
<Waypoint onEnter={this.handleLoadMore} />
|
||||
</div>
|
||||
</div>
|
||||
</div>);
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
EntryListing.propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
collections: PropTypes.oneOfType([
|
||||
ImmutablePropTypes.map,
|
||||
ImmutablePropTypes.iterable,
|
||||
]).isRequired,
|
||||
entries: ImmutablePropTypes.list,
|
||||
onPaginate: PropTypes.func.isRequired,
|
||||
page: PropTypes.number,
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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]) => (
|
||||
<DropTarget key={currColumn} onDrop={this.handleChangeStatus.bind(this, currColumn)}>
|
||||
{(isOver) => (
|
||||
<div className={isOver ? `${styles.column} ${styles.highlighted}` : styles.column}>
|
||||
<h2>{statusDescriptions.get(currColumn)}</h2>
|
||||
<DropTarget
|
||||
key={currColumn}
|
||||
/* eslint-disable */
|
||||
onDrop={this.handleChangeStatus.bind(this, currColumn)}
|
||||
/* eslint-enable */
|
||||
>
|
||||
{isHovered => (
|
||||
<div className={isHovered ? styles.columnHovered : styles.column}>
|
||||
<h2 className={styles.columnHeading}>
|
||||
{statusDescriptions.get(currColumn)}
|
||||
</h2>
|
||||
{this.renderColumns(currEntries, currColumn)}
|
||||
</div>
|
||||
)}
|
||||
</DropTarget>
|
||||
/* eslint-enable */
|
||||
));
|
||||
} else {
|
||||
return (<div>
|
||||
{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 */
|
||||
<DragSource key={slug} slug={slug} collection={collection} ownStatus={ownStatus}>
|
||||
<div className={styles.drag}>
|
||||
<Card className={styles.card}>
|
||||
<span className={styles.cardHeading}><Link to={link}>{entry.getIn(['data', 'title'])}</Link> <small>by {author}</small></span>
|
||||
<p className={styles.cardText}>Last updated: {timeStamp} by {entry.getIn(['metaData', 'user'])}</p>
|
||||
{(ownStatus === status.last()) &&
|
||||
<button className={styles.button} onClick={this.requestPublish.bind(this, collection, slug, ownStatus)}>Publish now</button>
|
||||
}
|
||||
</Card>
|
||||
</div>
|
||||
</DragSource>
|
||||
/* eslint-enable */
|
||||
);
|
||||
}
|
||||
)}
|
||||
</div>);
|
||||
}
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
entries: ImmutablePropTypes.orderedMap,
|
||||
handleChangeStatus: PropTypes.func.isRequired,
|
||||
handlePublish: PropTypes.func.isRequired,
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
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 (
|
||||
<DragSource
|
||||
key={slug}
|
||||
slug={slug}
|
||||
collection={collection}
|
||||
ownStatus={ownStatus}
|
||||
>
|
||||
<div className={styles.draggable}>
|
||||
<Card className={styles.card}>
|
||||
<CardTitle
|
||||
title={entry.getIn(['data', 'title'])}
|
||||
subtitle={`by ${ author }`}
|
||||
/>
|
||||
<CardText>
|
||||
Last updated: {timeStamp} by {entry.getIn(['metaData', 'user'])}
|
||||
</CardText>
|
||||
<CardActions>
|
||||
<Link to={link}>
|
||||
<Button>Edit</Button>
|
||||
</Link>
|
||||
{
|
||||
ownStatus === status.last() &&
|
||||
<Button
|
||||
accent
|
||||
/* eslint-disable */
|
||||
onClick={this.requestPublish.bind(this, collection, slug, ownStatus)}
|
||||
/* eslint-enable */
|
||||
>
|
||||
Publish now
|
||||
</Button>
|
||||
}
|
||||
</CardActions>
|
||||
</Card>
|
||||
</div>
|
||||
</DragSource>
|
||||
);
|
||||
})
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
@ -88,4 +117,4 @@ class UnpublishedListing extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default HTML5DragDrop(UnpublishedListing);
|
||||
export default HTML5DragDrop(UnpublishedListing); // eslint-disable-line
|
||||
|
@ -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');
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ class DashboardPage extends React.Component {
|
||||
page={page}
|
||||
onPaginate={this.handleLoadMore}
|
||||
>
|
||||
{collection.get('name')}
|
||||
{collection.get('label')}
|
||||
</EntryListing>
|
||||
:
|
||||
<Loader active>{['Loading Entries', 'Caching Entries', 'This might take several minutes']}</Loader>
|
||||
|
Loading…
x
Reference in New Issue
Block a user