Skip to content

Commit

Permalink
Initial storybook implementation with example stories and docs
Browse files Browse the repository at this point in the history
  • Loading branch information
georgewrmarshall committed Jan 27, 2022
1 parent cafc79d commit 5751742
Show file tree
Hide file tree
Showing 20 changed files with 1,670 additions and 17 deletions.
2 changes: 2 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ metro.config.js
jest.preprocessor.js
scripts/metamask-bot-build-announce.js
CHANGELOG.md
# Ignore auto generated file used for react-native-storybook-loader
/storybook/storyLoader.js
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,3 +185,7 @@ Whenever you change dependencies (adding, removing, or updating, either in `pack
### Architecture

To get a better understanding of the internal architecture of this app take a look at [this diagram](https://github.com/MetaMask/metamask-mobile/blob/main/architecture.svg).

### Storybook

We have begun documenting our components using storybook please read the [Documentation Guidelines](./storybook/DOCUMENTATION_GUIDELINES.md) to get up and running.
35 changes: 35 additions & 0 deletions app/components/Base/Alert.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from 'react';

import { storiesOf } from '@storybook/react-native';
import { action } from '@storybook/addon-actions';
import { text, boolean, select } from '@storybook/addon-knobs';

import Alert, { AlertType } from './Alert';
import Text from './Text';
import { colors, fontStyles } from ' ../../../styles/common';
import EvilIcons from 'react-native-vector-icons/EvilIcons';

const styles = {
alertIcon: {
fontSize: 20,
...fontStyles.bold,
color: colors.yellow,
marginRight: 6,
},
};

storiesOf('Base / Alert', module)
.addDecorator((getStory) => getStory())
.add('Default', () => {
const renderIconKnob = boolean('renderIcon', false);
return (
<Alert
type={select('Type', [AlertType.Info, AlertType.Warning, AlertType.Error], AlertType.Warning)}
small={boolean('small', false)}
renderIcon={renderIconKnob ? () => <EvilIcons name="bell" style={styles.alertIcon} /> : () => null}
onPress={action('onPress')}
>
<Text>{text('children', 'This is an Alert component')}</Text>
</Alert>
);
});
38 changes: 38 additions & 0 deletions app/components/Base/Text.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React from 'react';

import { storiesOf } from '@storybook/react-native';
import { action } from '@storybook/addon-actions';
import { text, boolean } from '@storybook/addon-knobs';

import Text from './Text';

storiesOf('Base / Text', module)
.addDecorator((getStory) => getStory())
.add('Default', () => (
<Text
onPress={action('onPress')}
reset={boolean('reset', false)}
centered={boolean('centered', false)}
right={boolean('right', false)}
bold={boolean('bold', false)}
green={boolean('green', false)}
black={boolean('black', false)}
blue={boolean('blue', false)}
grey={boolean('grey', false)}
red={boolean('red', false)}
orange={boolean('orange', false)}
primary={boolean('primary', false)}
disclaimer={boolean('disclaimer', false)}
small={boolean('small', false)}
big={boolean('big', false)}
upper={boolean('upper', false)}
modal={boolean('modal', false)}
infoModal={boolean('infoModal', false)}
link={boolean('link', false)}
strikethrough={boolean('strikethrough', false)}
underline={boolean('underline', false)}
noMargin={boolean('noMargin', false)}
>
{text('children', 'This is a Text component')}
</Text>
));
15 changes: 15 additions & 0 deletions app/components/Base/Title.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react';

import { storiesOf } from '@storybook/react-native';
import { action } from '@storybook/addon-actions';
import { text, boolean } from '@storybook/addon-knobs';

import Title from './Title';

storiesOf('Base / Title', module)
.addDecorator((getStory) => getStory())
.add('Default', () => (
<Title onPress={action('onPress')} centered={boolean('centered', false)} hero={boolean('hero', false)}>
{text('children', 'This is a Title component')}
</Title>
));
36 changes: 36 additions & 0 deletions app/components/UI/Fox/Fox.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React from 'react';

import { storiesOf } from '@storybook/react-native';
import { boolean } from '@storybook/addon-knobs';

import Fox from '.';
import backgroundShapes from '../Swaps/components/LoadingAnimation/backgroundShapes';

const customStyle = `
#head {
height: 30%;
top: 50%;
transform: translateY(-50%);
}
#bgShapes {
position: absolute;
left: 50%;
top: 50%;
width: 70%;
height: 70%;
transform: translateX(-50%) translateY(-50%) rotate(0deg);
animation: rotate 50s linear infinite;
}
@keyframes rotate {
to {
transform: translateX(-50%) translateY(-50%) rotate(360deg);
}
}
`;
storiesOf('UI / Fox', module)
.addDecorator((getStory) => getStory())
.add('Default', () => {
const customContentKnob = boolean('customContent', false);
return <Fox customContent={customContentKnob ? backgroundShapes : ''} customStyle={customStyle} />;
});
42 changes: 42 additions & 0 deletions app/components/UI/StyledButton/StyledButton.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React from 'react';
import { View } from 'react-native';
import { action } from '@storybook/addon-actions';
import { text, boolean, select } from '@storybook/addon-knobs';
import { storiesOf } from '@storybook/react-native';
import StyledButton from '.';

storiesOf('UI / StyledButton', module)
.addDecorator((getStory) => getStory())
.add('Default', () => (
<View>
<StyledButton
type={select(
'type',
{
orange: 'orange',
blue: 'blue',
confirm: 'confirm',
normal: 'normal',
'rounded-normal': 'rounded-normal',
cancel: 'cancel',
signingCancel: 'signingCancel',
transparent: 'transparent',
'transparent-blue': 'transparent-blue',
warning: 'warning',
'warning-empty': 'warning-empty',
info: 'info',
neutral: 'neutral',
danger: 'danger',
sign: 'sign',
view: 'view',
},
'confirm'
)}
onPress={action('onPress')}
disabled={boolean('disabled', false)}
onPressOut={action('onPressOut')}
>
{text('children', 'Confirm')}
</StyledButton>
</View>
));
23 changes: 21 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@
"sourcemaps:ios": "node_modules/.bin/react-native bundle --platform ios --entry-file index.js --dev false --reset-cache --bundle-output /tmp/bundle.ios.js --assets-dest /tmp/ --sourcemap-output sourcemaps/ios/index.js.map",
"stacktrace:android": "stack-beautifier sourcemaps/android/index.js.map -t sourcemaps/trace.txt",
"stacktrace:ios": "stack-beautifier sourcemaps/ios/index.js.map -t sourcemaps/trace.txt",
"update-changelog": "./scripts/auto-changelog.sh"
"update-changelog": "./scripts/auto-changelog.sh",
"prestorybook": "rnstl"
},
"prettier": {
"printWidth": 120,
Expand Down Expand Up @@ -246,6 +247,12 @@
"@metamask/eslint-config-typescript": "^7.0.0",
"@metamask/mobile-provider": "^2.1.0",
"@react-native-community/eslint-config": "^2.0.0",
"@storybook/addon-actions": "^5.3",
"@storybook/addon-knobs": "^5.3",
"@storybook/addon-links": "^5.3",
"@storybook/addon-ondevice-actions": "^5.3.23",
"@storybook/addon-ondevice-knobs": "^5.3.25",
"@storybook/react-native": "^5.3.25",
"@types/enzyme": "^3.10.9",
"@types/jest": "^27.0.1",
"@types/react": "^17.0.11",
Expand All @@ -258,6 +265,7 @@
"babel-core": "7.0.0-bridge.0",
"babel-eslint": "10.1.0",
"babel-jest": "^26.6.3",
"babel-loader": "^8.2.3",
"concat-cli": "4.0.0",
"detox": "19.4.0",
"enzyme": "3.9.0",
Expand All @@ -283,6 +291,7 @@
"prettier": "^2.2.1",
"react-dom": "16.8.4",
"react-native-cli": "2.0.1",
"react-native-storybook-loader": "^2.0.4",
"react-native-svg-asset-plugin": "^0.5.0",
"react-redux-test": "npm:[email protected]",
"react-test-renderer": "17.0.2",
Expand All @@ -291,6 +300,15 @@
"stack-beautifier": "1.0.2",
"typescript": "^4.4.2"
},
"config": {
"react-native-storybook-loader": {
"searchDir": [
"./app/components"
],
"pattern": "**/*.stories.@(js|tsx)",
"outputFile": "./storybook/storyLoader.js"
}
},
"detox": {
"configurations": {
"ios.sim.debug": {
Expand Down Expand Up @@ -378,7 +396,8 @@
"web3-bzz": false,
"bufferutil": false,
"utf-8-validate": false,
"web3-shh": false
"web3-shh": false,
"highlight.js": false
}
}
}
108 changes: 108 additions & 0 deletions storybook/DOCUMENTATION_GUIDELINES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# Documentation Guidelines

## General Guidelines

Thorough documentation makes it much easier for a component to be found, adapted and reused. It also provides space for explanation and reasoning for a component. This is useful as components become more complex.

## Using React Native Storybook

The below steps will explain how to use Storybook for React Native with our current set up.

In the React Native Storybook [Getting Started](https://github.com/storybookjs/react-native/tree/v5.3.25#getting-started) guide they suggest you change the default export to the storybook UI. This doesn't have the greatest developer experience and we will improve this in future([see other ways to render storybook](https://github.com/storybookjs/react-native/tree/v5.3.25#other-ways-to-render-storybook)) but for now:

**Prerequisite**
Make sure you have your environment set up first. To do that follow the set up instructions in the main [README.md](https://github.com/MetaMask/metamask-mobile)

1. In the root `./index.js` file comment out `AppRegistry.registerComponent(name, () => Root);` and add:

```
export { default } from "./storybook";
```

this will replace the entry point of the app with storybook.

2. Once you have replaced the entry point of the app with storybook in `./index.js` run

```
yarn watch
```

3. Open a new terminal window and run

```
yarn start:ios
```

Once the app builds you should be greeted with this screen.

![React Native Storybook Preview](./images/rn.sb.1.png)

To view all of the stories open the navigator

![Storybook Navigator](./images/rn.sb.2.png)

Select a story to preview

![Storybook Navigator](./images/rn.sb.3.png)

You should also be able to use storybook plugins including actions and knobs in the addons tab

![Storybook Plugins](./images/rn.sb.4.png)

## Creating a Story

1. Create a `ComponentName.stories.tsx` file (example `Alert` story below)
2. Run `yarn prestorybook` (Uses [storybook loader](https://github.com/elderfo/react-native-storybook-loader) to automatically find `stories.@(js|tsx)`(javascript or typescript) files **required step after every new story file is created**)

Example `Alert` story

```jsx
// app/components/Base/Alert.stories.tsx

