├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitignore ├── CHANGELOG.md ├── README.md ├── bower.json ├── dist ├── react-media-object.js └── react-media-object.min.js ├── example └── src │ ├── .gitignore │ ├── example.js │ ├── example.less │ └── index.html ├── gulpfile.js ├── lib ├── Bd.js ├── Img.js ├── ImgExt.js ├── Media.js ├── ReactMediaObject.js └── utils │ └── styleResolver.js ├── package.json ├── src ├── Bd.js ├── Img.js ├── ImgExt.js ├── Media.js ├── ReactMediaObject.js └── utils │ └── styleResolver.js └── test ├── .eslintrc ├── .gitkeep ├── Bd-test.js ├── Img-test.js ├── ImgExt-test.js ├── Media-test.js ├── mocha.opts ├── styleResolver-test.js └── utils └── document.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs 2 | # editorconfig.org 3 | root = true 4 | 5 | [*] 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = false 9 | insert_final_newline = true 10 | indent_style = tab 11 | 12 | [*.json] 13 | indent_style = space 14 | indent_size = 2 15 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | .publish/* 2 | dist/* 3 | example/dist/* 4 | lib/* 5 | node_modules/* 6 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "env": { 4 | "browser": true, 5 | "node": true 6 | }, 7 | "plugins": [ 8 | "react" 9 | ], 10 | "rules": { 11 | "curly": [2, "multi-line"], 12 | "quotes": [2, "single", "avoid-escape"], 13 | "react/display-name": 0, 14 | "react/jsx-boolean-value": 1, 15 | "react/jsx-quotes": 1, 16 | "react/jsx-no-undef": 1, 17 | "react/jsx-sort-props": 0, 18 | "react/jsx-sort-prop-types": 1, 19 | "react/jsx-uses-react": 1, 20 | "react/jsx-uses-vars": 1, 21 | "react/no-did-mount-set-state": 1, 22 | "react/no-did-update-set-state": 1, 23 | "react/no-multi-comp": 0, 24 | "react/no-unknown-property": 1, 25 | "react/prop-types": 1, 26 | "react/react-in-jsx-scope": 1, 27 | "react/self-closing-comp": 1, 28 | "react/wrap-multilines": 1, 29 | "semi": 2, 30 | "strict": 0 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Coverage tools 11 | lib-cov 12 | coverage 13 | coverage.html 14 | .cover* 15 | 16 | # Dependency directory 17 | node_modules 18 | 19 | # Example build directory 20 | example/dist 21 | .publish 22 | 23 | # Editor and other tmp files 24 | *.swp 25 | *.un~ 26 | *.iml 27 | *.ipr 28 | *.iws 29 | *.sublime-* 30 | .idea/ 31 | *.DS_Store 32 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## CHANGELOG 2 | 3 | ### 0.1.0 4 | 5 | * [BREAKING CHANGE] React 0.13 => React 0.14 6 | * The dependency allows the lib to use parent-based context 7 | * [FEATURE] `reverse` prop for flipping the side of the avatar 8 | 9 | ### 0.0.8 10 | 11 | * [FIX] remove `.isRequired` from `Img` href PropType 12 | 13 | ### 0.0.7 14 | 15 | * [BREAKING CHANGE] Remove default Media styles `margin: 0 1.5em` 16 | 17 | ### 0.0.6 18 | 19 | * [FEATURE] add noDefaultStyle prop for clearing default styles 20 | * [FEATURE] extract styleResolver into reusable function 21 | 22 | ### 0.0.5 23 | 24 | * [BREAKING CHANGE] remove default classes 25 | + media 26 | + img 27 | + bd 28 | * [BREAKING CHANGE] merge props.style into default style. no longer wipes out default style 29 | * [FEATURE] add tests via `npm test` and `npm run test:watch` 30 | 31 | ### 0.0.4 32 | 33 | * [BREAKING CHANGE] remove namspacing 34 | + MediaImg -> Img 35 | + MediaImgExt -> ImgExt 36 | + MediaBd -> Bd 37 | 38 | ### <0.0.4 39 | 40 | * ...stuff happened... 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Media Object 2 | 3 | This is a Media Object implementation in React.js. 4 | 5 | The CSS [Media Object](http://www.stubbornella.org/content/2010/06/25/the-media-object-saves-hundreds-of-lines-of-code/) was created by [Nicole Sullivan](https://twitter.com/stubbornella). It's an undeniably concise nugget of CSS. I love it. 6 | 7 | This project is part of a proof-of-concept for React inline-styles. In practice, can a Media Object component be as usefel and flexible as the original OOCSS version. 8 | 9 | 10 | ## Installation 11 | 12 | #### Node 13 | 14 | ```bash 15 | npm install react-media-object --save 16 | ``` 17 | 18 | #### In-browser 19 | 20 | ```html 21 | 22 | ``` 23 | 24 | *`React` must be available as a global when this script is run.* 25 | 26 | 27 | ## Usage 28 | 29 | ReactMediaObject is four components, half of which are optional 30 | 31 | ```html 32 | var { Media, Img, ImgExt, Bd } = require('react-media-object'); 33 | 34 | var MediaObjectWithAllRegions = ( 35 | 36 | 37 | 38 | 39 | 40 | 41 | I've spent most of my career focused on taming styles in CSS. I saw it as a 42 | problem that would never be solved. Then, #reactjs happened. 43 | 44 | 45 | ); 46 | 47 | var MediaObjectWithMinimalRegions = ( 48 | 49 | 50 | I've spent most of my career focused on taming styles in CSS. I saw it as a 51 | problem that would never be solved. Then, #reactjs happened. 52 | 53 | 54 | ); 55 | 56 | ReactDOM.render(MediaObjectWithAllRegions, mountNode); 57 | ``` 58 | 59 | 60 | ## Development 61 | 62 | To build the examples locally, clone and run: 63 | 64 | ```bash 65 | npm install 66 | npm start 67 | ``` 68 | 69 | Then open [`localhost:8000`](http://localhost:8000) in a browser. 70 | 71 | 72 | #### Structure 73 | 74 | **NOTE:** The source code for the component is in `src`. A transpiled CommonJS version (generated with Babel) is available in `lib` for use with node.js, browserify and webpack. A UMD bundle is also built to `dist`, which can be included without the need for any build system. 75 | 76 | To build, watch and serve the examples (which will also watch the component source), run `npm start`. If you just want to watch changes to `src` and rebuild `lib`, run `npm run watch` (this is useful if you are working with `npm link`). 77 | 78 | ## License 79 | 80 | The MIT License (MIT) 81 | 82 | Copyright (c) 2015 Michael Chan 83 | 84 | Permission is hereby granted, free of charge, to any person obtaining a copy 85 | of this software and associated documentation files (the "Software"), to deal 86 | in the Software without restriction, including without limitation the rights 87 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 88 | copies of the Software, and to permit persons to whom the Software is 89 | furnished to do so, subject to the following conditions: 90 | 91 | The above copyright notice and this permission notice shall be included in 92 | all copies or substantial portions of the Software. 93 | 94 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 95 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 96 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 97 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 98 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 99 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 100 | THE SOFTWARE. 101 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-media-object", 3 | "version": "0.1.0", 4 | "description": "A React.js Media-Object", 5 | "main": "dist/react-media-object.min.js", 6 | "homepage": "https://github.com/chantastic/react-media-object", 7 | "authors": [ 8 | "Michael Chan" 9 | ], 10 | "keywords": [ 11 | "react", 12 | "react-component", 13 | "media-object" 14 | ], 15 | "license": "MIT", 16 | "ignore": [ 17 | ".editorconfig", 18 | ".gitignore", 19 | "package.json", 20 | "src", 21 | "node_modules", 22 | "example", 23 | "test", 24 | "**/.*", 25 | "bower_components", 26 | "tests" 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /dist/react-media-object.js: -------------------------------------------------------------------------------- 1 | (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.ReactMediaObject = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 9 |
10 |

