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 = (
+
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/));
+ });
+});