├── .babelrc ├── .gitignore ├── .npmignore ├── .scripts └── npm-prepublish.js ├── .storybook ├── addons.js ├── config.js └── stories.js ├── CHANGELOG.md ├── README.md ├── dist ├── components │ └── ActionLogger │ │ ├── index.js │ │ └── style.js ├── containers │ └── ActionLogger │ │ └── index.js ├── index.js ├── manager.js └── preview.js ├── docs └── screenshot.png ├── package.json ├── register.js └── src ├── components └── ActionLogger │ ├── index.js │ └── style.js ├── containers └── ActionLogger │ └── index.js ├── index.js ├── manager.js └── preview.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-0", "react"] 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .babelrc 3 | -------------------------------------------------------------------------------- /.scripts/npm-prepublish.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var shell = require('shelljs'); 3 | var babel = ['node_modules', '.bin', 'babel'].join(path.sep); 4 | 5 | shell.rm('-rf', 'dist') 6 | shell.exec(babel + ' --ignore __tests__ src --out-dir dist') 7 | -------------------------------------------------------------------------------- /.storybook/addons.js: -------------------------------------------------------------------------------- 1 | // Uncomment to register defaults 2 | // import '@kadira/storybook/addons'; 3 | 4 | // Use the line below to register this addon 5 | // import '@kadira/storybook-addon-actions/register'; 6 | import '../register'; 7 | -------------------------------------------------------------------------------- /.storybook/config.js: -------------------------------------------------------------------------------- 1 | import * as storybook from '@kadira/storybook'; 2 | storybook.configure(() => require('./stories'), module); 3 | -------------------------------------------------------------------------------- /.storybook/stories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { storiesOf } from '@kadira/storybook'; 3 | import { action, decorateAction } from '../src'; 4 | 5 | const pickFirst = decorateAction([ 6 | args => args.slice(0, 1) 7 | ]); 8 | 9 | storiesOf('Button', module) 10 | .add('Hello World', () => ( 11 | 12 | )) 13 | .add('Decorated Action', () => ( 14 | 15 | )) 16 | .add('Circular Payload', () => { 17 | const circular = {foo: {}}; 18 | circular.foo.circular = circular; 19 | return ; 23 | }) 24 | .add('Function Name', () => { 25 | const fn = action('fnName'); 26 | return 27 | }); 28 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Changelog 2 | 3 | ### v1.1.3 4 | 22 Mar 2017 5 | 6 | * Fix issue caused by `v1.1.2` where storybook crashes when actions are used without names [PR30](https://github.com/storybooks/storybook-addon-actions/pull/30). 7 | 8 | ### v1.1.2 9 | 21 Mar 2017 10 | 11 | * Document `decorateAction` function (action decorator). [PR28](https://github.com/storybooks/storybook-addon-actions/pull/28) 12 | 13 | * Set correct Function.name for action handlers [PR24](https://github.com/storybooks/storybook-addon-actions/pull/24) 14 | 15 | ### v1.1.1 16 | 11 Sep 2016 17 | 18 | * Support arguments with circular references [PR6](https://github.com/kadirahq/storybook-addon-actions/pull/6) 19 | 20 | ### v1.1.0 21 | 11 Sep 2016 22 | 23 | * Support action decorators [PR3](https://github.com/kadirahq/storybook-addon-actions/pull/3) 24 | 25 | ### v1.0.4 26 | 27 | * Refactor source code (use separate files) 28 | 29 | ### v1.0.3 30 | 31 | * Log all arguments (only the first argument was logged previously) 32 | 33 | ### v1.0.2 34 | 35 | * Style tweak: center counter and inspector elements 36 | 37 | ### v1.0.1 38 | 39 | * First stable release with all features from the storybook action logger such as `action` function, react inspector and grouping log messages. 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Storybook Addon Actions 2 | 3 | This contents of this repo was moved to the [Storybook monorepo](https://github.com/storybooks/storybook/) and the NPM package name has been changed. 4 | 5 | - The old name of the package was: **@kadira/storybook-addon-actions** 6 | - The new name of the package is: **@storybook/addon-actions** 7 | - The location of the code is: https://github.com/storybooks/storybook/tree/master/addons/actions 8 | 9 | The repo you're looking at now is out of date and no longer maintained. 10 | -------------------------------------------------------------------------------- /dist/components/ActionLogger/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 8 | 9 | var _react = require('react'); 10 | 11 | var _react2 = _interopRequireDefault(_react); 12 | 13 | var _reactInspector = require('react-inspector'); 14 | 15 | var _reactInspector2 = _interopRequireDefault(_reactInspector); 16 | 17 | var _style = require('./style'); 18 | 19 | var _style2 = _interopRequireDefault(_style); 20 | 21 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 22 | 23 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 24 | 25 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } 26 | 27 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 28 | 29 | var ActionLogger = function (_Component) { 30 | _inherits(ActionLogger, _Component); 31 | 32 | function ActionLogger() { 33 | _classCallCheck(this, ActionLogger); 34 | 35 | return _possibleConstructorReturn(this, (ActionLogger.__proto__ || Object.getPrototypeOf(ActionLogger)).apply(this, arguments)); 36 | } 37 | 38 | _createClass(ActionLogger, [{ 39 | key: 'componentDidUpdate', 40 | value: function componentDidUpdate() { 41 | var latest = this.refs.latest; 42 | if (latest) { 43 | var borderLeft = _style2.default.action.borderLeft; 44 | latest.style.borderLeft = 'solid 5px #aaa'; 45 | setTimeout(function () { 46 | latest.style.borderLeft = borderLeft; 47 | }, 300); 48 | } 49 | } 50 | }, { 51 | key: 'renderAction', 52 | value: function renderAction(action, i) { 53 | var ref = i ? '' : 'latest'; 54 | var counter = _react2.default.createElement( 55 | 'div', 56 | { style: _style2.default.counter }, 57 | action.count 58 | ); 59 | return _react2.default.createElement( 60 | 'div', 61 | { ref: ref, key: action.id, style: _style2.default.action }, 62 | _react2.default.createElement( 63 | 'div', 64 | { style: _style2.default.countwrap }, 65 | action.count > 1 && counter 66 | ), 67 | _react2.default.createElement( 68 | 'div', 69 | { style: _style2.default.inspector }, 70 | _react2.default.createElement(_reactInspector2.default, { 71 | showNonenumerable: true, 72 | name: action.data.name, 73 | data: action.data.args || action.data 74 | }) 75 | ) 76 | ); 77 | } 78 | }, { 79 | key: 'getActionData', 80 | value: function getActionData() { 81 | var _this2 = this; 82 | 83 | return this.props.actions.map(function (action, i) { 84 | return _this2.renderAction(action, i); 85 | }); 86 | } 87 | }, { 88 | key: 'render', 89 | value: function render() { 90 | return _react2.default.createElement( 91 | 'div', 92 | { style: _style2.default.wrapper }, 93 | _react2.default.createElement( 94 | 'pre', 95 | { style: _style2.default.actions }, 96 | this.getActionData() 97 | ), 98 | _react2.default.createElement( 99 | 'button', 100 | { style: _style2.default.button, onClick: this.props.onClear }, 101 | 'CLEAR' 102 | ) 103 | ); 104 | } 105 | }]); 106 | 107 | return ActionLogger; 108 | }(_react.Component); 109 | 110 | ActionLogger.propTypes = { 111 | onClear: _react2.default.PropTypes.func, 112 | actions: _react2.default.PropTypes.array 113 | }; 114 | 115 | exports.default = ActionLogger; -------------------------------------------------------------------------------- /dist/components/ActionLogger/style.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = { 7 | wrapper: { 8 | flex: 1, 9 | display: 'flex', 10 | position: 'relative' 11 | }, 12 | actions: { 13 | flex: 1, 14 | margin: 0, 15 | padding: '8px 2px 20px 0', 16 | overflowY: 'auto', 17 | color: '#666' 18 | }, 19 | action: { 20 | display: 'flex', 21 | padding: '3px 3px 3px 0', 22 | borderLeft: '5px solid white', 23 | borderBottom: '1px solid #fafafa', 24 | transition: 'all 0.1s', 25 | alignItems: 'center' 26 | }, 27 | countwrap: { 28 | paddingBottom: 2 29 | }, 30 | counter: { 31 | margin: '0 5px 0 5px', 32 | backgroundColor: '#777777', 33 | color: '#ffffff', 34 | padding: '1px 5px', 35 | borderRadius: '20px' 36 | }, 37 | inspector: { 38 | flex: 1, 39 | padding: '0 0 0 5px' 40 | }, 41 | button: { 42 | position: 'absolute', 43 | bottom: 0, right: 0, 44 | border: 'none', 45 | borderTop: 'solid 1px rgba(0, 0, 0, 0.2)', 46 | borderLeft: 'solid 1px rgba(0, 0, 0, 0.2)', 47 | background: 'rgba(255, 255, 255, 0.5)', 48 | padding: '5px 10px', 49 | borderRadius: '4px 0 0 0', 50 | color: 'rgba(0, 0, 0, 0.5)', 51 | outline: 'none' 52 | } 53 | }; -------------------------------------------------------------------------------- /dist/containers/ActionLogger/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 8 | 9 | var _react = require('react'); 10 | 11 | var _react2 = _interopRequireDefault(_react); 12 | 13 | var _deepEqual = require('deep-equal'); 14 | 15 | var _deepEqual2 = _interopRequireDefault(_deepEqual); 16 | 17 | var _ActionLogger = require('../../components/ActionLogger/'); 18 | 19 | var _ActionLogger2 = _interopRequireDefault(_ActionLogger); 20 | 21 | var _ = require('../../'); 22 | 23 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 24 | 25 | function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } 26 | 27 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 28 | 29 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } 30 | 31 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 32 | 33 | var ActionLogger = function (_React$Component) { 34 | _inherits(ActionLogger, _React$Component); 35 | 36 | function ActionLogger(props) { 37 | var _ref; 38 | 39 | _classCallCheck(this, ActionLogger); 40 | 41 | for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { 42 | args[_key - 1] = arguments[_key]; 43 | } 44 | 45 | var _this = _possibleConstructorReturn(this, (_ref = ActionLogger.__proto__ || Object.getPrototypeOf(ActionLogger)).call.apply(_ref, [this, props].concat(args))); 46 | 47 | _this.state = { actions: [] }; 48 | _this._actionListener = function (action) { 49 | return _this.addAction(action); 50 | }; 51 | return _this; 52 | } 53 | 54 | _createClass(ActionLogger, [{ 55 | key: 'addAction', 56 | value: function addAction(action) { 57 | action.data.args = action.data.args.map(function (arg) { 58 | return JSON.parse(arg); 59 | }); 60 | var actions = [].concat(_toConsumableArray(this.state.actions)); 61 | var previous = actions.length && actions[0]; 62 | if (previous && (0, _deepEqual2.default)(previous.data, action.data, { strict: true })) { 63 | previous.count++; 64 | } else { 65 | action.count = 1; 66 | actions.unshift(action); 67 | } 68 | this.setState({ actions: actions }); 69 | } 70 | }, { 71 | key: 'clearActions', 72 | value: function clearActions() { 73 | this.setState({ actions: [] }); 74 | } 75 | }, { 76 | key: 'componentDidMount', 77 | value: function componentDidMount() { 78 | this.props.channel.on(_.EVENT_ID, this._actionListener); 79 | } 80 | }, { 81 | key: 'componentWillUnmount', 82 | value: function componentWillUnmount() { 83 | this.props.channel.removeListener(_.EVENT_ID, this._actionListener); 84 | } 85 | }, { 86 | key: 'render', 87 | value: function render() { 88 | var _this2 = this; 89 | 90 | var props = { 91 | actions: this.state.actions, 92 | onClear: function onClear() { 93 | return _this2.clearActions(); 94 | } 95 | }; 96 | return _react2.default.createElement(_ActionLogger2.default, props); 97 | } 98 | }]); 99 | 100 | return ActionLogger; 101 | }(_react2.default.Component); 102 | 103 | exports.default = ActionLogger; -------------------------------------------------------------------------------- /dist/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _manager = require('./manager'); 8 | 9 | Object.defineProperty(exports, 'register', { 10 | enumerable: true, 11 | get: function get() { 12 | return _manager.register; 13 | } 14 | }); 15 | 16 | var _preview = require('./preview'); 17 | 18 | Object.defineProperty(exports, 'action', { 19 | enumerable: true, 20 | get: function get() { 21 | return _preview.action; 22 | } 23 | }); 24 | Object.defineProperty(exports, 'decorateAction', { 25 | enumerable: true, 26 | get: function get() { 27 | return _preview.decorateAction; 28 | } 29 | }); 30 | // addons, panels and events get unique names using a prefix 31 | var ADDON_ID = exports.ADDON_ID = 'kadirahq/storybook-addon-actions'; 32 | var PANEL_ID = exports.PANEL_ID = ADDON_ID + '/actions-panel'; 33 | var EVENT_ID = exports.EVENT_ID = ADDON_ID + '/action-event'; -------------------------------------------------------------------------------- /dist/manager.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.register = register; 7 | 8 | var _react = require('react'); 9 | 10 | var _react2 = _interopRequireDefault(_react); 11 | 12 | var _storybookAddons = require('@kadira/storybook-addons'); 13 | 14 | var _storybookAddons2 = _interopRequireDefault(_storybookAddons); 15 | 16 | var _ActionLogger = require('./containers/ActionLogger'); 17 | 18 | var _ActionLogger2 = _interopRequireDefault(_ActionLogger); 19 | 20 | var _ = require('./'); 21 | 22 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 23 | 24 | function register() { 25 | _storybookAddons2.default.register(_.ADDON_ID, function (api) { 26 | var channel = _storybookAddons2.default.getChannel(); 27 | _storybookAddons2.default.addPanel(_.PANEL_ID, { 28 | title: 'Action Logger', 29 | render: function render() { 30 | return _react2.default.createElement(_ActionLogger2.default, { channel: channel }); 31 | } 32 | }); 33 | }); 34 | } -------------------------------------------------------------------------------- /dist/preview.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.action = action; 7 | exports.decorateAction = decorateAction; 8 | 9 | var _storybookAddons = require('@kadira/storybook-addons'); 10 | 11 | var _storybookAddons2 = _interopRequireDefault(_storybookAddons); 12 | 13 | var _jsonStringifySafe = require('json-stringify-safe'); 14 | 15 | var _jsonStringifySafe2 = _interopRequireDefault(_jsonStringifySafe); 16 | 17 | var _ = require('./'); 18 | 19 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 20 | 21 | function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } 22 | 23 | function _format(arg) { 24 | if (arg && typeof arg.preventDefault !== 'undefined') { 25 | return (0, _jsonStringifySafe2.default)('[SyntheticEvent]'); 26 | } 27 | return (0, _jsonStringifySafe2.default)(arg); 28 | } 29 | 30 | function action(name) { 31 | 32 | var handler = function handler() { 33 | for (var _len = arguments.length, _args = Array(_len), _key = 0; _key < _len; _key++) { 34 | _args[_key] = arguments[_key]; 35 | } 36 | 37 | var args = Array.from(_args).map(_format); 38 | var channel = _storybookAddons2.default.getChannel(); 39 | var randomId = Math.random().toString(16).slice(2); 40 | channel.emit(_.EVENT_ID, { 41 | id: randomId, 42 | data: { name: name, args: args } 43 | }); 44 | }; 45 | 46 | // some day when {[name]: function() {}} syntax is not transpiled by babel 47 | // we can get rid of this eval as by ES2015 spec the above function gets the 48 | // name `name`, but babel transpiles to Object.defineProperty which doesn't do 49 | // the same. 50 | // 51 | // Ref: https://bocoup.com/weblog/whats-in-a-function-name 52 | var fnName = name ? name.replace(/\W+/g, '_') : 'action'; 53 | var named = eval('(function ' + fnName + '() { return handler.apply(this, arguments) })'); 54 | return named; 55 | } 56 | 57 | function decorateAction(decorators) { 58 | return function (name) { 59 | var callAction = action(name); 60 | return function () { 61 | for (var _len2 = arguments.length, _args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { 62 | _args[_key2] = arguments[_key2]; 63 | } 64 | 65 | var decorated = decorators.reduce(function (args, fn) { 66 | return fn(args); 67 | }, _args); 68 | callAction.apply(undefined, _toConsumableArray(decorated)); 69 | }; 70 | }; 71 | } -------------------------------------------------------------------------------- /docs/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/storybook-eol/storybook-addon-actions/13cd9a54e0c8f8a4894d4ad7372f6222e02a55b1/docs/screenshot.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@kadira/storybook-addon-actions", 3 | "version": "1.1.3", 4 | "description": "Action Logger addon for storybook", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "deploy-storybook": "storybook-to-ghpages", 8 | "prepublish": "node .scripts/npm-prepublish.js", 9 | "storybook": "start-storybook -p 9001", 10 | "test": "echo \"Error: no test specified\" && exit 0" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/kadirahq/storybook-addon-actions.git" 15 | }, 16 | "keywords": [ 17 | "storybook" 18 | ], 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/kadirahq/storybook-addon-actions/issues" 22 | }, 23 | "homepage": "https://github.com/kadirahq/storybook-addon-actions#readme", 24 | "devDependencies": { 25 | "@kadira/storybook": "^2.20.1", 26 | "@kadira/storybook-addons": "^1.3.0", 27 | "@kadira/storybook-ui": "^3.0.0", 28 | "babel-cli": "^6.11.4", 29 | "babel-preset-es2015": "^6.9.0", 30 | "babel-preset-react": "^6.11.1", 31 | "babel-preset-stage-0": "^6.5.0", 32 | "react": "^15.3.1", 33 | "react-dom": "^15.3.2", 34 | "shelljs": "^0.7.3" 35 | }, 36 | "dependencies": { 37 | "deep-equal": "^1.0.1", 38 | "json-stringify-safe": "^5.0.1", 39 | "react-inspector": "^1.1.0" 40 | }, 41 | "peerDependencies": { 42 | "@kadira/storybook-addons": "^1.3.0", 43 | "react": "^0.14.7 || ^15.0.0", 44 | "react-dom": "^0.14.7 || ^15.0.0" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /register.js: -------------------------------------------------------------------------------- 1 | require('./dist').register(); 2 | -------------------------------------------------------------------------------- /src/components/ActionLogger/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Inspector from 'react-inspector'; 3 | import style from './style'; 4 | 5 | class ActionLogger extends Component { 6 | componentDidUpdate() { 7 | const latest = this.refs.latest; 8 | if (latest) { 9 | const borderLeft = style.action.borderLeft; 10 | latest.style.borderLeft = 'solid 5px #aaa'; 11 | setTimeout(() => { 12 | latest.style.borderLeft = borderLeft; 13 | }, 300); 14 | } 15 | } 16 | 17 | renderAction(action, i) { 18 | const ref = i ? '' : 'latest'; 19 | const counter = ( 20 |
{action.count}
21 | ); 22 | return ( 23 |
24 |
25 | {action.count > 1 && counter} 26 |
27 |
28 | 33 |
34 |
35 | ); 36 | } 37 | 38 | getActionData() { 39 | return this.props.actions.map((action, i) => { 40 | return this.renderAction(action, i); 41 | }); 42 | } 43 | 44 | render() { 45 | return ( 46 |
47 |
{this.getActionData()}
48 | 49 |
50 | ); 51 | } 52 | } 53 | 54 | ActionLogger.propTypes = { 55 | onClear: React.PropTypes.func, 56 | actions: React.PropTypes.array, 57 | }; 58 | 59 | export default ActionLogger; 60 | -------------------------------------------------------------------------------- /src/components/ActionLogger/style.js: -------------------------------------------------------------------------------- 1 | export default { 2 | wrapper: { 3 | flex: 1, 4 | display: 'flex', 5 | position: 'relative', 6 | }, 7 | actions: { 8 | flex: 1, 9 | margin: 0, 10 | padding: '8px 2px 20px 0', 11 | overflowY: 'auto', 12 | color: '#666', 13 | }, 14 | action: { 15 | display: 'flex', 16 | padding: '3px 3px 3px 0', 17 | borderLeft: '5px solid white', 18 | borderBottom: '1px solid #fafafa', 19 | transition: 'all 0.1s', 20 | alignItems: 'center', 21 | }, 22 | countwrap: { 23 | paddingBottom: 2, 24 | }, 25 | counter: { 26 | margin: '0 5px 0 5px', 27 | backgroundColor: '#777777', 28 | color: '#ffffff', 29 | padding: '1px 5px', 30 | borderRadius: '20px', 31 | }, 32 | inspector: { 33 | flex: 1, 34 | padding: '0 0 0 5px', 35 | }, 36 | button: { 37 | position: 'absolute', 38 | bottom: 0, right: 0, 39 | border: 'none', 40 | borderTop: 'solid 1px rgba(0, 0, 0, 0.2)', 41 | borderLeft: 'solid 1px rgba(0, 0, 0, 0.2)', 42 | background: 'rgba(255, 255, 255, 0.5)', 43 | padding: '5px 10px', 44 | borderRadius: '4px 0 0 0', 45 | color: 'rgba(0, 0, 0, 0.5)', 46 | outline: 'none', 47 | }, 48 | } 49 | -------------------------------------------------------------------------------- /src/containers/ActionLogger/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import deepEqual from 'deep-equal'; 3 | import ActionLoggerComponent from '../../components/ActionLogger/'; 4 | import { EVENT_ID } from '../../'; 5 | 6 | export default class ActionLogger extends React.Component { 7 | constructor(props, ...args) { 8 | super(props, ...args); 9 | this.state = {actions: []}; 10 | this._actionListener = action => this.addAction(action); 11 | } 12 | 13 | addAction(action) { 14 | action.data.args = action.data.args.map(arg => JSON.parse(arg)); 15 | const actions = [...this.state.actions]; 16 | const previous = actions.length && actions[0]; 17 | if (previous && deepEqual(previous.data, action.data, { strict: true })) { 18 | previous.count++; 19 | } else { 20 | action.count = 1; 21 | actions.unshift(action); 22 | } 23 | this.setState({actions}); 24 | } 25 | 26 | clearActions() { 27 | this.setState({actions: []}); 28 | } 29 | 30 | componentDidMount() { 31 | this.props.channel.on(EVENT_ID, this._actionListener); 32 | } 33 | 34 | componentWillUnmount() { 35 | this.props.channel.removeListener(EVENT_ID, this._actionListener); 36 | } 37 | 38 | render() { 39 | const props = { 40 | actions: this.state.actions, 41 | onClear: () => this.clearActions(), 42 | }; 43 | return ; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | // addons, panels and events get unique names using a prefix 2 | export const ADDON_ID = 'kadirahq/storybook-addon-actions'; 3 | export const PANEL_ID = `${ADDON_ID}/actions-panel`; 4 | export const EVENT_ID = `${ADDON_ID}/action-event`; 5 | 6 | export { register } from './manager'; 7 | export { action, decorateAction } from './preview'; 8 | -------------------------------------------------------------------------------- /src/manager.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import addons from '@kadira/storybook-addons'; 3 | import ActionLogger from './containers/ActionLogger' 4 | import { ADDON_ID, PANEL_ID } from './'; 5 | 6 | export function register() { 7 | addons.register(ADDON_ID, api => { 8 | const channel = addons.getChannel(); 9 | addons.addPanel(PANEL_ID, { 10 | title: 'Action Logger', 11 | render: () => 12 | }); 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /src/preview.js: -------------------------------------------------------------------------------- 1 | import addons from '@kadira/storybook-addons'; 2 | import stringify from 'json-stringify-safe'; 3 | import { EVENT_ID } from './'; 4 | 5 | function _format(arg) { 6 | if (arg && typeof arg.preventDefault !== 'undefined') { 7 | return stringify('[SyntheticEvent]'); 8 | } 9 | return stringify(arg); 10 | } 11 | 12 | export function action(name) { 13 | 14 | const handler = function (..._args) { 15 | const args = Array.from(_args).map(_format); 16 | const channel = addons.getChannel(); 17 | const randomId = Math.random().toString(16).slice(2); 18 | channel.emit(EVENT_ID, { 19 | id: randomId, 20 | data: { name, args }, 21 | }); 22 | }; 23 | 24 | // some day when {[name]: function() {}} syntax is not transpiled by babel 25 | // we can get rid of this eval as by ES2015 spec the above function gets the 26 | // name `name`, but babel transpiles to Object.defineProperty which doesn't do 27 | // the same. 28 | // 29 | // Ref: https://bocoup.com/weblog/whats-in-a-function-name 30 | const fnName = name ? name.replace(/\W+/g,'_') : 'action'; 31 | const named = eval(`(function ${fnName}() { return handler.apply(this, arguments) })`) 32 | return named 33 | } 34 | 35 | export function decorateAction(decorators) { 36 | return function (name) { 37 | const callAction = action(name); 38 | return function (..._args) { 39 | const decorated = decorators.reduce((args, fn) => fn(args), _args); 40 | callAction(...decorated); 41 | }; 42 | }; 43 | } 44 | --------------------------------------------------------------------------------