Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Viewport-addon Allow setting callback to be called whenever viewport changes #3283

Merged
merged 9 commits into from
Mar 28, 2018
101 changes: 100 additions & 1 deletion addons/viewport/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,53 @@ Setting this property to, let say `iphone6`, will make `iPhone 6` the default de

### viewports : Object
----
A key-value pair of viewport's key and properties for all viewports to be displayed. Default is [`INITIAL_VIEWPORTS`](src/shared/index.js)
A key-value pair of viewport's key and properties (see `Viewport` definition below) for all viewports to be displayed. Default is [`INITIAL_VIEWPORTS`](src/shared/index.js)

#### Viewport Model
```js
{
/**
* name to display in the dropdown
* @type {String}
*/
name: 'Responsive',

/**
* Inline styles to be applied to the story (iframe).
* styles is an object whose key is the camelCased version of the style name, and whose
* value is the style’s value, usually a string
* @type {Object}
*/
styles: {
width: '100%',
height: '100%',
},

/**
* type of the device (e.g. desktop, mobile, or tablet)
* @type {String}
*/
type: 'desktop',
}
```

## Decorators

Sometimes you want to show collection of mobile stories, and you know those stories look horible on desktop (`responsive`), so you think you need to change the default viewport only for those?

Here is the answer, with `withViewport` decorator, you can change the default viewport of single, multiple, or all stories.

`withViewport` accepts either
* A `String`, which represents the default viewport, or
* An `Object`, which looks like
```js
{
name: 'iphone6', // default viewport
onViewportChange({ viewport }) { // called whenever different viewport is selected from the dropdown

}
}
```

## Examples

Expand Down Expand Up @@ -120,3 +166,56 @@ configure({
defaultViewport: 'iphone6'
});
```

## withViewport Decorator

Change the default viewport for single/multiple/global stories, or listen to viewport selection changes

```js
import React from 'react';
import { storiesOf, addDecorator } from '@storybook/react';
import { withViewport } from '@storybook/addon-viewport';

// Globablly
addDecorator(withViewport('iphone5'));

// Collection
storiesOf('Decorator with string', module)
.addDecorator(withViewport('iphone6'))
.add('iPhone 6', () => (
<h1>
Do I look good on <b>iPhone 6</b>?
</h1>
));

storiesOf('Decorator with object', module)
.addDecorator(
withViewport({
onViewportChange({ viewport }) {
console.log(`Viewport changed: ${viewport.name} (${viewport.type})`); // e.g. Viewport changed: iphone6 (mobile)
},
})
)
.add('onViewportChange', () => <MobileFirstComponent />);

```

## Viewport Component

You can also change the default viewport for a single story using `Viewport` component

```js
import React from 'react';
import { storiesOf } from '@storybook/react';
import { Viewport } from '@storybook/addon-viewport';

// Collection
storiesOf('Custom Default', module)
.add('iphone6p', () => (
<Viewport name="iphone6p">
<h1>
Do I look good on <b>iPhone 6 Plus</b>?
</h1>
</Viewport>
));
```
1 change: 1 addition & 0 deletions addons/viewport/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"@storybook/components": "3.4.0-rc.3",
"babel-runtime": "^6.26.0",
"global": "^4.3.2",
"lodash.debounce": "^4.0.8",
"prop-types": "^15.6.1"
},
"peerDependencies": {
Expand Down
59 changes: 37 additions & 22 deletions addons/viewport/src/manager/components/Panel.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { baseFonts } from '@storybook/components';
import { document } from 'global';
import debounce from 'lodash.debounce';

import { resetViewport, viewportsTransformer } from './viewportInfo';
import { SelectViewport } from './SelectViewport';
import { RotateViewport } from './RotateViewport';
import {
SET_STORY_DEFAULT_VIEWPORT_EVENT_ID,
UNSET_STORY_DEFAULT_VIEWPORT_EVENT_ID,
CONFIGURE_VIEWPORT_EVENT_ID,
UPDATE_VIEWPORT_EVENT_ID,
VIEWPORT_CHANGED_EVENT_ID,
INITIAL_VIEWPORTS,
DEFAULT_VIEWPORT,
} from '../../shared';
Expand All @@ -31,6 +32,8 @@ const getDefaultViewport = (viewports, candidateViewport) =>
const getViewports = viewports =>
Object.keys(viewports).length > 0 ? viewports : INITIAL_VIEWPORTS;

const setStoryDefaultViewportWait = 100;

export class Panel extends Component {
static propTypes = {
channel: PropTypes.shape({}).isRequired,
Expand All @@ -50,6 +53,13 @@ export class Panel extends Component {
viewports: viewportsTransformer(INITIAL_VIEWPORTS),
isLandscape: false,
};

this.previousViewport = DEFAULT_VIEWPORT;

this.setStoryDefaultViewport = debounce(
this.setStoryDefaultViewport,
setStoryDefaultViewportWait
);
}

componentDidMount() {
Expand All @@ -60,10 +70,9 @@ export class Panel extends Component {
channel.on(UPDATE_VIEWPORT_EVENT_ID, this.changeViewport);
channel.on(CONFIGURE_VIEWPORT_EVENT_ID, this.configure);
channel.on(SET_STORY_DEFAULT_VIEWPORT_EVENT_ID, this.setStoryDefaultViewport);
channel.on(UNSET_STORY_DEFAULT_VIEWPORT_EVENT_ID, this.unsetStoryDefaultViewport);

this.unsubscribeFromOnStory = api.onStory(() => {
this.changeViewport(this.state.defaultViewport);
this.setStoryDefaultViewport(this.state.defaultViewport);
});
}

Expand All @@ -77,30 +86,16 @@ export class Panel extends Component {
channel.removeListener(UPDATE_VIEWPORT_EVENT_ID, this.changeViewport);
channel.removeListener(CONFIGURE_VIEWPORT_EVENT_ID, this.configure);
channel.removeListener(SET_STORY_DEFAULT_VIEWPORT_EVENT_ID, this.setStoryDefaultViewport);
channel.removeListener(UNSET_STORY_DEFAULT_VIEWPORT_EVENT_ID, this.unsetStoryDefaultViewport);
}

setStoryDefaultViewport = viewport => {
const { viewports } = this.state;
const defaultViewport = getDefaultViewport(viewports, viewport);

this.setState(
{
storyDefaultViewport: defaultViewport,
viewport: defaultViewport,
},
this.updateIframe
);
};

unsetStoryDefaultViewport = () => {
this.setState(
{
storyDefaultViewport: undefined,
viewport: this.state.defaultViewport,
},
this.updateIframe
);
this.setState({
storyDefaultViewport: defaultViewport,
});
this.changeViewport(defaultViewport);
};

configure = (options = Panel.defaultOptions) => {
Expand Down Expand Up @@ -128,11 +123,31 @@ export class Panel extends Component {
viewport,
isLandscape: false,
},
this.updateIframe
() => {
this.updateIframe();
this.emitViewportChanged();
}
);
}
};

emitViewportChanged = () => {
const { channel } = this.props;
const { viewport, viewports } = this.state;

if (!this.shouldNotify()) {
return;
}

this.previousViewport = viewport;

channel.emit(VIEWPORT_CHANGED_EVENT_ID, {
viewport: viewports[viewport],
});
};

shouldNotify = () => this.previousViewport !== this.state.viewport;

toggleLandscape = () => {
const { isLandscape } = this.state;

Expand Down
Loading