Skip to content

Commit

Permalink
[onDeviceUI] Adding on device addons
Browse files Browse the repository at this point in the history
  • Loading branch information
Gongreg committed Oct 12, 2018
1 parent fb774e0 commit 3f6dce7
Show file tree
Hide file tree
Showing 36 changed files with 1,636 additions and 1 deletion.
89 changes: 89 additions & 0 deletions addons/ondevice-backgrounds/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Storybook Addon On Device Backgrounds

Storybook On Device Background Addon can be used to change background colors inside the the simulator in [Storybook](https://storybook.js.org).

[Framework Support](https://github.com/storybooks/storybook/blob/master/ADDONS_SUPPORT.md)

![Storybook Addon Backgrounds Demo](docs/demo.png)

## Installation

```sh
npm i -D @storybook/addon-ondevice-backgrounds
```

## Configuration

Then create a file called `rn-addons.js` in your storybook config.

Add following content to it:

```js
import '@storybook/addon-ondevice-backgrounds/register';
```

Then import `rn-addons.js` next to your `getStorybookUI` call.
```js
import './rn-addons';
```


## Usage

Then write your stories like this:

```js
import React from 'react';
import { storiesOf } from '@storybook/react-native';
import { withBackgrounds } from '@storybook/addon-ondevice-backgrounds';

storiesOf('Button', module)
.addDecorator(
withBackgrounds([
{ name: 'twitter', value: '#00aced', default: true },
{ name: 'facebook', value: '#3b5998' },
])
)
.add('with text', () => <Text>Click me</Text>);
```

You can add the backgrounds to all stories with `addDecorator` in `.storybook/config.js`:

```js
import { addDecorator } from '@storybook/react-native'; // <- or your storybook framework
import { withBackgrounds } from '@storybook/addon-ondevice-backgrounds';

addDecorator(
withBackgrounds([
{ name: 'twitter', value: '#00aced', default: true },
{ name: 'facebook', value: '#3b5998' },
])
);
```

If you want to override backgrounds for a single story or group of stories, pass the `backgrounds` parameter:

```js
import React from 'react';
import { storiesOf } from '@storybook/react-native';

storiesOf('Button', module)
.addParameters({
backgrounds: [
{ name: 'red', value: '#F44336' },
{ name: 'blue', value: '#2196F3', default: true },
],
})
.add('with text', () => <button>Click me</button>);
```

If you don't want to use backgrounds for a story, you can set the `backgrounds` parameter to `[]`, or use `{ disable: true }` to skip the addon:

```js
import React from 'react';
import { storiesOf } from '@storybook/react-native';

storiesOf('Button', module).add('with text', () => <button>Click me</button>, {
backgrounds: { disable: true },
});
```
Binary file added addons/ondevice-backgrounds/docs/demo.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
33 changes: 33 additions & 0 deletions addons/ondevice-backgrounds/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "@storybook/addon-ondevice-backgrounds",
"version": "4.0.0-alpha.24",
"description": "A storybook addon to show different backgrounds for your preview",
"keywords": [
"addon",
"background",
"react",
"storybook"
],
"homepage": "https://storybook.js.org",
"bugs": {
"url": "https://github.com/storybooks/storybook/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/storybooks/storybook.git"
},
"license": "MIT",
"main": "dist/index.js",
"jsnext:main": "src/index.js",
"scripts": {
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "4.0.0-alpha.24",
"prop-types": "^15.6.2"
},
"peerDependencies": {
"react": "*",
"react-native": "*"
}
}
1 change: 1 addition & 0 deletions addons/ondevice-backgrounds/register.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
require('./dist/register');
112 changes: 112 additions & 0 deletions addons/ondevice-backgrounds/src/BackgroundPanel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { View, Text } from 'react-native';
import Events from './constants';
import Swatch from './Swatch';

const defaultBackground = {
name: 'default',
value: 'transparent',
};

const instructionsHtml = `
import { storiesOf } from '@storybook/react';
import { withBackgrounds } from '@storybook/addon-backgrounds';
storiesOf('First Component', module)
.addDecorator(withBackgrounds([
{ name: 'twitter', value: '#00aced' },
{ name: 'facebook', value: '#3b5998" },
]))
.add("First Button", () => <button>Click me</button>);
`.trim();

const Instructions = () => (
<View>
<Text style={{ fontSize: 16 }}>Setup Instructions</Text>
<Text>
Please add the background decorator definition to your story. The background decorate accepts
an array of items, which should include a name for your color (preferably the css class name)
and the corresponding color / image value.
</Text>
<Text>
Below is an example of how to add the background decorator to your story definition.
</Text>
<Text>{instructionsHtml}</Text>
</View>
);

export default class BackgroundPanel extends Component {
constructor(props) {
super(props);

this.state = { backgrounds: [] };
}

componentDidMount() {
const { channel } = this.props;

this.onSet = channel.on(Events.SET, data => {
const backgrounds = [...data];

this.setState({ backgrounds });
});

this.onUnset = channel.on(Events.UNSET, () => {
this.setState({ backgrounds: [] });
});
}

componentWillUnmount() {
const { channel } = this.props;
channel.removeListener(Events.SET, this.onSet);
channel.removeListener(Events.UNSET, this.onUnset);
}

setBackgroundFromSwatch = background => {
this.update(background);
};

update(background) {
const { channel } = this.props;
channel.emit(Events.UPDATE_BACKGROUND, background);
}

render() {
const { active } = this.props;
const { backgrounds = [] } = this.state;

if (!active) {
return null;
}
if (!backgrounds.length) return <Instructions />;

const hasDefault = backgrounds.filter(x => x.default).length;
if (!hasDefault) backgrounds.push(defaultBackground);

return (
<View>
{backgrounds.map(({ value, name }) => (
<View key={`${name} ${value}`}>
<Swatch value={value} name={name} setBackground={this.setBackgroundFromSwatch} />
</View>
))}
</View>
);
}
}
BackgroundPanel.propTypes = {
active: PropTypes.bool.isRequired,
api: PropTypes.shape({
getQueryParam: PropTypes.func,
setQueryParams: PropTypes.func,
}).isRequired,
channel: PropTypes.shape({
emit: PropTypes.func,
on: PropTypes.func,
removeListener: PropTypes.func,
}),
};
BackgroundPanel.defaultProps = {
channel: undefined,
};
30 changes: 30 additions & 0 deletions addons/ondevice-backgrounds/src/Swatch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from 'react';
import PropTypes from 'prop-types';
import { TouchableOpacity, View, Text } from 'react-native';

const Swatch = ({ name, value, setBackground }) => (
<TouchableOpacity
style={{
borderRadius: 4,
borderWidth: 1,
borderColor: 'rgba(0,0,0,0.2)',
marginTop: 10,
marginBottom: 20,
marginHorizontal: 10,
}}
onPress={() => setBackground(value)}
>
<View style={{ flex: 1, backgroundColor: value, height: 40 }} />
<View style={{ padding: 4, flexDirection: 'row', justifyContent: 'space-between' }}>
<Text>{name}:</Text>
<Text>{value}</Text>
</View>
</TouchableOpacity>
);
Swatch.propTypes = {
name: PropTypes.string.isRequired,
value: PropTypes.string.isRequired,
setBackground: PropTypes.func.isRequired,
};

export default Swatch;
8 changes: 8 additions & 0 deletions addons/ondevice-backgrounds/src/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export const ADDON_ID = 'storybook-addon-background';
export const PANEL_ID = `${ADDON_ID}/background-panel`;

export default {
SET: `${ADDON_ID}:set`,
UNSET: `${ADDON_ID}:unset`,
UPDATE_BACKGROUND: `${ADDON_ID}:update`,
};
52 changes: 52 additions & 0 deletions addons/ondevice-backgrounds/src/container.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React from 'react';
import { View } from 'react-native';
import PropTypes from 'prop-types';
import Events from './constants';

export default class Container extends React.Component {
constructor(props) {
super(props);
this.state = { background: props.initialBackground || '' };
this.onBackgroundChange = this.onBackgroundChange.bind(this);
}

componentDidMount() {
const { channel } = this.props;
// Listen to the notes and render it.
channel.on(Events.UPDATE_BACKGROUND, this.onBackgroundChange);
}

// This is some cleanup tasks when the Notes panel is unmounting.
componentWillUnmount() {
const { channel } = this.props;
channel.removeListener(Events.UPDATE_BACKGROUND, this.onBackgroundChange);
}

onBackgroundChange(background) {
this.setState({ background });
}

render() {
const { background } = this.state;
const { children } = this.props;

return (
<View style={{ flex: 1, backgroundColor: background || 'transparent' }}>{children}</View>
);
}
}

Container.propTypes = {
channel: PropTypes.shape({
emit: PropTypes.func,
on: PropTypes.func,
removeListener: PropTypes.func,
}),
initialBackground: PropTypes.string,
children: PropTypes.node.isRequired,
};

Container.defaultProps = {
channel: undefined,
initialBackground: '',
};
34 changes: 34 additions & 0 deletions addons/ondevice-backgrounds/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from 'react';

import addons, { makeDecorator } from '@storybook/addons';

import Events from './constants';
import Container from './container';

export const withBackgrounds = makeDecorator({
name: 'withBackgrounds',
parameterName: 'backgrounds',
skipIfNoParametersOrOptions: true,
allowDeprecatedUsage: true,
wrapper: (getStory, context, { options, parameters }) => {
const data = parameters || options || [];
const backgrounds = Array.isArray(data) ? data : Object.values(data);

let background = 'transparent';
if (backgrounds.length !== 0) {
addons.getChannel().emit(Events.SET, backgrounds);

const defaultOrFirst = backgrounds.find(x => x.default) || backgrounds[0];

if (defaultOrFirst) {
background = defaultOrFirst.value;
}
}

return (
<Container initialBackground={background} channel={addons.getChannel()}>
{getStory(context)}
</Container>
);
},
});
14 changes: 14 additions & 0 deletions addons/ondevice-backgrounds/src/register.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from 'react';
import addons from '@storybook/addons';

import { ADDON_ID, PANEL_ID } from './constants';
import BackgroundPanel from './BackgroundPanel';

addons.register(ADDON_ID, api => {
const channel = addons.getChannel();
addons.addPanel(PANEL_ID, {
title: 'Backgrounds',
// eslint-disable-next-line react/prop-types
render: ({ active }) => <BackgroundPanel channel={channel} api={api} active={active} />,
});
});

0 comments on commit 3f6dce7

Please sign in to comment.