Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions client/components/resizable-iframe/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
Resizable Iframe
================

Resizable Iframe is a React component for rendering an `<iframe>` element which can dynamically update its own dimensions using [`window.postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/Window.postMessage). This is useful in cases where an inline frame of an unknown size is to be displayed on the page.

## Example

The `ResizableIframe` component can be used in much the same way that you would use an `<iframe>` DOM element. Props are automatically transferred to the rendered `<iframe>`, in case you need to specify additional properties or styles.

```html
<ResizableIframe src={ myFrameUrl } frameBorder={ 0 } />
```

## Usage

To allow for resizing of the element, a `ResizableIframe` element listens for `message` events on the `window` object. If the rendered frame is not sandboxed, a script is injected in the frame to manage this behavior on your behalf. If the frame is sandboxed, any page you reference as the `src` URL is responsible for invoking this event using [`window.postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/Window.postMessage). The message should be a JSON string with an `action` value of "resize" and a numeric `width` and `height` to define the new pixel dimensions of the element.

For example, a page can trigger a resize using the following code snippet:

```javascript
if ( window.parent ) {
window.parent.postMessage( JSON.stringify( {
action: 'resize',
width: document.body.clientWidth,
height: document.body.clientHeight
} ), '*' );
}
```

## Props

### `src`

Treated as the `src` URL to be used in the rendered `<iframe>` DOM element.

### `width`

An optional fixed width value, if you don't want this to be the responsibility of the child window.

### `height`

An optional fixed height value, if you don't want this to be the responsibility of the child window.

### `onResize`

An optional function to trigger when the rendered frame has been resized.
173 changes: 173 additions & 0 deletions client/components/resizable-iframe/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
/**
* External dependencies
*/
import ReactDom from 'react-dom';
import React from 'react';
import { omit } from 'lodash';

/**
* Globals
*/
const noop = () => {};

export default React.createClass( {
displayName: 'ResizableIframe',

propTypes: {
src: React.PropTypes.string,
width: React.PropTypes.oneOfType( [
React.PropTypes.string,
React.PropTypes.number
] ),
height: React.PropTypes.oneOfType( [
React.PropTypes.string,
React.PropTypes.number
] ),
onLoad: React.PropTypes.func,
onResize: React.PropTypes.func
},

getInitialState: function() {
return { width: 0, height: 0 };
},

getDefaultProps: function() {
return {
onLoad: noop,
onResize: noop
};
},

componentDidMount: function() {
window.addEventListener( 'message', this.checkMessageForResize, false );
this.maybeConnect();
},

componentDidUpdate: function() {
this.maybeConnect();
},

componentWillUnmount: function() {
window.removeEventListener( 'message', this.checkMessageForResize );
},

getFrameBody: function() {
return ReactDom.findDOMNode( this.refs.iframe ).contentDocument.body;
},

maybeConnect: function() {
if ( ! this.isFrameAccessible() ) {
return;
}

const body = this.getFrameBody();
if ( null !== body.getAttribute( 'data-resizable-iframe-connected' ) ) {
return;
}

const script = document.createElement( 'script' );
script.innerHTML = `
( function() {
var observer;

if ( ! window.MutationObserver || ! document.body || ! window.top ) {
return;
}

function sendResize() {
window.top.postMessage( {
action: 'resize',
width: document.body.offsetWidth,
height: document.body.offsetHeight
}, '*' );
}

observer = new MutationObserver( sendResize );
observer.observe( document.body, {
attributes: true,
attributeOldValue: false,
characterData: true,
characterDataOldValue: false,
childList: true,
subtree: true
} );

window.addEventListener( 'load', sendResize, true );

// Hack: Remove viewport unit styles, as these are relative
// the iframe root and interfere with our mechanism for
// determining the unconstrained page bounds.
function removeViewportStyles( ruleOrNode ) {
[ 'width', 'height', 'minHeight', 'maxHeight' ].forEach( function( style ) {
if ( /^\\d+(vmin|vmax|vh|vw)$/.test( ruleOrNode.style[ style ] ) ) {
ruleOrNode.style[ style ] = '';
}
} );
}

Array.prototype.forEach.call( document.querySelectorAll( '[style]' ), removeViewportStyles );
Array.prototype.forEach.call( document.styleSheets, function( stylesheet ) {
Array.prototype.forEach.call( stylesheet.cssRules || stylesheet.rules, removeViewportStyles );
} );

document.body.style.position = 'absolute';
document.body.setAttribute( 'data-resizable-iframe-connected', '' );

sendResize();
} )();
`;
body.appendChild( script );
},

isFrameAccessible: function() {
try {
return !! this.getFrameBody();
} catch ( e ) {
return false;
}
},

checkMessageForResize: function( event ) {
const iframe = ReactDom.findDOMNode( this.refs.iframe );

// Attempt to parse the message data as JSON if passed as string
let data = event.data || {};
if ( 'string' === typeof data ) {
try {
data = JSON.parse( data );
} catch ( e ) {} // eslint-disable-line no-empty
}

// Verify that the mounted element is the source of the message
if ( ! iframe || iframe.contentWindow !== event.source ) {
return;
}

// Update the state only if the message is formatted as we expect, i.e.
// as an object with a 'resize' action, width, and height
const { action, width, height } = data;
const { width: oldWidth, height: oldHeight } = this.state;

if ( 'resize' === action && ( oldWidth !== width || oldHeight !== height ) ) {
this.setState( { width, height } );
this.props.onResize();
}
},

onLoad: function( event ) {
this.maybeConnect();
this.props.onLoad( event );
},

render: function() {
const omitProps = [ 'onResize' ];
return (
<iframe
ref="iframe"
{ ...omit( this.props, omitProps ) }
onLoad={ this.onLoad }
width={ this.props.width || this.state.width }
height={ this.props.height || this.state.height } />
);
}
} );
20 changes: 20 additions & 0 deletions client/components/social-logo/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
Social Logo
========

Component that renders a single socail-logo svg based on an `icon` prop. It takes a size property but defaults to 24px. For greater sharpness, the icons should only be shown at either 18, 24, 36 or 48px. There's a gallery with all the available icons in devdocs/design.

#### How to use:

```js
let SocialLogo = require( 'components/social-logo' );

render() {
return <SocialLogo icon="twitter" size={ 48 } />;
}
```

#### Props

* `icon`: String - the icon name.
* `size`: Number - (default: 24) set the size of the icon.
* `onClick`: Function - (optional) if you need a click callback.
Loading