With optional regions

11 |
12 | 13 | 14 | 17 | 18 | 19 | 20 | I've spent most of my career focused on taming styles in CSS. I 21 | saw it as a problem that would never be solved. Then, #reactjs 22 | happened. 23 |
24 | @chantastic 25 | 7 hours ago 26 |
27 |
28 |
29 |
30 |
31 | 32 |
33 |

With optional `reverse` prop

34 |
35 | 36 | 37 | 40 | 41 | 42 | 43 | I've spent most of my career focused on taming styles in CSS. I 44 | saw it as a problem that would never be solved. Then, #reactjs 45 | happened. 46 |
47 | @chantastic 48 | 7 hours ago 49 |
50 |
51 |
52 |
53 |
54 | 55 |
56 |

Without optional imgExt

57 |
58 | 59 | 60 | chantastic 61 | 62 | 63 | I've spent most of my career focused on taming styles in CSS. I 64 | saw it as a problem that would never be solved. Then, #reactjs 65 | happened. 66 |
67 | @chantastic  68 | 7 hours ago 69 |
70 |
71 |
72 |
73 |
74 | 75 |
76 |

Without optional img

77 |
78 | 79 | 80 |
81 | I've spent most of my career focused on taming styles in CSS. I 82 | saw it as a problem that would never be solved. Then, #reactjs 83 | happened. 84 | @chantastic 85 | 7 hours ago 86 |
87 |
88 |
89 |
90 |
91 | 92 |
93 |

Overriding inline-styles

94 |
95 | 96 | 97 | 102 | 103 | 104 | 105 | I've spent most of my career focused on taming styles in CSS. I 106 | saw it as a problem that would never be solved. Then, #reactjs 107 | happened. 108 |
109 | @chantastic 110 | 7 hours ago 111 |
112 |
113 |
114 |
115 |
116 | 117 |
118 |

With additional className

119 |
120 | 121 | 122 | 126 | 127 | 128 |
129 | I've spent most of my career focused on taming styles in CSS. I 130 | saw it as a problem that would never be solved. Then, #reactjs 131 | happened. 132 | @chantastic 133 | 7 hours ago 134 |
135 |
136 |
137 |
138 |
139 | 140 |
141 |

