├── .gitignore ├── README.md ├── icon-full-screen.js ├── index.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Component Responsive Iframe 2 | 3 | The [component-playground](https://github.com/FormidableLabs/component-playground) 4 | now supports custom renderers for previewing your components. Quite often, 5 | components need to be responsive to both window and local layout constraints. 6 | This component is intended to give you the opportunity to view your components 7 | in an isolated `` to see it interact with multiple layouts. 8 | 9 | This component scrapes and loads all `` and `` 10 | components from the host page under the assumption that the styles to render the 11 | component exist in the host page and should be transferred to the iframe context 12 | as well. 13 | 14 | ## Preview 15 | 16 |  17 | 18 | ## MIT License 19 | 20 | The MIT License (MIT) 21 | 22 | Copyright (c) 2015 Dustan Kasten 23 | 24 | Permission is hereby granted, free of charge, to any person obtaining a copy 25 | of this software and associated documentation files (the "Software"), to deal 26 | in the Software without restriction, including without limitation the rights 27 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 28 | copies of the Software, and to permit persons to whom the Software is 29 | furnished to do so, subject to the following conditions: 30 | 31 | The above copyright notice and this permission notice shall be included in 32 | all copies or substantial portions of the Software. 33 | 34 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 35 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 36 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 37 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 38 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 39 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 40 | THE SOFTWARE. 41 | 42 | -------------------------------------------------------------------------------- /icon-full-screen.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | var FullScreen = ({ 4 | ...props, 5 | height = '100%', 6 | width = '100%' 7 | } = {}) => ( 8 | 17 | ); 18 | 19 | export default FullScreen; 20 | 21 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import React, {Component, PropTypes} from 'react'; 2 | import Frame from 'react-frame-component'; 3 | import FullScreen from './icon-full-screen'; 4 | 5 | const shouldObserveHead = ( 6 | process.env.NODE_ENV === 'development' && 7 | typeof MutationObserver !== 'undefined' 8 | ); 9 | 10 | const devices = [ 11 | {name: 'iPhone 4', value: '320x480'}, 12 | {name: 'iPhone 5', value: '320x568'}, 13 | {name: 'iPhone 6', value: '375x627'}, 14 | {name: 'iPhone 6 Plus', value: '414x736'}, 15 | {name: 'iPad', value: '768x1024'}, 16 | {name: 'Nexus 4', value: '384x567'}, 17 | {name: 'Nexus 5', value: '360x567'}, 18 | {name: 'Nexus 6', value: '412x659'}, 19 | {name: 'Nexus 7', value: '600x960'}, 20 | {name: 'Nexus 10', value: '800x1280'}, 21 | ]; 22 | 23 | var styles = { 24 | container: { 25 | fullScreen: { 26 | position: 'fixed', 27 | top: 0, 28 | left: 0, 29 | bottom: 0, 30 | right: 0, 31 | background: '#eee', 32 | overflow: 'auto', 33 | padding: '1em', 34 | zIndex: 999 35 | }, 36 | 'default': { 37 | position: 'relative', 38 | background: '#eee', 39 | overflow: 'auto', 40 | padding: '1em' 41 | } 42 | }, 43 | form: { 44 | absolute: { 45 | position: 'absolute', 46 | top: 0, 47 | right: 0, 48 | background: '#f0f0f0', 49 | border: '1px solid #ccc', 50 | borderTopColor: '#ddd', 51 | borderBottomWidth: '2px', 52 | padding: '0.5em' 53 | }, 54 | top: { 55 | background: '#303030', 56 | borderBottom: '2px solid #000', 57 | margin: '-1em -1em 1em -1em', 58 | padding: '1em', 59 | color: '#eee' 60 | } 61 | }, 62 | inputs: { 63 | display: 'inline-block', 64 | marginLeft: '1em' 65 | }, 66 | input: { 67 | width: '5em', 68 | background: 'transparent', 69 | border: '0', 70 | borderBottom: '1px solid #ccc', 71 | padding: '0.1em 0.5em', 72 | color: 'inherit' 73 | }, 74 | x: { 75 | fontFace: 'sans-serif', 76 | fontSize: '0.6666em', 77 | display: 'inline-block', 78 | margin: '0 0.333em' 79 | }, 80 | button: { 81 | fontSize: '0.666em', 82 | height: '1.1666rem', 83 | float: 'right' 84 | } 85 | }; 86 | 87 | // grab all of the document style and link tags and to inject into our iFrame 88 | // component 89 | var documentHead = () => typeof document !== 'undefined' && ( 90 |
91 | {Array.prototype.slice.call(document.head.children, 0).map((child, index) => ( 92 | child.tagName === 'STYLE' && child.type === 'text/css' 93 | ? 94 | : child.tagName === 'LINK' && child.rel === 'stylesheet' 95 | ? 96 | : null 97 | ))} 98 | 99 | ); 100 | 101 | class ResponsiveIframe extends Component { 102 | constructor(props, context) { 103 | super(props, context); 104 | 105 | this.state = { 106 | width: 0, 107 | height: 0, 108 | formPosition: 'top', 109 | isFullScreen: false 110 | }; 111 | this.onChange = event => { 112 | var {name, value} = event.target; 113 | this.setState({[name]: value}); 114 | }; 115 | this.handleChangeSelect = event => { 116 | var [width, height] = event.target.value.split('x').map(n => +n); 117 | if (width === 'Custom') return; 118 | this.setState({width, height}); 119 | }; 120 | 121 | this.onSubmit = event => event.preventDefault(); 122 | 123 | this.toggleFormPosition = event => { 124 | this.setState({ 125 | formPosition: this.state.formPosition === 'top' ? 'absolute' : 'top' 126 | }); 127 | }; 128 | 129 | this.toggleFixed = event => { 130 | this.setState({isFullScreen: !this.state.isFullScreen}); 131 | }; 132 | 133 | if (shouldObserveHead) { 134 | this.observer = true; 135 | } 136 | } 137 | 138 | componentWillMount() { 139 | if (typeof document === 'undefined') return; 140 | 141 | if (shouldObserveHead) { 142 | var target = document.head; 143 | // this is a bit of hack to keep the iframe synced with local dev 144 | // stylesheets 145 | this.observer = new MutationObserver((mutations) => { 146 | this.forceUpdate(); 147 | }); 148 | 149 | var config = {childList: true, characterData: true}; 150 | this.observer.observe(target, config); 151 | } 152 | } 153 | 154 | componentWillUnmount() { 155 | if (shouldObserveHead) { 156 | this.observer.disconnect(); 157 | this.observer = null; 158 | } 159 | } 160 | 161 | render() { 162 | var {width, height} = this.state; 163 | var frameStyle = { 164 | background: '#fff', 165 | border: '1px solid #e0e0e0', 166 | width: width || '100%', 167 | height: height || null, 168 | display: 'block', 169 | margin: '0 auto', 170 | }; 171 | 172 | var containerStyles = styles.container[this.state.isFullScreen ? 'fullScreen' : 'default']; 173 | return ( 174 |