UI updates (#151)
* infer card title * Infer entry body & image * infer image * Better terminology: EntryListing accept a single Collection * remove log * Refactored Collections VO into selectors * use selectors when showning card * fixed size cards * Added 'bio' and 'biography' to collection description inference synonyms * Removed unused card file * throw error instance * bugfix for file based collections * lint * moved components with css to own folder * Search Bugfix: More than one collection might be returned * Changed sidebar implementation. Closes #104 & #152 * Show spinning loading for unpublished entries * Refactored Sidebar into a separate container * Make preview widgets more robust
This commit is contained in:
@ -17,7 +17,7 @@ export default class AppHeader extends React.Component {
|
||||
commands: PropTypes.array.isRequired, // eslint-disable-line
|
||||
defaultCommands: PropTypes.array.isRequired, // eslint-disable-line
|
||||
runCommand: PropTypes.func.isRequired,
|
||||
toggleNavDrawer: PropTypes.func.isRequired,
|
||||
toggleDrawer: PropTypes.func.isRequired,
|
||||
onCreateEntryClick: PropTypes.func.isRequired,
|
||||
onLogoutClick: PropTypes.func.isRequired,
|
||||
};
|
||||
@ -59,7 +59,7 @@ export default class AppHeader extends React.Component {
|
||||
commands,
|
||||
defaultCommands,
|
||||
runCommand,
|
||||
toggleNavDrawer,
|
||||
toggleDrawer,
|
||||
onLogoutClick,
|
||||
} = this.props;
|
||||
|
||||
@ -88,7 +88,7 @@ export default class AppHeader extends React.Component {
|
||||
</Menu>
|
||||
</div>
|
||||
}
|
||||
onLeftIconClick={toggleNavDrawer}
|
||||
onLeftIconClick={toggleDrawer}
|
||||
onRightIconClick={this.handleRightIconClick}
|
||||
>
|
||||
<IndexLink to="/">
|
||||
|
@ -1,11 +0,0 @@
|
||||
import UnknownCard from './Cards/UnknownCard';
|
||||
import ImageCard from './Cards/ImageCard';
|
||||
import AlltypeCard from './Cards/AlltypeCard';
|
||||
|
||||
const Cards = {
|
||||
unknown: UnknownCard,
|
||||
image: ImageCard,
|
||||
alltype: AlltypeCard,
|
||||
};
|
||||
|
||||
export default Cards;
|
@ -1,5 +0,0 @@
|
||||
.cardContent {
|
||||
white-space: nowrap;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
}
|
@ -1,81 +0,0 @@
|
||||
import React, { PropTypes } from 'react';
|
||||
import { Card } from '../UI';
|
||||
import ScaledLine from './ScaledLine';
|
||||
import styles from './AlltypeCard.css';
|
||||
|
||||
export default class AlltypeCard extends React.Component {
|
||||
|
||||
// Based on the Slabtype Algorithm by Erik Loyer
|
||||
// http://erikloyer.com/index.php/blog/the_slabtype_algorithm_part_1_background/
|
||||
renderInscription(inscription) {
|
||||
|
||||
const idealCharPerLine = 22;
|
||||
|
||||
// segment the text into lines
|
||||
const words = inscription.split(' ');
|
||||
let preText, postText, finalText;
|
||||
let preDiff, postDiff;
|
||||
let wordIndex = 0;
|
||||
const lineText = [];
|
||||
|
||||
// while we still have words left, build the next line
|
||||
while (wordIndex < words.length) {
|
||||
postText = '';
|
||||
|
||||
// build two strings (preText and postText) word by word, with one
|
||||
// string always one word behind the other, until
|
||||
// the length of one string is less than the ideal number of characters
|
||||
// per line, while the length of the other is greater than that ideal
|
||||
while (postText.length < idealCharPerLine) {
|
||||
preText = postText;
|
||||
postText += words[wordIndex] + ' ';
|
||||
wordIndex++;
|
||||
if (wordIndex >= words.length) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// calculate the character difference between the two strings and the
|
||||
// ideal number of characters per line
|
||||
preDiff = idealCharPerLine - preText.length;
|
||||
postDiff = postText.length - idealCharPerLine;
|
||||
|
||||
// if the smaller string is closer to the length of the ideal than
|
||||
// the longer string, and doesn’t contain just a single space, then
|
||||
// use that one for the line
|
||||
if ((preDiff < postDiff) && (preText.length > 2)) {
|
||||
finalText = preText;
|
||||
wordIndex--;
|
||||
|
||||
// otherwise, use the longer string for the line
|
||||
} else {
|
||||
finalText = postText;
|
||||
}
|
||||
|
||||
lineText.push(finalText.substr(0, finalText.length - 1));
|
||||
}
|
||||
return lineText.map(text => (
|
||||
<ScaledLine key={text.trim().replace(/[^a-z0-9]+/gi, '-')} toWidth={216}>
|
||||
{text}
|
||||
</ScaledLine>
|
||||
));
|
||||
}
|
||||
|
||||
render() {
|
||||
const { onClick, text } = this.props;
|
||||
return (
|
||||
<Card onClick={onClick}>
|
||||
<div className={styles.cardContent}>{this.renderInscription(text)}</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AlltypeCard.propTypes = {
|
||||
onClick: PropTypes.func,
|
||||
text: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
AlltypeCard.defaultProps = {
|
||||
onClick: function() {},
|
||||
};
|
@ -1,4 +0,0 @@
|
||||
.root {
|
||||
width: 240px;
|
||||
cursor: pointer;
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
import React, { PropTypes } from 'react';
|
||||
import { Card, CardMedia, CardTitle, CardText } from 'react-toolbox/lib/card';
|
||||
import styles from './ImageCard.css';
|
||||
|
||||
const ImageCard = (
|
||||
{
|
||||
author,
|
||||
description,
|
||||
image,
|
||||
text,
|
||||
onClick,
|
||||
onImageLoaded,
|
||||
}) => (
|
||||
<Card
|
||||
onClick={onClick}
|
||||
className={styles.root}
|
||||
>
|
||||
<CardTitle
|
||||
title={text}
|
||||
/>
|
||||
{
|
||||
image && <CardMedia aspectRatio="wide">
|
||||
<img
|
||||
src={image}
|
||||
alt={text}
|
||||
onLoad={onImageLoaded}
|
||||
/>
|
||||
</CardMedia>
|
||||
}
|
||||
{ description && <CardText>{ description }</CardText> }
|
||||
</Card>
|
||||
);
|
||||
|
||||
ImageCard.propTypes = {
|
||||
image: PropTypes.string,
|
||||
onClick: PropTypes.func,
|
||||
onImageLoaded: PropTypes.func,
|
||||
text: PropTypes.string.isRequired,
|
||||
description: PropTypes.string,
|
||||
};
|
||||
|
||||
ImageCard.defaultProps = {
|
||||
onClick: () => {
|
||||
},
|
||||
onImageLoaded: () => {
|
||||
},
|
||||
};
|
||||
|
||||
export default ImageCard;
|
@ -1,39 +0,0 @@
|
||||
import React, { PropTypes } from 'react';
|
||||
|
||||
export default class ScaledLine extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this._content = null;
|
||||
this.state = {
|
||||
ratio: 1,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const actualContent = this._content.children[0];
|
||||
|
||||
this.setState({
|
||||
ratio: this.props.toWidth / actualContent.offsetWidth,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { ratio } = this.state;
|
||||
const { children } = this.props;
|
||||
|
||||
const styles = {
|
||||
fontSize: ratio.toFixed(3) + 'em'
|
||||
};
|
||||
|
||||
return (
|
||||
<div ref={(c) => this._content = c} style={styles}>
|
||||
<span>{children}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ScaledLine.propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
toWidth: PropTypes.number.isRequired
|
||||
};
|
@ -1,15 +0,0 @@
|
||||
import React from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { Card } from '../UI';
|
||||
|
||||
export default function UnknownCard({ collection }) {
|
||||
return (
|
||||
<Card>
|
||||
<p>No card of type “{collection.getIn(['card', 'type'])}”.</p>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
UnknownCard.propTypes = {
|
||||
collection: ImmutablePropTypes.map,
|
||||
};
|
@ -3,6 +3,10 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { resolveWidget } from '../Widgets';
|
||||
import styles from './ControlPane.css';
|
||||
|
||||
function isHidden(field) {
|
||||
return field.get('widget') === 'hidden';
|
||||
}
|
||||
|
||||
export default class ControlPane extends Component {
|
||||
|
||||
controlFor(field) {
|
||||
@ -38,7 +42,7 @@ export default class ControlPane extends Component {
|
||||
<div>
|
||||
{
|
||||
fields.map(field =>
|
||||
<div
|
||||
isHidden(field) ? null : <div
|
||||
key={field.get('name')}
|
||||
className={styles.widget}
|
||||
>
|
||||
|
@ -1,127 +0,0 @@
|
||||
import React, { PropTypes } from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { Map } from 'immutable';
|
||||
import { throttle } from 'lodash';
|
||||
import bricks from 'bricks.js';
|
||||
import Waypoint from 'react-waypoint';
|
||||
import history from '../routing/history';
|
||||
import Cards from './Cards';
|
||||
|
||||
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;
|
||||
|
||||
this.bricksConfig = {
|
||||
packed: 'data-packed',
|
||||
sizes: [
|
||||
{ columns: 1, gutter: 15 },
|
||||
{ mq: '495px', columns: 2, gutter: 15 },
|
||||
{ mq: '750px', columns: 3, gutter: 15 },
|
||||
{ mq: '1005px', columns: 4, gutter: 15 },
|
||||
{ mq: '1515px', columns: 5, gutter: 15 },
|
||||
{ mq: '1770px', columns: 6, gutter: 15 },
|
||||
],
|
||||
};
|
||||
|
||||
this.updateBricks = throttle(this.updateBricks.bind(this), 30);
|
||||
this.handleLoadMore = this.handleLoadMore.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.bricksInstance = bricks({
|
||||
container: this.containerNode,
|
||||
packed: this.bricksConfig.packed,
|
||||
sizes: this.bricksConfig.sizes,
|
||||
});
|
||||
|
||||
this.bricksInstance.resize(true);
|
||||
|
||||
if (this.props.entries && this.props.entries.size > 0) {
|
||||
setTimeout(() => {
|
||||
this.bricksInstance.pack();
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if ((prevProps.entries === undefined || prevProps.entries.size === 0)
|
||||
&& this.props.entries.size === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.bricksInstance.pack();
|
||||
}
|
||||
|
||||
componengWillUnmount() {
|
||||
this.bricksInstance.resize(false);
|
||||
}
|
||||
|
||||
updateBricks() {
|
||||
this.bricksInstance.pack();
|
||||
}
|
||||
|
||||
cardFor(collection, entry, link) {
|
||||
const cardType = collection.getIn(['card', 'type']) || 'alltype';
|
||||
const card = Cards[cardType] || Cards.unknown;
|
||||
return React.createElement(card, {
|
||||
key: entry.get('slug'),
|
||||
collection,
|
||||
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,
|
||||
});
|
||||
}
|
||||
|
||||
handleLoadMore() {
|
||||
this.props.onPaginate(this.props.page + 1);
|
||||
}
|
||||
|
||||
handleRef = (node) => {
|
||||
this.containerNode = node;
|
||||
};
|
||||
|
||||
renderCards = () => {
|
||||
const { collections, entries } = this.props;
|
||||
if (Map.isMap(collections)) {
|
||||
const collectionName = collections.get('name');
|
||||
return entries.map((entry) => {
|
||||
const path = `/collections/${ collectionName }/entries/${ entry.get('slug') }`;
|
||||
return this.cardFor(collections, 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;
|
||||
return (
|
||||
<div>
|
||||
<h1>{children}</h1>
|
||||
<div ref={this.handleRef}>
|
||||
{ this.renderCards() }
|
||||
<Waypoint onEnter={this.handleLoadMore} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
21
src/components/EntryListing/EntryListing.css
Normal file
21
src/components/EntryListing/EntryListing.css
Normal file
@ -0,0 +1,21 @@
|
||||
.card {
|
||||
overflow: hidden;
|
||||
margin-bottom: 10px;
|
||||
max-height: 290px;
|
||||
width: 240px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.cardImage {
|
||||
width: 240px;
|
||||
height: 135px;
|
||||
background-position: center center;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.cardsGrid {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
justify-content: space-around;
|
||||
}
|
89
src/components/EntryListing/EntryListing.js
Normal file
89
src/components/EntryListing/EntryListing.js
Normal file
@ -0,0 +1,89 @@
|
||||
import React, { PropTypes } from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import Waypoint from 'react-waypoint';
|
||||
import { Map } from 'immutable';
|
||||
import history from '../../routing/history';
|
||||
import { selectFields, selectInferedField } from '../../reducers/collections';
|
||||
import { Card } from '../UI';
|
||||
import styles from './EntryListing.css';
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
handleLoadMore = () => {
|
||||
this.props.onPaginate(this.props.page + 1);
|
||||
};
|
||||
|
||||
inferFields(collection) {
|
||||
const titleField = selectInferedField(collection, 'title');
|
||||
const descriptionField = selectInferedField(collection, 'description');
|
||||
const imageField = selectInferedField(collection, 'image');
|
||||
const fields = selectFields(collection);
|
||||
const inferedFields = [titleField, descriptionField, imageField];
|
||||
const remainingFields = fields && fields.filter(f => inferedFields.indexOf(f.get('name')) === -1);
|
||||
return { titleField, descriptionField, imageField, remainingFields };
|
||||
}
|
||||
|
||||
renderCard(collection, entry, inferedFields) {
|
||||
const path = `/collections/${ collection.get('name') }/entries/${ entry.get('slug') }`;
|
||||
const label = entry.get('label');
|
||||
const title = label || entry.getIn(['data', inferedFields.titleField]);
|
||||
const image = entry.getIn(['data', inferedFields.imageField]);
|
||||
return (
|
||||
<Card
|
||||
key={entry.get('slug')}
|
||||
onClick={history.push.bind(this, path)} // eslint-disable-line
|
||||
className={styles.card}
|
||||
>
|
||||
{ image &&
|
||||
<header className={styles.cardImage} style={{ backgroundImage: `url(${ image })` }} />
|
||||
}
|
||||
<h1>{title}</h1>
|
||||
{inferedFields.descriptionField ?
|
||||
<p>{entry.getIn(['data', inferedFields.descriptionField])}</p>
|
||||
: inferedFields.remainingFields && inferedFields.remainingFields.map(f => (
|
||||
<p key={f.get('name')}>
|
||||
<strong>{f.get('label')}:</strong> {entry.getIn(['data', f.get('name')])}
|
||||
</p>
|
||||
))
|
||||
}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
renderCards = () => {
|
||||
const { collections, entries } = this.props;
|
||||
|
||||
if (Map.isMap(collections)) {
|
||||
const inferedFields = this.inferFields(collections);
|
||||
return entries.map(entry => this.renderCard(collections, entry, inferedFields));
|
||||
}
|
||||
return entries.map((entry) => {
|
||||
const collection = collections
|
||||
.filter(collection => collection.get('name') === entry.get('collection')).first();
|
||||
const inferedFields = this.inferFields(collection);
|
||||
return this.renderCard(collection, entry, inferedFields);
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { children } = this.props;
|
||||
return (
|
||||
<div>
|
||||
<h1>{children}</h1>
|
||||
<div className={styles.cardsGrid}>
|
||||
{ this.renderCards() }
|
||||
<Waypoint onEnter={this.handleLoadMore} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,13 +1,17 @@
|
||||
import React, { PropTypes } from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
|
||||
function isVisible(field) {
|
||||
return field.get('widget') !== 'hidden';
|
||||
}
|
||||
|
||||
export default function Preview({ collection, fields, widgetFor }) {
|
||||
if (!collection || !fields) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
{fields.map(field => widgetFor(field.get('name')))}
|
||||
{fields.filter(isVisible).map(field => widgetFor(field.get('name')))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -3,13 +3,12 @@ import ReactDOM from 'react-dom';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { ScrollSyncPane } from '../ScrollSync';
|
||||
import registry from '../../lib/registry';
|
||||
import Collection from '../../valueObjects/Collection';
|
||||
import { resolveWidget } from '../Widgets';
|
||||
import { selectTemplateName } from '../../reducers/collections';
|
||||
import Preview from './Preview';
|
||||
import styles from './PreviewPane.css';
|
||||
|
||||
export default class PreviewPane extends React.Component {
|
||||
|
||||
componentDidUpdate() {
|
||||
this.renderPreview();
|
||||
}
|
||||
@ -28,8 +27,8 @@ export default class PreviewPane extends React.Component {
|
||||
|
||||
renderPreview() {
|
||||
const { entry, collection } = this.props;
|
||||
const collectionModel = new Collection(collection);
|
||||
const component = registry.getPreviewTemplate(collectionModel.templateName(entry.get('slug'))) || Preview;
|
||||
const component = registry.getPreviewTemplate(selectTemplateName(collection, entry.get('slug'))) || Preview;
|
||||
|
||||
const previewProps = {
|
||||
...this.props,
|
||||
widgetFor: this.widgetFor,
|
||||
|
@ -1,4 +1,4 @@
|
||||
@import "../theme.css";
|
||||
@import '../theme.css';
|
||||
|
||||
.card {
|
||||
composes: base container rounded depth;
|
||||
@ -7,8 +7,8 @@
|
||||
}
|
||||
|
||||
.card > *:not(iframe, video, img, header, footer) {
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.card > *:not(iframe, video, img, header, footer):first-child {
|
||||
@ -19,6 +19,16 @@
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.card > iframe, .card > video, .card > img {
|
||||
.card > iframe,
|
||||
.card > video,
|
||||
.card > img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.card h1 {
|
||||
border: none;
|
||||
color: var(--defaultColor);
|
||||
font-size: 18px;
|
||||
margin: 15px 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ 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 { status, statusDescriptions } from '../../constants/publishModes';
|
||||
import styles from './UnpublishedListing.css';
|
||||
|
||||
class UnpublishedListing extends React.Component {
|
@ -1,9 +1,9 @@
|
||||
import React, { PropTypes } from 'react';
|
||||
|
||||
export default function StringPreview({ value }) {
|
||||
return <span>{value}</span>;
|
||||
export default function DatePreview({ value }) {
|
||||
return <span>{value ? value.toString() : null}</span>;
|
||||
}
|
||||
|
||||
StringPreview.propTypes = {
|
||||
DatePreview.propTypes = {
|
||||
value: PropTypes.node,
|
||||
};
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { PropTypes, Component } from 'react';
|
||||
import { resolveWidget } from '../Widgets';
|
||||
|
||||
export default class ObjectPreview extends Component {
|
||||
export default class ListPreview extends Component {
|
||||
widgetFor = (field, value) => {
|
||||
const { getMedia } = this.props;
|
||||
const widget = resolveWidget(field.get('widget'));
|
||||
@ -22,11 +22,11 @@ export default class ObjectPreview extends Component {
|
||||
</div>)}</div>) : null;
|
||||
}
|
||||
|
||||
return value ? value.join(', ') : null;
|
||||
return <span>{value ? value.join(', ') : null}</span>;
|
||||
}
|
||||
}
|
||||
|
||||
ObjectPreview.propTypes = {
|
||||
ListPreview.propTypes = {
|
||||
value: PropTypes.node,
|
||||
field: PropTypes.node,
|
||||
getMedia: PropTypes.func.isRequired,
|
||||
|
@ -1,9 +1,9 @@
|
||||
import React, { PropTypes } from 'react';
|
||||
|
||||
export default function StringPreview({ value }) {
|
||||
return <span>{value}</span>;
|
||||
export default function NumberPreview({ value }) {
|
||||
return <span>{value ? value.toString() : null}</span>;
|
||||
}
|
||||
|
||||
StringPreview.propTypes = {
|
||||
NumberPreview.propTypes = {
|
||||
value: PropTypes.node,
|
||||
};
|
||||
|
@ -17,7 +17,7 @@ export default class ObjectPreview extends Component {
|
||||
const { field } = this.props;
|
||||
const fields = field && field.get('fields');
|
||||
|
||||
return <div>{fields && fields.map(f => this.widgetFor(f))}</div>;
|
||||
return <div>{fields ? fields.map(f => this.widgetFor(f)) : null}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { PropTypes } from 'react';
|
||||
|
||||
export default function StringPreview({ value }) {
|
||||
return <span>{value}</span>;
|
||||
return <span>{value ? value.toString() : null}</span>;
|
||||
}
|
||||
|
||||
StringPreview.propTypes = {
|
||||
|
@ -1,4 +1,9 @@
|
||||
import StringPreview from './StringPreview';
|
||||
import React, { PropTypes } from 'react';
|
||||
|
||||
export default class TextPreview extends StringPreview {
|
||||
export default function TextPreview({ value }) {
|
||||
return <span>{value ? value.toString() : null}</span>;
|
||||
}
|
||||
|
||||
TextPreview.propTypes = {
|
||||
value: PropTypes.node,
|
||||
};
|
||||
|
Reference in New Issue
Block a user