-
-
Save stubbornella/e97662e4a197eb9a534a to your computer and use it in GitHub Desktop.
'use strict'; | |
var React = require('react/addons'); | |
var _ = require('lodash'); | |
var setClass = React.addons.classSet; | |
var MediaObject = React.createClass({ | |
render: function () { | |
var classes = setClass({ | |
'media-left': this.props.horizontalAlignment === 'left', | |
'media-right': this.props.horizontalAlignment === 'right', | |
'media-middle': this.props.verticalAlignment === 'middle', | |
'media-bottom': this.props.verticalAlignment === 'bottom', | |
'media-object': !this.props.imageHref | |
}); | |
var paddingClasses = setClass({ | |
'prs': this.props.leftMediaSpacing === 'small', | |
'prm': this.props.leftMediaSpacing === 'medium', | |
'prl': this.props.leftMediaSpacing === 'large', | |
'prxl': this.props.leftMediaSpacing === 'xlarge', | |
'pls': this.props.rightMediaSpacing === 'small', | |
'plm': this.props.rightMediaSpacing === 'medium', | |
'pll': this.props.rightMediaSpacing === 'large', | |
'plxl': this.props.rightMediaSpacing === 'xlarge' | |
}); | |
var mediaClasses = [classes, paddingClasses].join(' '); | |
if (this.props.imageHref) { | |
return ( | |
<a className={mediaClasses} href={this.props.imageHref} > | |
<img alt={this.props.alt} className="media-object" src={this.props.imageSource} height={this.props.height} width={this.props.width} /> | |
</a> | |
); | |
} else { | |
return ( | |
<img alt={this.props.alt} className={mediaClasses} src={this.props.imageSource} height={this.props.height} width={this.props.width} /> | |
); | |
} | |
} | |
}); | |
var Media = React.createClass({ | |
render: function () { | |
var leftMedia, | |
rightMedia = ''; | |
var classes = setClass({ | |
'media': true, | |
'media-stackable-xs': this.props.stackSize === 'xsmall', | |
'media-stackable-sm': this.props.stackSize === 'small', | |
'media-stackable-md': this.props.stackSize === 'medium', | |
'media-stackable-lg': this.props.stackSize === 'large' | |
}); | |
var bodyClasses = setClass({ | |
'media-body': true, | |
'media-middle': this.props.bodyAlignment === 'middle', | |
'media-bottom': this.props.bodyAlignment === 'bottom' | |
}); | |
if (this.props.leftImageSource) { | |
leftMedia = ( | |
<MediaObject | |
horizontalAlignment='left' | |
verticalAlignment={this.props.leftImageAlignment} | |
imageHref={this.props.leftImageHref} | |
imageSource={this.props.leftImageSource} | |
leftMediaSpacing={this.props.leftMediaSpacing} | |
alt={this.props.leftAlt} | |
height={this.props.leftImageHeight} | |
width={this.props.leftImageWidth}> | |
</MediaObject> | |
); | |
} | |
if (this.props.rightImageSource) { | |
rightMedia = ( | |
<MediaObject | |
horizontalAlignment='right' | |
verticalAlignment={this.props.rightImageAlignment} | |
imageHref={this.props.rightImageHref} | |
imageSource={this.props.rightImageSource} | |
rightMediaSpacing={this.props.rightMediaSpacing} | |
alt={this.props.rightAlt} | |
height={this.props.rightImageHeight} | |
width={this.props.rightImageWidth}> | |
</MediaObject> | |
); | |
} | |
return ( | |
<div {...this.props} className={classes}> | |
{leftMedia} | |
<div className={bodyClasses}> | |
{this.props.children} | |
</div> | |
{rightMedia} | |
</div> | |
); | |
} | |
}); | |
module.exports = { | |
Media: Media | |
}; |
@IamDustin - yes, we didn't expose the composition, but I like where you went with it.
Something like this: https://gist.github.com/iamdustan/1dc2a007cbeef6f1faca
@svnlto - do you know of a way to validate that child nodes are a particular type of component? e.g. Media
should only contain MediaBody
or a `MediaImage
We found a property called type.displayName... but we're not sure if we should use it this way. What do you think?
That's a good question, I've only ever had to check for props etc. using propTypes
. Checking the displayName
property on a given component seems sensible tho.
At Facebook, we have a component similar to the media block called "image block" (this may be familiar to you) that requires an image child and a content child. It is used like this:
<ImageBlock>{image}{content}</ImageBlock>
You can imagine easily extending this to:
<ImageBlock>{imageLeft}{content}{imageRight}</ImageBlock>
This gets slightly more complicated if either image is optional. For example, if someone wanted to leave off the left image, the code would be pretty gross:
<ImageBlock>{null}{content}{imageRight}</ImageBlock>
If optional left/right images were desired, I would recommend passing each image in as props instead. For example:
<ImageBlock
imageLeft={imageLeft}
imageRight={imageLeft}>
{content}
</ImageBlock>
Although this last suggestion might feel weird, something to keep in mind is that in React, children are simply props. The only thing that really differentiates children is how it is represented in JSX and how the primitive DOM components treat them.
Hope this helps.
Thanks @yungsters, of course I remember the ImageBlock, though it was written in xhp back when I was at facebook. ;)
Here is where we've ended up...
- We created a flag object to handle middle vertical alignment, it just passes through to media and sets the right property. Media handles top alignment by default.
- We pass in the image as a param, which feels much cleaner
- We were able to add some validations
'use strict';
var React = require('react/addons');
var _ = require('lodash');
var setClass = React.addons.classSet;
var MediaObject = React.createClass({
render: function () {
var classes = setClass({
'media-left': this.props.horizontalAlignment === 'left',
'media-right': this.props.horizontalAlignment === 'right',
'media-middle': this.props.vAlign === 'middle',
'media-bottom': this.props.vAlign === 'bottom'
});
var paddingClasses = setClass({
'prs': this.props.leftMediaSpacing === 'small',
'prm': this.props.leftMediaSpacing === 'medium',
'prl': this.props.leftMediaSpacing === 'large',
'prxl': this.props.leftMediaSpacing === 'xlarge',
'pls': this.props.rightMediaSpacing === 'small',
'plm': this.props.rightMediaSpacing === 'medium',
'pll': this.props.rightMediaSpacing === 'large',
'plxl': this.props.rightMediaSpacing === 'xlarge'
});
var mediaClasses = [classes, paddingClasses].join(' ');
return (
<div className={mediaClasses}>
{this.props.children}
</div>
);
}
});
var Media = React.createClass({
propTypes: {
stackSize: React.PropTypes.oneOf(["xsmall", "small", "medium", "large"]),
vAlign: React.PropTypes.oneOf(["middle","bottom"]),
leftImage: function(props, propName, componentName) {
if(props[propName] && (!props[propName].type || props[propName].type !== "img") ) {
return new Error("Left image must be an image")
}
},
rightImage: function(props, propName, componentName) {
if(props[propName] && (!props[propName].type || props[propName].type !== "img") ) {
return new Error("Right image must be an image")
}
},
hasImages: function(props, propName, componentName) {
if(!props["leftImage"] && !props["rightImage"]) {
return new Error("The media component must have at least one image")
}
}
},
render: function () {
var leftMedia,
rightMedia = '';
var classes = setClass({
'media': true,
'media-stackable-xs': this.props.stackSize === 'xsmall',
'media-stackable-sm': this.props.stackSize === 'small',
'media-stackable-md': this.props.stackSize === 'medium',
'media-stackable-lg': this.props.stackSize === 'large'
});
var bodyClasses = setClass({
'media-body': true,
'media-middle': this.props.vAlign === 'middle',
'media-bottom': this.props.vAlign === 'bottom'
});
if (this.props.leftImage) {
leftMedia = (
<MediaObject
horizontalAlignment='left'
vAlign={this.props.vAlign}
leftMediaSpacing={this.props.leftMediaSpacing}>
{this.props.leftImage}
</MediaObject>
);
}
if (this.props.rightImage) {
rightMedia = (
<MediaObject
horizontalAlignment='right'
vAlign={this.props.vAlign}
rightMediaSpacing={this.props.rightMediaSpacing}>
{this.props.rightImage}
</MediaObject>
);
}
return (
<div {...this.props} className={classes}>
{leftMedia}
<div className={bodyClasses}>
{this.props.children}
</div>
{rightMedia}
</div>
);
}
});
var Flag = React.createClass({
getDefaultProps: function () {
return {
vAlign: 'middle'
}
},
render: function () {
return (
<Media {...this.props}>{this.props.children}</Media>
);
}
});
module.exports = {
Media: Media,
Flag: Flag
};
We still want to set it up to use a <Image />
component rather than the default html img (because it is weird to have an href attribute on a base html element, but that will have to be done after the holidays.
We think the validations will be simpler with an <Image />
component, something like:
optionalUnion: React.PropTypes.oneOfType([
React.PropTypes.instanceOf(Image),
React.PropTypes.instanceOf(Svg),
React.PropTypes.instanceOf(Icon)
]),
But yeah, that will have to wait...
To check if a ReactElement has a particular type, check its .type
property:
var img = <Image />;
img.type === Image // true
There's no built-in PropTypes validator to check for an element with a particular type.
You need to be really careful when checking against the type
field of a ReactElement. When done the naive way, it prevents you from writing abstractions.
For example, if you only accept a prop img
that satisfies img.type === Image
, then it only accepts a literal <Image ...>
component. But it won't accept a higher level component like <ProfilePicture>
that eventually renders an <Image>
component.
@spicyj @vjeux I'm having difficulty getting a ReactElement to return its type the way you specified... my reproduction produced the following:
var img = <Image />
img.type === Image // false
console.log(img.type)
outputs...
function (props) {
// This constructor is overridden by mocks. The argument is used
// by mocks to assert on what gets mounted. This will later be used
// by the stand-alone class implementation.
}
which comes from ReactClass.js. Would love any feedback to clarify my understanding of how to use the type
field of ReactElements!
If it's helpful, we have our docs for this react spike here:
http://styleguide.cfapps.io/beta.html#media_react