diff --git a/docs/assets/TheresaKnott_castle.svg b/docs/assets/TheresaKnott_castle.svg new file mode 100644 index 0000000000..97b2d08617 --- /dev/null +++ b/docs/assets/TheresaKnott_castle.svg @@ -0,0 +1,451 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + Open Clip Art Library + + + castle + 2006-09-03T02:39:11 + Fantasy castle. + http://openclipart.org/detail/85/castle-by-theresaknott + + + TheresaKnott + + + + + building + castle + clip art + clipart + fantasy + medieval + no contour + tower + + + + + + + + + + + diff --git a/docs/client.js b/docs/client.js index c48d2b1628..d9833bd95f 100644 --- a/docs/client.js +++ b/docs/client.js @@ -7,6 +7,7 @@ import './assets/logo.png'; import './assets/favicon.ico'; import './assets/thumbnail.png'; import './assets/thumbnaildiv.png'; +import './assets/TheresaKnott_castle.svg'; import 'codemirror/mode/htmlmixed/htmlmixed'; import 'codemirror/mode/javascript/javascript'; diff --git a/docs/examples/.eslintrc b/docs/examples/.eslintrc index d0f16ae46a..aae7ac2f5f 100644 --- a/docs/examples/.eslintrc +++ b/docs/examples/.eslintrc @@ -48,6 +48,7 @@ "PanelGroup", "Popover", "ProgressBar", + "ResponsiveEmbed", "Row", "SplitButton", "Tab", diff --git a/docs/examples/ResponsiveEmbed.js b/docs/examples/ResponsiveEmbed.js new file mode 100644 index 0000000000..5df6997765 --- /dev/null +++ b/docs/examples/ResponsiveEmbed.js @@ -0,0 +1,9 @@ +const responsiveEmbedInstance = ( +
+ + + +
+); + +React.render(responsiveEmbedInstance, mountNode); diff --git a/docs/src/ComponentsPage.js b/docs/src/ComponentsPage.js index 9ba1aaf324..66f8dcca10 100644 --- a/docs/src/ComponentsPage.js +++ b/docs/src/ComponentsPage.js @@ -807,6 +807,19 @@ const ComponentsPage = React.createClass({ + {/* Responsive embed */} +
+

Responsive embed

+ +

Allow browsers to determine video or slideshow dimensions based on the width of their containing block by creating an intrinsic ratio that will properly scale on any device.

+

You don't need to include frameborder="0" in your iframes.

+

Either 16by9 or 4by3 aspect ratio via a16by9 or a4by3 attribute must be set.

+ + +

Props

+ +
+ {/* Wells */}

Wells

@@ -977,6 +990,7 @@ const ComponentsPage = React.createClass({ Badges Jumbotron Page Header + Responsive embed Wells Glyphicons Tables diff --git a/docs/src/ReactPlayground.js b/docs/src/ReactPlayground.js index 410dbaa6da..1d7f8d9cf9 100644 --- a/docs/src/ReactPlayground.js +++ b/docs/src/ReactPlayground.js @@ -48,6 +48,7 @@ const Panel = require('../../src/Panel'); const PanelGroup = require('../../src/PanelGroup'); const Popover = require('../../src/Popover'); const ProgressBar = require('../../src/ProgressBar'); +const ResponsiveEmbed = require('../../src/ResponsiveEmbed'); const Row = require('../../src/Row'); const SplitButton = require('../../src/SplitButton'); const Tab = require('../../src/Tab'); diff --git a/docs/src/Samples.js b/docs/src/Samples.js index dc2d4ee966..b985e74351 100644 --- a/docs/src/Samples.js +++ b/docs/src/Samples.js @@ -108,6 +108,7 @@ export default { MenuItem: require('fs').readFileSync(__dirname + '/../examples/MenuItem.js', 'utf8'), ImageResponsive: require('fs').readFileSync(__dirname + '/../examples/ImageResponsive.js', 'utf8'), ImageShape: require('fs').readFileSync(__dirname + '/../examples/ImageShape.js', 'utf8'), + ResponsiveEmbed: require('fs').readFileSync(__dirname + '/../examples/ResponsiveEmbed.js', 'utf8'), Overlay: require('fs').readFileSync(__dirname + '/../examples/Overlay.js', 'utf8'), OverlayCustom: require('fs').readFileSync(__dirname + '/../examples/OverlayCustom.js', 'utf8') diff --git a/src/ResponsiveEmbed.js b/src/ResponsiveEmbed.js new file mode 100644 index 0000000000..3196048009 --- /dev/null +++ b/src/ResponsiveEmbed.js @@ -0,0 +1,56 @@ +import React, { PropTypes, cloneElement } from 'react'; +import warning from 'react/lib/warning'; +import classNames from 'classnames'; + +class ResponsiveEmbed extends React.Component { + render() { + const { bsClass, className, a16by9, a4by3, ...props } = this.props; + warning(!(!a16by9 && !a4by3), '`a16by9` or `a4by3` attribute must be set.'); + warning(!(a16by9 && a4by3), 'Either `a16by9` or `a4by3` attribute can be set. Not both.'); + + const aspectRatio = { + 'embed-responsive-16by9': a16by9, + 'embed-responsive-4by3': a4by3 + }; + + return ( +
+ {cloneElement( + this.props.children, + { + ...props, + className: classNames(className, 'embed-responsive-item') + } + )} +
+ ); + } +} + +ResponsiveEmbed.defaultProps = { + bsClass: 'embed-responsive', + a16by9: false, + a4by3: false +}; + +ResponsiveEmbed.propTypes = { + /** + * bootstrap className + * @private + */ + bsClass: PropTypes.string, + /** + * This component accepts only one child element + */ + children: PropTypes.element.isRequired, + /** + * 16by9 aspect ratio + */ + a16by9: PropTypes.bool, + /** + * 4by3 aspect ratio + */ + a4by3: PropTypes.bool +}; + +export default ResponsiveEmbed; diff --git a/src/index.js b/src/index.js index 270a9386d9..cd5023009c 100644 --- a/src/index.js +++ b/src/index.js @@ -52,6 +52,7 @@ export Panel from './Panel'; export PanelGroup from './PanelGroup'; export Popover from './Popover'; export ProgressBar from './ProgressBar'; +export ResponsiveEmbed from './ResponsiveEmbed'; export Row from './Row'; export SafeAnchor from './SafeAnchor'; export SplitButton from './SplitButton'; diff --git a/test/ResponsiveEmbedSpec.js b/test/ResponsiveEmbedSpec.js new file mode 100644 index 0000000000..0d1877827f --- /dev/null +++ b/test/ResponsiveEmbedSpec.js @@ -0,0 +1,92 @@ +import React from 'react'; +import ReactTestUtils from 'react/lib/ReactTestUtils'; +import ResponsiveEmbed from '../src/ResponsiveEmbed'; +import { shouldWarn } from './helpers'; + +describe('ResponsiveEmbed', () => { + it('should contain `embed-responsive` class', () => { + let instance = ReactTestUtils.renderIntoDocument( + +
+ + ); + + let instanceClassName = React.findDOMNode(instance).className; + assert.ok(instanceClassName, 'embed-responsive'); + }); + + it('should warn if neither `a16by9` nor `a4by3` attribute is set', () => { + ReactTestUtils.renderIntoDocument( + +
+ + ); + + shouldWarn('`a16by9` or `a4by3` attribute must be set.'); + }); + + it('should warn about both `a16by9` or `a4by3` attributes set', () => { + ReactTestUtils.renderIntoDocument( + +
+ + ); + + shouldWarn('Either `a16by9` or `a4by3` attribute can be set. Not both.'); + }); + + it('should add `embed-responsive-item` class to child element', () => { + const instance = ReactTestUtils.renderIntoDocument( + +
+ + ); + + let child = React.findDOMNode(instance).firstChild; + assert.ok(child.className.match(/\bembed-responsive-item\b/)); + }); + + it('should add custom classes to child element', () => { + const instance = ReactTestUtils.renderIntoDocument( + +
+ + ); + + let child = React.findDOMNode(instance).firstChild; + assert.ok(child.className.match(/\bcustom-class\b/)); + }); + + it('should pass custom attributes to child element', () => { + const instance = ReactTestUtils.renderIntoDocument( + +
+ + ); + + let child = React.findDOMNode(instance).firstChild; + assert.equal(child.style.color, 'white'); + }); + + it('should add `embed-responsive-16by9` class with `a16by9` attribute set', () => { + const instance = ReactTestUtils.renderIntoDocument( + +
+ + ); + + let wrapper = React.findDOMNode(instance); + assert.ok(wrapper.className.match(/\bembed-responsive-16by9\b/)); + }); + + it('should add `embed-responsive-4by3` class with `a4by3` attribute set', () => { + const instance = ReactTestUtils.renderIntoDocument( + +
+ + ); + + let wrapper = React.findDOMNode(instance); + assert.ok(wrapper.className.match(/\bembed-responsive-4by3\b/)); + }); +});