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

Storybook install, stories and guidelines #3374

Merged
merged 5 commits into from
Feb 1, 2022
Merged
Show file tree
Hide file tree
Changes from 4 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
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
georgewrmarshall marked this conversation as resolved.
Show resolved Hide resolved
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>
));
4 changes: 4 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ LogBox.ignoreLogs([
'PushNotificationIOS has been extracted', // RNC PushNotification iOS issue - https://github.com/react-native-push-notification/ios/issues/43
]);

/* Uncomment and comment regular registration below */
// import Storybook from './storybook';
// AppRegistry.registerComponent(name, () => Storybook);

/**
* Application entry point responsible for registering root component
*/
Expand Down
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 @@ -248,6 +249,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 @@ -260,6 +267,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 @@ -285,6 +293,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 @@ -293,6 +302,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 @@ -380,7 +398,8 @@
"web3-bzz": false,
"bufferutil": false,
"utf-8-validate": false,
"web3-shh": false
"web3-shh": false,
"highlight.js": false
}
}
}
119 changes: 119 additions & 0 deletions storybook/DOCUMENTATION_GUIDELINES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# 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 **uncomment**:

```
// import Storybook from './storybook';
// AppRegistry.registerComponent(name, () => Storybook);
```

and add **comment** out:

```
AppRegistry.registerComponent(name, () => Root);
```

It should look like the below screenshot

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

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