noDefaultStyle prop

142 |
143 | 144 | 145 | 149 | 150 | 151 | I've spent most of my career focused on taming styles in CSS. I 152 | saw it as a problem that would never be solved. Then, #reactjs 153 | happened. 154 | @chantastic 155 | 7 hours ago 156 | 157 | 158 |
159 |
160 | 161 | ); 162 | } 163 | }); 164 | 165 | ReactDOM.render(, document.getElementById('app')); 166 | -------------------------------------------------------------------------------- /example/src/example.less: -------------------------------------------------------------------------------- 1 | /* 2 | // Examples Stylesheet 3 | // ------------------- 4 | */ 5 | 6 | body { 7 | font-family: Helvetica Neue, Helvetica, Arial, sans-serif; 8 | font-size: 14px; 9 | color: #333; 10 | margin: 0; 11 | padding: 0; 12 | } 13 | 14 | a { 15 | color: #08c; 16 | text-decoration: none; 17 | } 18 | 19 | a:hover { 20 | text-decoration: underline; 21 | } 22 | 23 | .container { 24 | margin-left: auto; 25 | margin-right: auto; 26 | max-width: 720px; 27 | padding: 1em; 28 | } 29 | 30 | .footer { 31 | margin-top: 50px; 32 | border-top: 1px solid #eee; 33 | padding: 20px 0; 34 | font-size: 12px; 35 | color: #999; 36 | } 37 | 38 | h1, h2, h3, h4, h5, h6 { 39 | color: #222; 40 | font-weight: 100; 41 | margin: 0.5em 0; 42 | } 43 | 44 | label { 45 | color: #999; 46 | display: inline-block; 47 | font-size: 0.85em; 48 | font-weight: bold; 49 | margin: 1em 0; 50 | text-transform: uppercase; 51 | } 52 | 53 | .hint { 54 | margin: 15px 0; 55 | font-style: italic; 56 | color: #999; 57 | } 58 | 59 | // @chantastic additions 60 | 61 | .body { 62 | background-color: hsl(0,0,95%); 63 | font-family: 'avenir next', avenir, helvetica, 'helvetica neue', arial, sans-serif; 64 | font-weight: normal; 65 | font-size: initial; 66 | } 67 | 68 | .section { 69 | margin: 3em 0; 70 | } 71 | 72 | .body-heading { 73 | color: hsl(0,0,20%); 74 | font-weight: bold; 75 | } 76 | 77 | .demo-heading { 78 | margin-bottom: 1em; 79 | color: hsl(0,0,30%); 80 | font-weight: bold; 81 | } 82 | 83 | .demo-block { 84 | background-color: #fff; 85 | padding: 1.5em; 86 | border-radius: 3px; 87 | box-shadow: 0 3px 3px -2px hsl(0,0,80%); 88 | } 89 | 90 | // examples 91 | 92 | .MyMedia { 93 | color: red; 94 | font-weight: bold; 95 | } 96 | 97 | .MyImg { border-radius: 50% } 98 | 99 | .MyImgExt { 100 | border: 1px solid navy; 101 | border-radius: 50%; 102 | overflow: hidden; 103 | } 104 | .MyBd { 105 | font-style: oblique 106 | } -------------------------------------------------------------------------------- /example/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | React Media Object 4 | 5 | 6 | 7 |
8 |

React Media Object

9 | View project on GitHub 10 | 11 |
12 |
13 | 14 |
15 | 18 |
19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var initGulpTasks = require('react-component-gulp-tasks'); 3 | 4 | /** 5 | * Tasks are added by the react-component-gulp-tasks package 6 | * 7 | * See https://github.com/JedWatson/react-component-gulp-tasks 8 | * for documentation. 9 | * 10 | * You can also add your own additional gulp tasks if you like. 11 | */ 12 | 13 | var taskConfig = { 14 | 15 | component: { 16 | name: 'ReactMediaObject', 17 | dependencies: [ 18 | 'classnames', 19 | 'react', 20 | 'react/addons' 21 | ], 22 | lib: 'lib' 23 | }, 24 | 25 | example: { 26 | src: 'example/src', 27 | dist: 'example/dist', 28 | files: [ 29 | 'index.html', 30 | '.gitignore' 31 | ], 32 | scripts: [ 33 | 'example.js' 34 | ], 35 | less: [ 36 | 'example.less' 37 | ] 38 | } 39 | 40 | }; 41 | 42 | initGulpTasks(gulp, taskConfig); 43 | -------------------------------------------------------------------------------- /lib/Bd.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, '__esModule', { 4 | value: true 5 | }); 6 | 7 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 8 | 9 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 10 | 11 | var _react = require('react'); 12 | 13 | var _react2 = _interopRequireDefault(_react); 14 | 15 | var _utilsStyleResolver = require('./utils/styleResolver'); 16 | 17 | var _utilsStyleResolver2 = _interopRequireDefault(_utilsStyleResolver); 18 | 19 | var styles = { 20 | display: 'table-cell', 21 | verticalAlign: 'top', 22 | width: '10000px !important' 23 | }; 24 | 25 | var Bd = function Bd(props) { 26 | return _react2['default'].createElement('div', _extends({}, props, { style: (0, _utilsStyleResolver2['default'])(styles, props) })); 27 | }; 28 | 29 | Bd.propTypes = { 30 | children: _react.PropTypes.node, 31 | className: _react.PropTypes.string, 32 | style: _react.PropTypes.object 33 | }; 34 | 35 | exports['default'] = Bd; 36 | module.exports = exports['default']; -------------------------------------------------------------------------------- /lib/Img.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, '__esModule', { 4 | value: true 5 | }); 6 | 7 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 8 | 9 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 10 | 11 | var _react = require('react'); 12 | 13 | var _react2 = _interopRequireDefault(_react); 14 | 15 | var _utilsStyleResolverJs = require('./utils/styleResolver.js'); 16 | 17 | var _utilsStyleResolverJs2 = _interopRequireDefault(_utilsStyleResolverJs); 18 | 19 | var styles = { 20 | standard: { 21 | float: 'left', 22 | marginRight: 10 23 | }, 24 | reverse: { 25 | float: 'right', 26 | marginLeft: 10 27 | } 28 | }; 29 | 30 | function baseStyle(context) { 31 | return context.reverse ? styles.reverse : styles.standard; 32 | } 33 | 34 | var Img = function Img(props, context) { 35 | return _react2['default'].createElement('a', _extends({}, props, { style: (0, _utilsStyleResolverJs2['default'])(baseStyle(context), props, context) })); 36 | }; 37 | 38 | Img.propTypes = { 39 | children: _react.PropTypes.node.isRequired, 40 | href: _react.PropTypes.string, 41 | style: _react.PropTypes.object 42 | }; 43 | 44 | Img.contextTypes = { 45 | reverse: _react.PropTypes.bool 46 | }; 47 | 48 | exports['default'] = Img; 49 | module.exports = exports['default']; -------------------------------------------------------------------------------- /lib/ImgExt.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, '__esModule', { 4 | value: true 5 | }); 6 | 7 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 8 | 9 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 10 | 11 | var _react = require('react'); 12 | 13 | var _react2 = _interopRequireDefault(_react); 14 | 15 | var _utilsStyleResolver = require('./utils/styleResolver'); 16 | 17 | var _utilsStyleResolver2 = _interopRequireDefault(_utilsStyleResolver); 18 | 19 | var styles = { display: 'block' }; 20 | 21 | var ImgExt = function ImgExt(props) { 22 | return _react2['default'].createElement('img', _extends({}, props, { style: (0, _utilsStyleResolver2['default'])(styles, props) })); 23 | }; 24 | 25 | ImgExt.propTypes = { 26 | alt: _react.PropTypes.string.isRequired, 27 | src: _react.PropTypes.string.isRequired, 28 | style: _react.PropTypes.object 29 | }; 30 | 31 | exports['default'] = ImgExt; 32 | module.exports = exports['default']; -------------------------------------------------------------------------------- /lib/Media.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, '__esModule', { 4 | value: true 5 | }); 6 | 7 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 8 | 9 | var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 10 | 11 | var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; desc = parent = getter = undefined; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; 12 | 13 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 14 | 15 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 16 | 17 | function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 18 | 19 | var _react = require('react'); 20 | 21 | var _react2 = _interopRequireDefault(_react); 22 | 23 | var _utilsStyleResolverJs = require('./utils/styleResolver.js'); 24 | 25 | var _utilsStyleResolverJs2 = _interopRequireDefault(_utilsStyleResolverJs); 26 | 27 | var clearfixStyles = { 28 | ':before': { 29 | display: 'table' 30 | }, 31 | ':after': { 32 | display: 'table', 33 | clear: 'both' 34 | } 35 | }; 36 | 37 | var styles = null; 38 | 39 | var Media = (function (_Component) { 40 | _inherits(Media, _Component); 41 | 42 | function Media() { 43 | _classCallCheck(this, Media); 44 | 45 | _get(Object.getPrototypeOf(Media.prototype), 'constructor', this).apply(this, arguments); 46 | } 47 | 48 | _createClass(Media, [{ 49 | key: 'getChildContext', 50 | value: function getChildContext() { 51 | return { reverse: this.props.reverse }; 52 | } 53 | }, { 54 | key: 'render', 55 | value: function render() { 56 | return _react2['default'].createElement( 57 | 'div', 58 | _extends({}, this.props, { style: (0, _utilsStyleResolverJs2['default'])(styles, this.props) }), 59 | _react2['default'].createElement('div', { style: clearfixStyles[':before'] }), 60 | this.props.children, 61 | _react2['default'].createElement('div', { style: clearfixStyles[':after'] }) 62 | ); 63 | } 64 | }]); 65 | 66 | return Media; 67 | })(_react.Component); 68 | 69 | exports['default'] = Media; 70 | 71 | Media.propTypes = { 72 | children: _react.PropTypes.node.isRequired, 73 | reverse: _react.PropTypes.bool, 74 | style: _react.PropTypes.object 75 | }; 76 | 77 | Media.childContextTypes = { 78 | reverse: _react.PropTypes.bool 79 | }; 80 | 81 | exports['default'] = Media; 82 | module.exports = exports['default']; -------------------------------------------------------------------------------- /lib/ReactMediaObject.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, '__esModule', { 4 | value: true 5 | }); 6 | 7 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 8 | 9 | var _MediaJs = require('./Media.js'); 10 | 11 | var _MediaJs2 = _interopRequireDefault(_MediaJs); 12 | 13 | var _ImgJs = require('./Img.js'); 14 | 15 | var _ImgJs2 = _interopRequireDefault(_ImgJs); 16 | 17 | var _ImgExtJs = require('./ImgExt.js'); 18 | 19 | var _ImgExtJs2 = _interopRequireDefault(_ImgExtJs); 20 | 21 | var _BdJs = require('./Bd.js'); 22 | 23 | var _BdJs2 = _interopRequireDefault(_BdJs); 24 | 25 | exports.Media = _MediaJs2['default']; 26 | exports.Img = _ImgJs2['default']; 27 | exports.ImgExt = _ImgExtJs2['default']; 28 | exports.Bd = _BdJs2['default']; -------------------------------------------------------------------------------- /lib/utils/styleResolver.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 8 | 9 | exports["default"] = style; 10 | var nullBase = {}; 11 | var nullProps = { 12 | style: {}, 13 | noDefaultStyle: false 14 | }; 15 | 16 | function style() { 17 | var base = arguments.length <= 0 || arguments[0] === undefined ? nullBase : arguments[0]; 18 | var props = arguments.length <= 1 || arguments[1] === undefined ? nullProps : arguments[1]; 19 | 20 | return _extends({}, !props.noDefaultStyle && base, props.style); 21 | } 22 | 23 | module.exports = exports["default"]; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-media-object", 3 | "version": "0.1.0", 4 | "description": "A React.js Media-Object", 5 | "main": "lib/ReactMediaObject.js", 6 | "author": "Michael Chan", 7 | "license": "MIT", 8 | "homepage": "https://github.com/chantastic/react-media-object", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/chantastic/react-media-object.git" 12 | }, 13 | "bugs": { 14 | "url": "https://github.com/chantastic/react-media-object/issues" 15 | }, 16 | "dependencies": { 17 | "classnames": "^2.1.1" 18 | }, 19 | "devDependencies": { 20 | "babel": "^5.8.23", 21 | "babel-eslint": "^4.1.1", 22 | "eslint": "^1.4.1", 23 | "eslint-plugin-react": "^3.3.2", 24 | "gulp": "^3.8.11", 25 | "mocha": "^2.2.5", 26 | "react": "^0.14.2", 27 | "react-component-gulp-tasks": "^0.7.0", 28 | "react-dom": "0.14.0-rc1", 29 | "sinon": "^1.16.1" 30 | }, 31 | "peerDependencies": { 32 | "react": ">=0.14.0" 33 | }, 34 | "browserify-shim": { 35 | "react": "global:React" 36 | }, 37 | "scripts": { 38 | "build": "gulp clean && NODE_ENV=production gulp build", 39 | "examples": "gulp dev:server", 40 | "lint": "eslint ./src", 41 | "lint:fix": "node_modules/.bin/eslint ./src --fix", 42 | "publish:site": "gulp publish:examples", 43 | "release": "gulp release", 44 | "start": "gulp dev", 45 | "test": "mocha", 46 | "test:watch": "node_modules/.bin/_mocha --watch", 47 | "watch": "gulp watch:lib" 48 | }, 49 | "keywords": [ 50 | "react", 51 | "react-component" 52 | ] 53 | } 54 | -------------------------------------------------------------------------------- /src/Bd.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import styleResolver from './utils/styleResolver'; 3 | 4 | const styles = { 5 | display: 'table-cell', 6 | verticalAlign: 'top', 7 | width: '10000px !important' 8 | }; 9 | 10 | const Bd = props => ( 11 |
12 | ); 13 | 14 | Bd.propTypes = { 15 | children: PropTypes.node, 16 | className: PropTypes.string, 17 | style: PropTypes.object 18 | }; 19 | 20 | export default Bd; 21 | -------------------------------------------------------------------------------- /src/Img.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import styleResolver from './utils/styleResolver.js'; 3 | 4 | const styles = { 5 | standard: { 6 | float: 'left', 7 | marginRight: 10 8 | }, 9 | reverse: { 10 | float: 'right', 11 | marginLeft: 10 12 | } 13 | }; 14 | 15 | function baseStyle (context) { 16 | return context.reverse ? styles.reverse : styles.standard; 17 | } 18 | 19 | const Img = (props, context) => ( 20 | 21 | ); 22 | 23 | Img.propTypes = { 24 | children: PropTypes.node.isRequired, 25 | href: PropTypes.string, 26 | style: PropTypes.object 27 | }; 28 | 29 | Img.contextTypes = { 30 | reverse: PropTypes.bool 31 | }; 32 | 33 | export default Img; 34 | -------------------------------------------------------------------------------- /src/ImgExt.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import styleResolver from './utils/styleResolver'; 3 | 4 | const styles = { display: 'block' }; 5 | 6 | const ImgExt = props => ( 7 | 8 | ); 9 | 10 | ImgExt.propTypes = { 11 | alt: PropTypes.string.isRequired, 12 | src: PropTypes.string.isRequired, 13 | style: PropTypes.object 14 | }; 15 | 16 | export default ImgExt; 17 | -------------------------------------------------------------------------------- /src/Media.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import styleResolver from './utils/styleResolver.js'; 3 | 4 | const clearfixStyles = { 5 | ':before': { 6 | display: 'table' 7 | }, 8 | ':after': { 9 | display: 'table', 10 | clear: 'both' 11 | } 12 | }; 13 | 14 | const styles = null; 15 | 16 | export default class Media extends Component { 17 | getChildContext () { 18 | return { reverse: this.props.reverse }; 19 | } 20 | 21 | render () { 22 | return ( 23 |
24 |
25 | {this.props.children} 26 |
27 |
28 | ); 29 | } 30 | } 31 | 32 | Media.propTypes = { 33 | children: PropTypes.node.isRequired, 34 | reverse: PropTypes.bool, 35 | style: PropTypes.object 36 | }; 37 | 38 | Media.childContextTypes = { 39 | reverse: PropTypes.bool 40 | }; 41 | 42 | export default Media; 43 | -------------------------------------------------------------------------------- /src/ReactMediaObject.js: -------------------------------------------------------------------------------- 1 | import Media from './Media.js'; 2 | import Img from './Img.js'; 3 | import ImgExt from './ImgExt.js'; 4 | import Bd from './Bd.js'; 5 | 6 | export { Media, Img, ImgExt, Bd }; 7 | -------------------------------------------------------------------------------- /src/utils/styleResolver.js: -------------------------------------------------------------------------------- 1 | const nullBase = {}; 2 | const nullProps = { 3 | style: {}, 4 | noDefaultStyle: false 5 | }; 6 | 7 | export default function style (base = nullBase, props = nullProps) { 8 | return Object.assign( 9 | {}, 10 | !props.noDefaultStyle && base, 11 | props.style 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | }, 5 | "rules": { 6 | "quotes": [2, "double", "avoid-escape"] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chantastic/react-media-object/268a6c820980a3839f384e2131a1e9d0025f53c9/test/.gitkeep -------------------------------------------------------------------------------- /test/Bd-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import sinon from "sinon"; 3 | 4 | import React, { addons } from "react/addons"; 5 | import Bd from "../src/Bd.js"; 6 | 7 | let shallowRenderer = addons.TestUtils.createRenderer(); 8 | 9 | describe("Bd", () => { 10 | let result; 11 | 12 | beforeEach(() => { 13 | shallowRenderer.render(Inner Text); 14 | result = shallowRenderer.getRenderOutput(); 15 | }); 16 | 17 | it("renders an `div` as it's root element", () => { 18 | assert.strictEqual(result.type, "div"); 19 | }); 20 | 21 | it("renders provided children", () => { 22 | assert.strictEqual(result.props.children, "Inner Text"); 23 | 24 | shallowRenderer.render(strong); 25 | result = shallowRenderer.getRenderOutput(); 26 | 27 | assert.deepEqual(result.props.children, strong); 28 | 29 | shallowRenderer.render(1); 30 | result = shallowRenderer.getRenderOutput(); 31 | 32 | assert.deepEqual(result.props.children, 1); 33 | }); 34 | 35 | it("element has default styles", () => { 36 | assert.deepEqual(result.props.style, { 37 | display: "table-cell", 38 | verticalAlign: "top", 39 | width: "10000px !important" 40 | }); 41 | }); 42 | 43 | it("when given a style prop, it merges styles", () => { 44 | shallowRenderer.render(Inner Text); 45 | result = shallowRenderer.getRenderOutput(); 46 | 47 | assert.deepEqual(result.props.style, { 48 | backgroundColor: "blue", 49 | display: "table-cell", 50 | verticalAlign: "top", 51 | width: "10000px !important" 52 | }); 53 | }); 54 | 55 | it("when given a style prop, with a rule that overrides a default, it replaces the default", () => { 56 | shallowRenderer.render(Inner Text); 57 | result = shallowRenderer.getRenderOutput(); 58 | 59 | assert.deepEqual(result.props.style, { 60 | display: "table-cell", 61 | verticalAlign: "bottom", 62 | width: "10000px !important" 63 | }); 64 | }); 65 | 66 | it("accepts event props", () => { 67 | let spyCallback = sinon.spy(); 68 | 69 | shallowRenderer.render(); 70 | result = shallowRenderer.getRenderOutput(); 71 | result.props.onClick(); 72 | 73 | assert.strictEqual(spyCallback.calledOnce, true); 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /test/Img-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import sinon from "sinon"; 3 | 4 | import React, { addons } from "react/addons"; 5 | import Img from "../src/Img.js"; 6 | import Media from "../src/Media.js"; 7 | 8 | let shallowRenderer = addons.TestUtils.createRenderer(); 9 | 10 | describe("Img", () => { 11 | let result; 12 | 13 | beforeEach(() => { 14 | shallowRenderer.render(
); 15 | result = shallowRenderer.getRenderOutput(); 16 | }); 17 | 18 | it("renders an `a` as it's root element", () => { 19 | assert.strictEqual(result.type, "a"); 20 | }); 21 | 22 | it("element has default styles", () => { 23 | assert.deepEqual(result.props.style, { 24 | float: "left", 25 | marginRight: 10 26 | }); 27 | }); 28 | 29 | it("when given a style prop, it merges styles", () => { 30 | shallowRenderer.render(
); 31 | result = shallowRenderer.getRenderOutput(); 32 | 33 | assert.deepEqual(result.props.style, { 34 | backgroundColor: "blue", 35 | float: "left", 36 | marginRight: 10 37 | }); 38 | }); 39 | 40 | it("when given a style prop, with a rule that overrides a default, it replaces the default", () => { 41 | shallowRenderer.render(
); 42 | result = shallowRenderer.getRenderOutput(); 43 | 44 | assert.deepEqual(result.props.style, { 45 | float: "right", 46 | marginRight: 0, 47 | marginLeft: 10 48 | }); 49 | }); 50 | 51 | /* 52 | * FIX: 53 | * Something is preventing doing a camparison with JSX. Find out why. 54 | * Example: https://github.com/facebook/react/blob/4b9c349fd057b3c4b1a0fab5fbdf5bd0c625db8d/src/test/__tests__/ReactTestUtils-test.js#L179-L195 55 | */ 56 | it("renders `props.children`", () => { 57 | shallowRenderer.render(hi); 58 | result = shallowRenderer.getRenderOutput(); 59 | 60 | assert.strictEqual(result.props.children, "hi"); 61 | }); 62 | 63 | it("accepts event props", () => { 64 | let spyCallback = sinon.spy(); 65 | 66 | shallowRenderer.render(
); 67 | result = shallowRenderer.getRenderOutput(); 68 | result.props.onClick(); 69 | 70 | assert.strictEqual(spyCallback.calledOnce, true); 71 | }); 72 | 73 | it("responds to parent `reverse` context with right-floated styles", () => { 74 | shallowRenderer.render(hi, { reverse: true }); 75 | result = shallowRenderer.getRenderOutput(); 76 | 77 | assert.deepEqual(result.props.style, { 78 | float: "right", 79 | marginLeft: 10 80 | }); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /test/ImgExt-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import sinon from "sinon"; 3 | 4 | import React, { addons } from "react/addons"; 5 | import ImgExt from "../src/ImgExt.js"; 6 | 7 | let shallowRenderer = addons.TestUtils.createRenderer(); 8 | 9 | describe("ImgExt", () => { 10 | let result; 11 | 12 | beforeEach(() => { 13 | shallowRenderer.render(); 14 | result = shallowRenderer.getRenderOutput(); 15 | }); 16 | 17 | it("renders an `img` as it's root element", () => { 18 | assert.strictEqual(result.type, "img"); 19 | }); 20 | 21 | it("renders with provided `alt` prop", () => { 22 | assert.strictEqual(result.props.alt, "sample alt"); 23 | }); 24 | 25 | it("renders with provided `src` prop", () => { 26 | assert.strictEqual(result.props.src, "http://x.xxx"); 27 | }); 28 | 29 | it("element has default styles", () => { 30 | assert.deepEqual(result.props.style, { display: "block" }); 31 | }); 32 | 33 | it("when given a style prop, it merges styles", () => { 34 | shallowRenderer.render(); 35 | result = shallowRenderer.getRenderOutput(); 36 | 37 | assert.deepEqual(result.props.style, { 38 | display: "block", 39 | backgroundColor: "blue" 40 | }); 41 | }); 42 | 43 | it("when given a style prop, with a rule that overrides a default, it replaces the default", () => { 44 | shallowRenderer.render(); 45 | result = shallowRenderer.getRenderOutput(); 46 | 47 | assert.deepEqual(result.props.style, { 48 | display: "inline-block" 49 | }); 50 | }); 51 | 52 | it("accepts event props", () => { 53 | let spyCallback = sinon.spy(); 54 | 55 | shallowRenderer.render(); 56 | result = shallowRenderer.getRenderOutput(); 57 | result.props.onClick(); 58 | 59 | assert.strictEqual(spyCallback.calledOnce, true); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /test/Media-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import sinon from "sinon"; 3 | 4 | import React, { addons } from "react/addons"; 5 | import Media from "../src/Media.js"; 6 | 7 | let shallowRenderer = addons.TestUtils.createRenderer(); 8 | 9 | describe("Media", () => { 10 | let result; 11 | 12 | beforeEach(() => { 13 | shallowRenderer.render(
); 14 | result = shallowRenderer.getRenderOutput(); 15 | }); 16 | 17 | it("renders a `div` as it's root element", () => { 18 | assert.strictEqual(result.type, "div"); 19 | }); 20 | 21 | it("renders with no default style", () => { 22 | assert.deepEqual(result.props.style, {}); 23 | }); 24 | 25 | it("when given a style prop, it merges styles", () => { 26 | shallowRenderer.render(
); 27 | result = shallowRenderer.getRenderOutput(); 28 | 29 | assert.deepEqual(result.props.style, { backgroundColor: "blue" }); 30 | }); 31 | 32 | it("has a styled `:before`-like element", () => { 33 | assert.strictEqual(result.type, "div"); 34 | assert.strictEqual(result.props.children[0].props.style.display, "table"); 35 | }); 36 | 37 | it("has a styled `:after`-like element", () => { 38 | assert.strictEqual(result.type, "div"); 39 | 40 | let lastChildIndex = result.props.children.length - 1; 41 | assert.strictEqual(result.props.children[lastChildIndex].props.style.display, "table"); 42 | assert.strictEqual(result.props.children[lastChildIndex].props.style.clear, "both"); 43 | }); 44 | 45 | it("renders `props.children` between before/after elements", () => { 46 | shallowRenderer.render(
); 47 | result = shallowRenderer.getRenderOutput(); 48 | 49 | assert.strictEqual(result.props.children.length, 3); 50 | assert.strictEqual(result.props.children[1].type, "blockquote"); 51 | assert.strictEqual(result.props.children[1].props.className, "test-child"); 52 | }); 53 | 54 | it("accepts event props", () => { 55 | let spyCallback = sinon.spy(); 56 | 57 | shallowRenderer.render(
); 58 | result = shallowRenderer.getRenderOutput(); 59 | 60 | result.props.onClick(); 61 | 62 | assert.strictEqual(spyCallback.calledOnce, true); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --require test/utils/document.js 2 | --compilers js:babel/register 3 | -------------------------------------------------------------------------------- /test/styleResolver-test.js: -------------------------------------------------------------------------------- 1 | var assert = require("assert"); 2 | var styleResolver = require("../src/utils/styleResolver.js"); 3 | 4 | describe("styleResolver", () => { 5 | it("returns {} when no arguments are given", () => { 6 | assert.deepEqual(styleResolver(), {}); 7 | }); 8 | 9 | // NOTE: awaiting bad-data scenarios to write tests and conditions 10 | 11 | describe("when props.noDefaultStyle is falsy", () => { 12 | it("returns {} when base and props.style are empty", () => { 13 | const base = null; 14 | const props = { style: null }; 15 | 16 | assert.deepEqual(styleResolver(base, props), {}); 17 | }); 18 | 19 | it("returns base when props.style is empty", () => { 20 | const base = { backgroundColor: "blue" }; 21 | const props = { style: null }; 22 | 23 | assert.deepEqual(styleResolver(base, props), { backgroundColor: "blue" }); 24 | }); 25 | 26 | it("returns props.style when base is empty", () => { 27 | const base = null; 28 | const props = { style: { backgroundColor: "blue" } }; 29 | 30 | assert.deepEqual(styleResolver(base, props), { backgroundColor: "blue" }); 31 | }); 32 | 33 | it("returns a props.style-priority merge of the two objects when neither are empty", () => { 34 | const base = { 35 | backgroundColor: "blue", 36 | color: "white" 37 | }; 38 | 39 | const props = { 40 | style: { 41 | backgroundColor: "pink", 42 | color: "blue" 43 | } 44 | }; 45 | 46 | assert.deepEqual( 47 | styleResolver(base, props), 48 | { 49 | backgroundColor: "pink", 50 | color: "blue" 51 | } 52 | ); 53 | }); 54 | }); 55 | 56 | describe("when props.noDefaultStyle is truthy", () => { 57 | it("returns {} when base and props.style are empty", () => { 58 | const base = null; 59 | const props = { 60 | style: null, 61 | noDefaultStyle: true 62 | }; 63 | 64 | assert.deepEqual(styleResolver(base, props), {}); 65 | }); 66 | 67 | it("returns {} when props.style is empty", () => { 68 | const base = { backgroundColor: "blue" }; 69 | const props = { 70 | style: null, 71 | noDefaultStyle: true 72 | }; 73 | 74 | assert.deepEqual(styleResolver(base, props), {}); 75 | }); 76 | 77 | it("returns props.style when base is empty", () => { 78 | const base = null; 79 | const props = { 80 | style: { backgroundColor: "blue" }, 81 | noDefaultStyle: true 82 | }; 83 | 84 | assert.deepEqual(styleResolver(base, props), { backgroundColor: "blue" }); 85 | }); 86 | 87 | it("returns a props.style-priority merge of the two objects when neither are empty", () => { 88 | const base = { 89 | backgroundColor: "blue", 90 | color: "white" 91 | }; 92 | 93 | const props = { 94 | style: { backgroundColor: "pink" }, 95 | noDefaultStyle: true 96 | }; 97 | 98 | assert.deepEqual(styleResolver(base, props), { backgroundColor: "pink" }); 99 | }); 100 | }); 101 | }); 102 | -------------------------------------------------------------------------------- /test/utils/document.js: -------------------------------------------------------------------------------- 1 | global.document = { 2 | createElement: function () {} 3 | }; 4 | --------------------------------------------------------------------------------