Add CRNA support

1. detect CRNA projects
2. simplify RN and CRNA templates by consolidating app into single
`index.js` instead of `index.ios.js` and `index.android.js`
3. notify user of extra setup steps in CLI
4. document CRNA idiosyncrasies in README
This commit is contained in:
Michael Shilman 2017-05-24 23:34:29 +10:00
parent f908535e60
commit 3e2673b962
15 changed files with 197 additions and 8 deletions

View File

@ -21,7 +21,30 @@ npm -g i @storybook/cli
getstorybook
```
## Start the Storybook
After you have installed, there are additional steps for `create-react-native-app` apps. See the section for details, otherwise skip to [Start Storybook](#start-storybook)
to see the next step.
## Create React Native App (CRNA)
If you run `getstorybook` inside a CRNA app, you'll be notified that there is an extra step required to use Storybook.
The easiest way to use Storybook inside CRNA is to simply replace your App with the Storybook UI, which is possible by replacing `App.js` with a single line of code:
```js
export default from './storybook';
```
This will get you up and running quickly, but then you lose your app!
There are multiple options here. for example, you can export conditionally:
```js
import StorybookUI from './storybook';
module.exports = __DEV__ ? StorybookUI : App;
```
Alternatively, `StorybookUI` is simply a RN `View` component that can be embedded anywhere in your RN application, e.g. on a tab or within an admin screen.
## Start Storybook
After initial setup start the storybook server with the storybook npm script.
@ -29,14 +52,22 @@ After initial setup start the storybook server with the storybook npm script.
npm run storybook
```
also start your mobile app with the `react-native` command.
Now, you can open <http://localhost:7007> to view your storybook menus in the browser.
## Start App
To see your Storybook stories on the device, you should start your mobile app for the `<platform>` of your choice (typically `ios` or `android`).
For CRNA apps:
```
react-native run-ios
react-native run-android
npm run <platform>
```
For RN apps:
```
react-native run-<platform>
```
Now, you can open <http://localhost:7007> to view your storybook.
Once your app is started, changing the selected story in web browser will update the story displayed within your mobile app.
## Learn More

View File

