Skip to content

Commit

Permalink
Merge pull request #6594 from ForbesLindesay/ondevice-actions
Browse files Browse the repository at this point in the history
Ondevice actions
  • Loading branch information
benoitdion authored May 2, 2019
2 parents dfc0772 + 3990ba7 commit c726632
Show file tree
Hide file tree
Showing 13 changed files with 382 additions and 3 deletions.
2 changes: 1 addition & 1 deletion ADDONS_SUPPORT.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
| | [React](app/react)|[React Native](app/react-native)|[Vue](app/vue)|[Angular](app/angular)| [Polymer](app/polymer)| [Mithril](app/mithril)| [HTML](app/html)| [Marko](app/marko)| [Svelte](app/svelte)| [Riot](app/riot)| [Ember](app/ember)| [Preact](app/preact)|
| ----------- |:-------:|:-------:|:-------:|:-------:|:-------:|:-------:|:-------:|:-------:|:-------:|:-------:|:-------:|:-------:|
|[a11y](addons/a11y) |+| |+|+|+|+|+|+| | |+|+|
|[actions](addons/actions) |+|+|+|+|+|+|+|+|+|+|+|+|
|[actions](addons/actions) |+|+*|+|+|+|+|+|+|+|+|+|+|
|[backgrounds](addons/backgrounds) |+|*|+|+|+|+|+|+|+|+|+|+|
|[centered](addons/centered) |+| |+|+| |+|+| |+| |+|+|
|[contexts](addons/contexts) |+| |+| | | | | | | | |+|
Expand Down
1 change: 1 addition & 0 deletions addons/actions/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './constants';
export * from './models';
export * from './preview';