// Import react
import React from 'react';

// Import storybook functions and plugins
import { storiesOf } from '@storybook/react-native';
import { action } from '@storybook/addon-actions';
import { text, boolean, select } from '@storybook/addon-knobs';

// Import the component and any supplementary components / styles that will help with documentation / interactivity
import Alert, { AlertType } from './Alert';
import Text from './Text';
import { colors, fontStyles } from ' ../../../styles/common';
import EvilIcons from 'react-native-vector-icons/EvilIcons';

// Add any styles that are needed
const styles = {
alertIcon: {
fontSize: 20,
...fontStyles.bold,
color: colors.yellow,
marginRight: 6,
},
};

// Create story using the component directory and name for the title
storiesOf('Base / Alert', module)
.addDecorator((getStory) => getStory())
// The naming convention for a component's the first story should be "Default"
.add('Default', () => {
const renderIconKnob = boolean('renderIcon', false);
return (
<Alert
// All appropriate props should include an action or knob to show component api options
type={select('Type', [AlertType.Info, AlertType.Warning, AlertType.Error], AlertType.Warning)}
small={boolean('small', false)}
renderIcon={renderIconKnob ? () => <EvilIcons name="bell" style={styles.alertIcon} /> : () => null}
onPress={action('onPress')}
>
<Text>{text('children', 'This is an Alert component')}</Text>
</Alert>
);
});
```

Nice work! You're now ready to start creating component documentation using storybook 🎉 👍

> Note: Currently React Native Storybook is at v5.3 hoping to upgrade to [v6](https://github.com/storybookjs/react-native/blob/next-6.0/v6README.md) soon..
Loading

0 comments on commit 5751742

Please sign in to comment.