@ -61,6 +61,9 @@ const end = () => {
logger.log();
};
const CRNA_DISCUSSION =
'https://github.com/storybooks/storybook/blob/master/app/react-native/docs/manual-setup.md';
switch (projectType) {
case types.ALREADY_HAS_STORYBOOK:
logger.log();
@ -83,6 +86,24 @@ switch (projectType) {
.then(end);
break;
case types.REACT_NATIVE_SCRIPTS:
require('../generators/REACT_NATIVE_SCRIPTS')
.then(commandLog('Adding storybook support to your "Create React Native App" app'))
.then(end)
.then(() => {
logger.log(chalk.red('NOTE: CRNA app installation is not 100% automated.'));
logger.log(
'To quickly run storybook, replace contents of',
chalk.bold('\'./App.js\''),
'with:\n'
);
codeLog(["export default from './storybook';"]);
logger.log('\nFor a more complete discussion of options, see:\n');
logger.log(chalk.cyan(CRNA_DISCUSSION));
logger.log();
});
break;
case types.REACT_NATIVE:
require('../generators/REACT_NATIVE')
.then(commandLog('Adding storybook support to your "React Native" app'))

View File

@ -13,8 +13,7 @@ module.exports = latestVersion('@storybook/react-native').then(version => {
const projectName =
dirname && dirname.slice('ios/'.length, dirname.length - '.xcodeproj'.length - 1);
if (projectName) {
shell.sed('-i', '%APP_NAME%', projectName, 'storybook/index.ios.js');
shell.sed('-i', '%APP_NAME%', projectName, 'storybook/index.android.js');
shell.sed('-i', '%APP_NAME%', projectName, 'storybook/index.js');
}
const packageJson = helpers.getPackageJson();

View File

@ -8,3 +8,4 @@ configure(() => {
const StorybookUI = getStorybookUI({ port: 7007, host: 'localhost' });
AppRegistry.registerComponent('%APP_NAME%', () => StorybookUI);
export default StorybookUI;

View File

@ -0,0 +1,26 @@
const mergeDirs = require('merge-dirs').default;
const helpers = require('../../lib/helpers');
const path = require('path');
const shell = require('shelljs');
const latestVersion = require('latest-version');
module.exports = latestVersion('@storybook/react-native').then(version => {
// copy all files from the template directory to project directory
mergeDirs(path.resolve(__dirname, 'template/'), '.', 'overwrite');
const packageJson = helpers.getPackageJson();
packageJson.dependencies = packageJson.dependencies || {};
packageJson.devDependencies = packageJson.devDependencies || {};
packageJson.devDependencies['@storybook/react-native'] = `^${version}`;
if (!packageJson.dependencies['react-dom'] && !packageJson.devDependencies['react-dom']) {
packageJson.devDependencies['react-dom'] = '^15.5.4';
}
packageJson.scripts = packageJson.scripts || {};
packageJson.scripts['storybook'] = 'storybook start -p 7007';
helpers.writePackageJson(packageJson);
});

View File

@ -0,0 +1,2 @@
import '@storybook/addon-actions/register';
import '@storybook/addon-links/register';

View File

@ -7,4 +7,4 @@ configure(() => {
}, module);
const StorybookUI = getStorybookUI({ port: 7007, host: 'localhost' });
AppRegistry.registerComponent('%APP_NAME%', () => StorybookUI);
export default StorybookUI;

View File

@ -0,0 +1,10 @@
import React from 'react';
import { TouchableNativeFeedback } from 'react-native';
export default function Button(props) {
return (
<TouchableNativeFeedback onPress={props.onPress || Function()}>
{props.children}
</TouchableNativeFeedback>
);
}

View File

@ -0,0 +1,10 @@
import React from 'react';
import { TouchableHighlight } from 'react-native';
export default function Button(props) {
return (
<TouchableHighlight onPress={props.onPress || Function()}>
{props.children}
</TouchableHighlight>
);
}

View File

@ -0,0 +1,11 @@
import React from 'react';
import { View } from 'react-native';
import style from './style';
export default function CenterView(props) {
return (
<View style={style.main}>
{props.children}
</View>
);
}

View File

@ -0,0 +1,8 @@
export default {
main: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
};

View File

@ -0,0 +1,40 @@
import React from 'react';
import { View, Text } from 'react-native';
export default class Welcome extends React.Component {
styles = {
wrapper: {
flex: 1,
padding: 24,
justifyContent: 'center',
},
header: {
fontSize: 18,
marginBottom: 18,
},
content: {
fontSize: 12,
marginBottom: 10,
lineHeight: 18,
},
};
showApp(e) {
e.preventDefault();
if (this.props.showApp) this.props.showApp();
}
render() {
return (
<View style={this.styles.wrapper}>
<Text style={this.styles.header}>Welcome to React Native Storybook</Text>
<Text style={this.styles.content}>
This is a UI Component development environment for your React Native app. Here you can display and interact with your UI components as stories. A story is a single state of one or more UI components. You can have as many stories as you want. In other words a story is like a visual test case.
</Text>
<Text style={this.styles.content}>
We have added some stories inside the "storybook/stories" directory for examples. Try editing the "storybook/stories/Welcome.js" file to edit this message.
</Text>
</View>
);
}
}

View File

@ -0,0 +1,25 @@
import React from 'react';
import { Text } from 'react-native';
import { storiesOf } from '@storybook/react-native';
import { action } from '@storybook/addon-actions';
import { linkTo } from '@storybook/addon-links';
import Button from './Button';
import CenterView from './CenterView';
import Welcome from './Welcome';
storiesOf('Welcome', module).add('to Storybook', () => <Welcome showApp={linkTo('Button')} />);
storiesOf('Button', module)
.addDecorator(getStory => <CenterView>{getStory()}</CenterView>)
.add('with text', () => (
<Button onPress={action('clicked-text')}>
<Text>Hello Button</Text>
</Button>
))
.add('with some emoji', () => (
<Button onPress={action('clicked-emoji')}>
<Text>😀 😎 👍 💯</Text>
</Button>
));

View File

@ -34,6 +34,10 @@ module.exports = function detect(options) {
return types.REACT_PROJECT;
}
if (packageJson.dependencies && packageJson.devDependencies['react-native-scripts']) {
return types.REACT_NATIVE_SCRIPTS;
}
if (packageJson.dependencies && packageJson.dependencies['react-native']) {
return types.REACT_NATIVE;
}

View File

@ -4,6 +4,7 @@ module.exports = {
METEOR: 'METEOR',
REACT: 'REACT',
REACT_NATIVE: 'REACT_NATIVE',
REACT_NATIVE_SCRIPTS: 'REACT_NATIVE_SCRIPTS',
REACT_PROJECT: 'REACT_PROJECT',
WEBPACK_REACT: 'WEBPACK_REACT',
ALREADY_HAS_STORYBOOK: 'ALREADY_HAS_STORYBOOK',