if (module && module.hot && module.hot.decline) {
Expand Down
32 changes: 32 additions & 0 deletions addons/ondevice-actions/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Storybook Actions Addon for react-native

Storybook Actions Addon allows you to log events/actions inside stories in [Storybook](https://storybook.js.org).

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

**This addon is a wrapper for addon [@storybook/addon-actions](https://github.com/storybooks/storybook/blob/master/addons/actions).
Refer to its documentation to understand how to use actions**

## Installation

```sh
yarn add -D @storybook/addon-ondevice-actions @storybook/addon-actions
```

## Configuration

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

Add following content to it:

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

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

```js
import './rn-addons';
```

See [@storybook/addon-actions](https://github.com/storybooks/storybook/blob/master/addons/actions) to learn how to write stories with actions and the [crna-kitchen-sink app](../../examples-native/crna-kitchen-sink) for more examples.
38 changes: 38 additions & 0 deletions addons/ondevice-actions/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"name": "@storybook/addon-ondevice-actions",
"version": "5.1.0-alpha.37",
"description": "Action Logger addon for react-native storybook",
"keywords": [
"storybook"
],
"homepage": "https://github.com/storybooks/storybook/tree/master/addons/actions",
"bugs": {
"url": "https://github.com/storybooks/storybook/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/storybooks/storybook.git"
},
"license": "MIT",
"main": "dist/index.js",
"scripts": {
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@storybook/addons": "5.1.0-alpha.37",
"@storybook/core-events": "5.1.0-alpha.37",
"core-js": "^2.5.7",
"fast-deep-equal": "^2.0.1"
},
"devDependencies": {
"@storybook/addon-actions": "5.1.0-alpha.37"
},
"peerDependencies": {
"@storybook/addon-actions": "*",
"react": "*",
"react-native": "*"
},
"publishConfig": {
"access": "public"
}
}
1 change: 1 addition & 0 deletions addons/ondevice-actions/register.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
require('./dist').register();
173 changes: 173 additions & 0 deletions addons/ondevice-actions/src/components/ActionLogger/Inspect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
import React from 'react';
import { Button, View, Text } from 'react-native';

const theme = {
OBJECT_PREVIEW_ARRAY_MAX_PROPERTIES: 10,
OBJECT_PREVIEW_OBJECT_MAX_PROPERTIES: 5,
OBJECT_NAME_COLOR: 'rgb(136, 19, 145)',
OBJECT_VALUE_NULL_COLOR: 'rgb(128, 128, 128)',
OBJECT_VALUE_UNDEFINED_COLOR: 'rgb(128, 128, 128)',
OBJECT_VALUE_REGEXP_COLOR: 'rgb(196, 26, 22)',
OBJECT_VALUE_STRING_COLOR: 'rgb(196, 26, 22)',
OBJECT_VALUE_SYMBOL_COLOR: 'rgb(196, 26, 22)',
OBJECT_VALUE_NUMBER_COLOR: 'rgb(28, 0, 207)',
OBJECT_VALUE_BOOLEAN_COLOR: 'rgb(28, 0, 207)',
OBJECT_VALUE_FUNCTION_PREFIX_COLOR: 'rgb(13, 34, 170)',

ARROW_COLOR: '#6e6e6e',
ARROW_MARGIN_RIGHT: 3,
ARROW_FONT_SIZE: 12,
ARROW_ANIMATION_DURATION: '0',
};

class Inspect extends React.Component<{ name?: string; value: any }, { expanded: boolean }> {
state = { expanded: false };
render() {
const { name, value } = this.props;
const { expanded } = this.state;
const toggle = (
<View style={{ width: 40, height: 40 }}>
{name &&
((Array.isArray(value) && value.length) ||
(value &&
typeof value === 'object' &&
!Array.isArray(value) &&
Object.keys(value).length)) ? (
<Button
onPress={() => this.setState(s => ({ expanded: !s.expanded }))}
title={!expanded ? '▶' : '▼'}
/>
) : null}
</View>
);

const nameComponent = name ? (
<Text style={{ color: theme.OBJECT_NAME_COLOR }}>{name}</Text>
) : null;

if (Array.isArray(value)) {
if (name) {
return (
<>
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
{toggle}
{nameComponent}
<Text>{': ' + (value.length === 0 ? '[]' : expanded ? '[' : '[...]')}</Text>
</View>
{expanded ? (
<View style={{ marginLeft: 40 }}>
{value.map((v, i) => (
<View key={i} style={{ marginLeft: 40 }}>
<Inspect value={v} />
</View>
))}
<View style={{ marginLeft: 20 }}>
<Text>{']'}</Text>
</View>
</View>
) : null}
</>
);
}
return (
<View>
<Text>{'['}</Text>
{value.map((v, i) => (
<View key={i} style={{ marginLeft: 20 }}>
<Inspect value={v} />
</View>
))}
<Text>{']'}</Text>
</View>
);
}
if (value && typeof value === 'object' && !(value instanceof RegExp)) {
if (name) {
return (
<>
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
{toggle}
{nameComponent}
<Text>
{': ' + (Object.keys(value).length === 0 ? '{}' : expanded ? '{' : '{...}')}
</Text>
</View>
{expanded ? (
<View style={{ marginLeft: 40 }}>
{Object.entries(value).map(([key, v]) => (
<View key={key}>
<Inspect name={key} value={v} />
</View>
))}
<View style={{ marginLeft: 20 }}>
<Text>{'}'}</Text>
</View>
</View>
) : null}
</>
);
}
return (
<View>
<Text>{'{'}</Text>
{Object.entries(value).map(([key, v]) => (
<View key={key}>
<Inspect name={key} value={v} />
</View>
))}
<Text>{'}'}</Text>
</View>
);
}
if (name) {
return (
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
{toggle}
{nameComponent}
<Text>{': '}</Text>
<Value value={value} />
</View>
);
}
return (
<View>
<Value value={value} />
</View>
);
}
}

function Value({ value }: { value: any }) {
if (value === null) {
return <Text style={{ color: theme.OBJECT_VALUE_NULL_COLOR }}>null</Text>;
}
if (value === undefined) {
return <Text style={{ color: theme.OBJECT_VALUE_UNDEFINED_COLOR }}>undefined</Text>;
}
if (value instanceof RegExp) {
return (
<Text style={{ color: theme.OBJECT_VALUE_REGEXP_COLOR }}>
{'/' + value.source + '/' + value.flags}
</Text>
);
}
switch (typeof value) {
case 'string':
return (
<Text style={{ color: theme.OBJECT_VALUE_STRING_COLOR }}>{JSON.stringify(value)}</Text>
);
case 'number':
return (
<Text style={{ color: theme.OBJECT_VALUE_NUMBER_COLOR }}>{JSON.stringify(value)}</Text>
);
case 'boolean':
return (
<Text style={{ color: theme.OBJECT_VALUE_BOOLEAN_COLOR }}>{JSON.stringify(value)}</Text>
);
case 'function':
return <Text style={{ color: theme.OBJECT_VALUE_FUNCTION_PREFIX_COLOR }}>[Function]</Text>;
}
return <Text>{JSON.stringify(value)}</Text>;
}

export default Inspect;
31 changes: 31 additions & 0 deletions addons/ondevice-actions/src/components/ActionLogger/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from 'react';
import { Button, View, Text, ScrollView } from 'react-native';
import { ActionDisplay } from '@storybook/addon-actions';
import Inspect from './Inspect';

interface ActionLoggerProps {
actions: ActionDisplay[];
onClear: () => void;
}

export const ActionLogger = ({ actions, onClear }: ActionLoggerProps) => (
<ScrollView>
<ScrollView horizontal>
<View>
{actions.map((action: ActionDisplay) => (
<View key={action.id} style={{ flexDirection: 'row' }}>
<View>{action.count > 1 ? <Text>{action.count}</Text> : null}</View>
<View style={{ flexGrow: 1 }}>
<Inspect name={action.data.name} value={action.data.args || action.data} />
</View>
</View>
))}
</View>
</ScrollView>
<View>
<Button onPress={onClear} title="CLEAR" />
</View>
</ScrollView>
);

export default ActionLogger;
79 changes: 79 additions & 0 deletions addons/ondevice-actions/src/containers/ActionLogger/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import React, { Component } from 'react';
import deepEqual from 'fast-deep-equal';

import { addons } from '@storybook/addons';
import { STORY_RENDERED } from '@storybook/core-events';
import { ActionDisplay, EVENT_ID } from '@storybook/addon-actions';

import { ActionLogger as ActionLoggerComponent } from '../../components/ActionLogger';

interface ActionLoggerProps {
active: boolean;
}

interface ActionLoggerState {
actions: ActionDisplay[];
}

const safeDeepEqual = (a: any, b: any): boolean => {
try {
return deepEqual(a, b);
} catch (e) {
return false;
}
};

export default class ActionLogger extends Component<ActionLoggerProps, ActionLoggerState> {
private channel = addons.getChannel();

constructor(props: ActionLoggerProps) {
super(props);

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

componentDidMount() {
this.channel.addListener(EVENT_ID, this.addAction);
this.channel.addListener(STORY_RENDERED, this.handleStoryChange);
}

componentWillUnmount() {
this.channel.removeListener(STORY_RENDERED, this.handleStoryChange);
this.channel.removeListener(EVENT_ID, this.addAction);
}

handleStoryChange = () => {
const { actions } = this.state;
if (actions.length > 0 && actions[0].options.clearOnStoryChange) {
this.clearActions();
}
};

addAction = (action: ActionDisplay) => {
this.setState((prevState: ActionLoggerState) => {
const actions = [...prevState.actions];
const previous = actions.length && actions[0];
if (previous && safeDeepEqual(previous.data, action.data)) {
previous.count++; // eslint-disable-line
} else {
action.count = 1; // eslint-disable-line
actions.unshift(action);
}
return { actions: actions.slice(0, action.options.limit) };
});
};

clearActions = () => {
this.setState({ actions: [] });
};

render() {
const { actions = [] } = this.state;
const { active } = this.props;
const props = {
actions,
onClear: this.clearActions,
};
return active ? <ActionLoggerComponent {...props} /> : null;
}
}
13 changes: 13 additions & 0 deletions addons/ondevice-actions/src/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React from 'react';
import addons from '@storybook/addons';
import { ADDON_ID, PANEL_ID } from '@storybook/addon-actions';
import ActionLogger from './containers/ActionLogger';

export function register() {
addons.register(ADDON_ID, () => {
addons.addPanel(PANEL_ID, {
title: 'Actions',
render: ({ active, key }) => <ActionLogger key={key} active={active} />,
});
});
}
8 changes: 8 additions & 0 deletions addons/ondevice-actions/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"rootDir": "./src"
},
"include": ["src/**/*"],
"exclude": ["src/__tests__/**/*"]
}
Loading

1 comment on commit c726632

@vercel
Copy link

@vercel vercel bot commented on c726632 May 2, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.