Skip to content

Commit e14bd71

Browse files
Storybook install, stories and guidelines (MetaMask#3374)
* Initial storybook implementation with example stories and docs * Rebasing main, updating index.js and docs, minifying images * Further docs image reduction
1 parent a61c28a commit e14bd71

22 files changed

+1679
-17
lines changed

.prettierignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,5 @@ metro.config.js
1414
jest.preprocessor.js
1515
scripts/metamask-bot-build-announce.js
1616
CHANGELOG.md
17+
# Ignore auto generated file used for react-native-storybook-loader
18+
/storybook/storyLoader.js

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,3 +185,7 @@ Whenever you change dependencies (adding, removing, or updating, either in `pack
185185
### Architecture
186186

187187
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).
188+
189+
### Storybook
190+
191+
We have begun documenting our components using storybook please read the [Documentation Guidelines](./storybook/DOCUMENTATION_GUIDELINES.md) to get up and running.

app/components/Base/Alert.stories.tsx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import React from 'react';
2+
3+
import { storiesOf } from '@storybook/react-native';
4+
import { action } from '@storybook/addon-actions';
5+
import { text, boolean, select } from '@storybook/addon-knobs';
6+
7+
import Alert, { AlertType } from './Alert';
8+
import Text from './Text';
9+
import { colors, fontStyles } from ' ../../../styles/common';
10+
import EvilIcons from 'react-native-vector-icons/EvilIcons';
11+
12+
const styles = {
13+
alertIcon: {
14+
fontSize: 20,
15+
...fontStyles.bold,
16+
color: colors.yellow,
17+
marginRight: 6,
18+
},
19+
};
20+
21+
storiesOf('Base / Alert', module)
22+
.addDecorator((getStory) => getStory())
23+
.add('Default', () => {
24+
const renderIconKnob = boolean('renderIcon', false);
25+
return (
26+
<Alert
27+
type={select('Type', [AlertType.Info, AlertType.Warning, AlertType.Error], AlertType.Warning)}
28+
small={boolean('small', false)}
29+
renderIcon={renderIconKnob ? () => <EvilIcons name="bell" style={styles.alertIcon} /> : () => null}
30+
onPress={action('onPress')}
31+
>
32+
<Text>{text('children', 'This is an Alert component')}</Text>
33+
</Alert>
34+
);
35+
});

app/components/Base/Text.stories.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import React from 'react';
2+
3+
import { storiesOf } from '@storybook/react-native';
4+
import { action } from '@storybook/addon-actions';
5+
import { text, boolean } from '@storybook/addon-knobs';
6+
7+
import Text from './Text';
8+
9+
storiesOf('Base / Text', module)
10+
.addDecorator((getStory) => getStory())
11+
.add('Default', () => (
12+
<Text
13+
onPress={action('onPress')}
14+
reset={boolean('reset', false)}
15+
centered={boolean('centered', false)}
16+
right={boolean('right', false)}
17+
bold={boolean('bold', false)}
18+
green={boolean('green', false)}
19+
black={boolean('black', false)}
20+
blue={boolean('blue', false)}
21+
grey={boolean('grey', false)}
22+
red={boolean('red', false)}
23+
orange={boolean('orange', false)}
24+
primary={boolean('primary', false)}
25+
disclaimer={boolean('disclaimer', false)}
26+
small={boolean('small', false)}
27+
big={boolean('big', false)}
28+
upper={boolean('upper', false)}
29+
modal={boolean('modal', false)}
30+
infoModal={boolean('infoModal', false)}
31+
link={boolean('link', false)}
32+
strikethrough={boolean('strikethrough', false)}
33+
underline={boolean('underline', false)}
34+
noMargin={boolean('noMargin', false)}
35+
>
36+
{text('children', 'This is a Text component')}
37+
</Text>
38+
));

app/components/Base/Title.stories.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import React from 'react';
2+
3+
import { storiesOf } from '@storybook/react-native';
4+
import { action } from '@storybook/addon-actions';
5+
import { text, boolean } from '@storybook/addon-knobs';
6+
7+
import Title from './Title';
8+
9+
storiesOf('Base / Title', module)
10+
.addDecorator((getStory) => getStory())
11+
.add('Default', () => (
12+
<Title onPress={action('onPress')} centered={boolean('centered', false)} hero={boolean('hero', false)}>
13+
{text('children', 'This is a Title component')}
14+
</Title>
15+
));

app/components/UI/Fox/Fox.stories.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import React from 'react';
2+
3+
import { storiesOf } from '@storybook/react-native';
4+
import { boolean } from '@storybook/addon-knobs';
5+
6+
import Fox from '.';
7+
import backgroundShapes from '../Swaps/components/LoadingAnimation/backgroundShapes';
8+
9+
const customStyle = `
10+
#head {
11+
height: 30%;
12+
top: 50%;
13+
transform: translateY(-50%);
14+
}
15+
#bgShapes {
16+
position: absolute;
17+
left: 50%;
18+
top: 50%;
19+
width: 70%;
20+
height: 70%;
21+
transform: translateX(-50%) translateY(-50%) rotate(0deg);
22+
animation: rotate 50s linear infinite;
23+
}
24+
25+
@keyframes rotate {
26+
to {
27+
transform: translateX(-50%) translateY(-50%) rotate(360deg);
28+
}
29+
}
30+
`;
31+
storiesOf('UI / Fox', module)
32+
.addDecorator((getStory) => getStory())
33+
.add('Default', () => {
34+
const customContentKnob = boolean('customContent', false);
35+
return <Fox customContent={customContentKnob ? backgroundShapes : ''} customStyle={customStyle} />;
36+
});
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import React from 'react';
2+
import { View } from 'react-native';
3+
import { action } from '@storybook/addon-actions';
4+
import { text, boolean, select } from '@storybook/addon-knobs';
5+
import { storiesOf } from '@storybook/react-native';
6+
import StyledButton from '.';
7+
8+
storiesOf('UI / StyledButton', module)
9+
.addDecorator((getStory) => getStory())
10+
.add('Default', () => (
11+
<View>
12+
<StyledButton
13+
type={select(
14+
'type',
15+
{
16+
orange: 'orange',
17+
blue: 'blue',
18+
confirm: 'confirm',
19+
normal: 'normal',
20+
'rounded-normal': 'rounded-normal',
21+
cancel: 'cancel',
22+
signingCancel: 'signingCancel',
23+
transparent: 'transparent',
24+
'transparent-blue': 'transparent-blue',
25+
warning: 'warning',
26+
'warning-empty': 'warning-empty',
27+
info: 'info',
28+
neutral: 'neutral',
29+
danger: 'danger',
30+
sign: 'sign',
31+
view: 'view',
32+
},
33+
'confirm'
34+
)}
35+
onPress={action('onPress')}
36+
disabled={boolean('disabled', false)}
37+
onPressOut={action('onPressOut')}
38+
>
39+
{text('children', 'Confirm')}
40+
</StyledButton>
41+
</View>
42+
));

index.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ LogBox.ignoreLogs([
6161
'PushNotificationIOS has been extracted', // RNC PushNotification iOS issue - https://github.com/react-native-push-notification/ios/issues/43
6262
]);
6363

64+
/* Uncomment and comment regular registration below */
65+
// import Storybook from './storybook';
66+
// AppRegistry.registerComponent(name, () => Storybook);
67+
6468
/**
6569
* Application entry point responsible for registering root component
6670
*/

package.json

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@
4545
"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",
4646
"stacktrace:android": "stack-beautifier sourcemaps/android/index.js.map -t sourcemaps/trace.txt",
4747
"stacktrace:ios": "stack-beautifier sourcemaps/ios/index.js.map -t sourcemaps/trace.txt",
48-
"update-changelog": "./scripts/auto-changelog.sh"
48+
"update-changelog": "./scripts/auto-changelog.sh",
49+
"prestorybook": "rnstl"
4950
},
5051
"prettier": {
5152
"printWidth": 120,
@@ -248,6 +249,12 @@
248249
"@metamask/eslint-config-typescript": "^7.0.0",
249250
"@metamask/mobile-provider": "^2.1.0",
250251
"@react-native-community/eslint-config": "^2.0.0",
252+
"@storybook/addon-actions": "^5.3",
253+
"@storybook/addon-knobs": "^5.3",
254+
"@storybook/addon-links": "^5.3",
255+
"@storybook/addon-ondevice-actions": "^5.3.23",
256+
"@storybook/addon-ondevice-knobs": "^5.3.25",
257+
"@storybook/react-native": "^5.3.25",
251258
"@types/enzyme": "^3.10.9",
252259
"@types/jest": "^27.0.1",
253260
"@types/react": "^17.0.11",
@@ -260,6 +267,7 @@
260267
"babel-core": "7.0.0-bridge.0",
261268
"babel-eslint": "10.1.0",
262269
"babel-jest": "^26.6.3",
270+
"babel-loader": "^8.2.3",
263271
"concat-cli": "4.0.0",
264272
"detox": "19.4.0",
265273
"enzyme": "3.9.0",
@@ -285,6 +293,7 @@
285293
"prettier": "^2.2.1",
286294
"react-dom": "16.8.4",
287295
"react-native-cli": "2.0.1",
296+
"react-native-storybook-loader": "^2.0.4",
288297
"react-native-svg-asset-plugin": "^0.5.0",
289298
"react-redux-test": "npm:[email protected]",
290299
"react-test-renderer": "17.0.2",
@@ -293,6 +302,15 @@
293302
"stack-beautifier": "1.0.2",
294303
"typescript": "^4.4.2"
295304
},
305+
"config": {
306+
"react-native-storybook-loader": {
307+
"searchDir": [
308+
"./app/components"
309+
],
310+
"pattern": "**/*.stories.@(js|tsx)",
311+
"outputFile": "./storybook/storyLoader.js"
312+
}
313+
},
296314
"detox": {
297315
"configurations": {
298316
"ios.sim.debug": {
@@ -380,7 +398,8 @@
380398
"web3-bzz": false,
381399
"bufferutil": false,
382400
"utf-8-validate": false,
383-
"web3-shh": false
401+
"web3-shh": false,
402+
"highlight.js": false
384403
}
385404
}
386405
}

storybook/DOCUMENTATION_GUIDELINES.md

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
# Documentation Guidelines
2+
3+
## General Guidelines
4+
5+
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.
6+
7+
## Using React Native Storybook
8+
9+
The below steps will explain how to use Storybook for React Native with our current set up.
10+
11+
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:
12+
13+
**Prerequisite**
14+
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)
15+
16+
1. In the root `./index.js` file **uncomment**:
17+
18+
```
19+
// import Storybook from './storybook';
20+
// AppRegistry.registerComponent(name, () => Storybook);
21+
```
22+
23+
and add **comment** out:
24+
25+
```
26+
AppRegistry.registerComponent(name, () => Root);
27+
```
28+
29+
It should look like the below screenshot
30+
31+
![React Native Storybook Preview](./images/rn.sb.0.png)
32+
33+
this will replace the entry point of the app with storybook.
34+
35+
2. Once you have replaced the entry point of the app with storybook in `./index.js` run
36+
37+
```
38+
yarn watch
39+
```
40+
41+
3. Open a new terminal window and run
42+
43+
```
44+
yarn start:ios
45+
```
46+
47+
Once the app builds you should be greeted with this screen.
48+
49+
![React Native Storybook Preview](./images/rn.sb.1.png)
50+
51+
To view all of the stories open the navigator
52+
53+
![Storybook Navigator](./images/rn.sb.2.png)
54+
55+
Select a story to preview
56+
57+
![Storybook Navigator](./images/rn.sb.3.png)
58+
59+
You should also be able to use storybook plugins including actions and knobs in the addons tab
60+
61+
![Storybook Plugins](./images/rn.sb.4.png)
62+
63+
## Creating a Story
64+
65+
1. Create a `ComponentName.stories.tsx` file (example `Alert` story below)
66+
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**)
67+
68+
Example `Alert` story
69+
70+
```jsx
71+
// app/components/Base/Alert.stories.tsx
72+
73+
// Import react
74+
import React from 'react';
75+
76+
// Import storybook functions and plugins
77+
import { storiesOf } from '@storybook/react-native';
78+
import { action } from '@storybook/addon-actions';
79+
import { text, boolean, select } from '@storybook/addon-knobs';
80+
81+
// Import the component and any supplementary components / styles that will help with documentation / interactivity
82+
import Alert, { AlertType } from './Alert';
83+
import Text from './Text';
84+
import { colors, fontStyles } from ' ../../../styles/common';
85+
import EvilIcons from 'react-native-vector-icons/EvilIcons';
86+
87+
// Add any styles that are needed
88+
const styles = {
89+
alertIcon: {
90+
fontSize: 20,
91+
...fontStyles.bold,
92+
color: colors.yellow,
93+
marginRight: 6,
94+
},
95+
};
96+
97+
// Create story using the component directory and name for the title
98+
storiesOf('Base / Alert', module)
99+
.addDecorator((getStory) => getStory())
100+
// The naming convention for a component's the first story should be "Default"
101+
.add('Default', () => {
102+
const renderIconKnob = boolean('renderIcon', false);
103+
return (
104+
<Alert
105+
// All appropriate props should include an action or knob to show component api options
106+
type={select('Type', [AlertType.Info, AlertType.Warning, AlertType.Error], AlertType.Warning)}
107+
small={boolean('small', false)}
108+
renderIcon={renderIconKnob ? () => <EvilIcons name="bell" style={styles.alertIcon} /> : () => null}
109+
onPress={action('onPress')}
110+
>
111+
<Text>{text('children', 'This is an Alert component')}</Text>
112+
</Alert>
113+
);
114+
});
115+
```
116+
117+
Nice work! You're now ready to start creating component documentation using storybook 🎉 👍
118+
119+
> 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..

0 commit comments

Comments
 (0)