Merge branch 'master' of github.com:storybooks/storybook-addon-knobs

# Conflicts:
#	.gitignore
This commit is contained in:
Norbert de Langen 2017-03-29 21:19:42 +02:00
commit e50269fd96
68 changed files with 9288 additions and 0 deletions

3
.babelrc Normal file
View File

@ -0,0 +1,3 @@
{
"presets": ["es2015", "stage-2", "react"]
}

17
.eslintrc Normal file
View File

@ -0,0 +1,17 @@
{
"extends": "airbnb",
"rules": {
"arrow-body-style": 0,
"prefer-arrow-callback": 0,
"func-names": 0,
"react/jsx-no-bind": 0,
"react/jsx-uses-react": 1,
"react/prefer-stateless-function": 0
},
"parserOptions": {
"ecmaVersion": 6,
"ecmaFeatures": {
"experimentalObjectRestSpread": true
}
}
}

1
.gitignore vendored
View File

@ -3,3 +3,4 @@ node_modules
.idea
npm-shrinkwrap.json
dist
.tern-port

2
.npmignore Normal file
View File

@ -0,0 +1,2 @@
src
.babelrc

View File

@ -0,0 +1,11 @@
// IMPORTANT
// ---------
// This is an auto generated file with React CDK.
// Do not modify this file.
const parse = require('git-url-parse');
var ghUrl = process.argv[2];
const parsedUrl = parse(ghUrl);
const ghPagesUrl = 'https://' + parsedUrl.owner + '.github.io/' + parsedUrl.name;
console.log(ghPagesUrl);

33
.scripts/mocha_runner.js Normal file
View File

@ -0,0 +1,33 @@
// IMPORTANT
// ---------
// This is an auto generated file with React CDK.
// Do not modify this file.
// Use `.scripts/user/pretest.js instead`.
require('babel-core/register');
require('babel-polyfill');
// Add jsdom support, which is required for enzyme.
var jsdom = require('jsdom').jsdom;
var exposedProperties = ['window', 'navigator', 'document'];
global.document = jsdom('');
global.window = document.defaultView;
Object.keys(document.defaultView).forEach((property) => {
if (typeof global[property] === 'undefined') {
exposedProperties.push(property);
global[property] = document.defaultView[property];
}
});
global.navigator = {
userAgent: 'node.js'
};
process.on('unhandledRejection', function (error) {
console.error('Unhandled Promise Rejection:');
console.error(error && error.stack || error);
});
require('./user/pretest.js');

16
.scripts/prepublish.sh Normal file
View File

@ -0,0 +1,16 @@
#!/bin/bash
# IMPORTANT
# ---------
# This is an auto generated file with React CDK.
# Do not modify this file.
# Use `.scripts/user/prepublish.sh instead`.
echo "=> Transpiling 'src' into ES5 ..."
echo ""
rm -rf ./dist
./node_modules/.bin/babel --ignore tests,stories --plugins "transform-runtime" ./src --out-dir ./dist
echo ""
echo "=> Transpiling completed."
. .scripts/user/prepublish.sh

View File

@ -0,0 +1,47 @@
#!/bin/bash
# IMPORTANT
# ---------
# This is an auto generated file with React CDK.
# Do not modify this file.
set -e # exit with nonzero exit code if anything fails
# get GIT url
GIT_URL=`git config --get remote.origin.url`
if [[ $GIT_URL == "" ]]; then
echo "This project is not configured with a remote git repo".
exit 1
fi
# clear and re-create the out directory
rm -rf .out || exit 0;
mkdir .out;
# run our compile script, discussed above
build-storybook -o .out
# go to the out directory and create a *new* Git repo
cd .out
git init
# inside this git repo we'll pretend to be a new user
git config user.name "GH Pages Bot"
git config user.email "hello@ghbot.com"
# The first and only commit to this new Git repo contains all the
# files present with the commit message "Deploy to GitHub Pages".
git add .
git commit -m "Deploy Storybook to GitHub Pages"
# Force push from the current repo's master branch to the remote
# repo's gh-pages branch. (All previous history on the gh-pages branch
# will be lost, since we are overwriting it.) We redirect any output to
# /dev/null to hide any sensitive credential data that might otherwise be exposed.
git push --force --quiet $GIT_URL master:gh-pages > /dev/null 2>&1
cd ..
rm -rf .out
echo ""
echo "=> Storybook deployed to: `node .scripts/get_gh_pages_url.js $GIT_URL`"

View File

@ -0,0 +1 @@
# Use this file to your own code to run at NPM `prepublish` event.

1
.scripts/user/pretest.js Normal file
View File

@ -0,0 +1 @@
// Use this file to setup any test utilities.

2
.storybook/addons.js Normal file
View File

@ -0,0 +1,2 @@
import '../src/register';
import '@kadira/storybook/addons'

12
.storybook/config.js Normal file
View File

@ -0,0 +1,12 @@
// IMPORTANT
// ---------
// This is an auto generated file with React CDK.
// Do not modify this file.
import { configure } from '@kadira/storybook';
function loadStories() {
require('../example/stories');
}
configure(loadStories, module);

View File

@ -0,0 +1,4 @@
module.exports = function (config) {
// This is the default webpack config defined in the `../webpack.config.js`
// modify as you need.
};

View File

@ -0,0 +1,23 @@
// IMPORTANT
// ---------
// This is an auto generated file with React CDK.
// Do not modify this file.
// Use `.storybook/user/modify_webpack_config.js instead`.
const path = require('path');
const updateConfig = require('./user/modify_webpack_config');
const config = {
module: {
loaders: [
{
test: /\.css?$/,
loaders: ['style', 'raw'],
include: path.resolve(__dirname, '../'),
},
],
},
};
updateConfig(config);
module.exports = config;

88
CHANGELOG.md Normal file
View File

@ -0,0 +1,88 @@
# Changelog
### v1.7.1
01-December-2016
Update style changes in the panel. [PR79](https://github.com/storybooks/storybook-addon-knobs/pull/79)
### v1.7.0
25-November-2016
Modify number() to support a range slider as an input type for a knob.[PR77](https://github.com/storybooks/storybook-addon-knobs/pull/77)
### v1.6.0
19-November-2016
Add Color as a type. [PR75](https://github.com/storybooks/storybook-addon-knobs/pull/75)
### v1.5.0
8-November-2016
Add Array as a type. [PR70](https://github.com/storybooks/storybook-addon-knobs/pull/70).
### v1.4.1
27-October-2016
Fix some issues related to typescript declaration. [PR69](Typescript declaration updates)
### v1.4.0
27-October-2016
Add TypeScript support [PR65](https://github.com/kadirahq/storybook-addon-knobs/pull/65).
### v1.3.3
13-October-2016
Fix issue where hot reloaded updates were not rendered [PR62](https://github.com/kadirahq/storybook-addon-knobs/pull/62).
### v1.3.2
13-October-2016
Make the render function of WrapStory pure [PR61](https://github.com/kadirahq/storybook-addon-knobs/pull/61).
### v1.3.1
13-October-2016
Show or hide knob from panel depending on whether it was called in the story or not [PR59](https://github.com/kadirahq/storybook-addon-knobs/pull/59).
### v1.3.0
05-October-2016
Accept null values for the date type. [PR56](https://github.com/kadirahq/storybook-addon-knobs/pull/56).
### v1.2.3
28-September-2016
Remove setting selected prop in options in Select knobs [PR47](https://github.com/kadirahq/storybook-addon-knobs/pull/47)
### v1.2.2
25-September-2016
Fix copy-paste error [PR37](https://github.com/kadirahq/storybook-addon-knobs/pull/37)
Remove <div> wrapper from WrapStory [PR42](https://github.com/kadirahq/storybook-addon-knobs/pull/42)
### v1.2.1
20-September-2016
Support to use date type without a default value. [PR32](https://github.com/kadirahq/storybook-addon-knobs/pull/32)
### v1.2.0
19-September-2016
Add a knob to get date from the user. [PR26](https://github.com/kadirahq/storybook-addon-knobs/pull/26)
### v1.1.0
11-September-2016
Implement the select knob. [PR21](https://github.com/kadirahq/storybook-addon-knobs/pull/21)
### v1.0.1
09-September-2016
Allow user to write JSON in the Object knob freely.
Earlier, it's hard to add new fields without creating a JSON error.
### v1.0.0
09-September-2016
Initial Release.

24
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,24 @@
# Contributing to Storybook Knobs
We welcome your help to make this component better. This document will help to streamline the contributing process and save everyone's precious time.
## Development Setup
This component has been setup with [React CDK](https://github.com/kadirahq/react-cdk). Refer [React CDK documentation](https://github.com/kadirahq/react-cdk)) to get started with the development.
## Setting Up
You can start the built-in storybook and play with this addon. Do this for that:
```sh
npm i
npm run storybook
```
Storybook lives inside the `example` directory and source code lives in `src` directory.
## Adding a new knob
Adding a new knob is pretty easy. First you need to add the story side API to the `src/index.js`.
Then you need to write the UI component to show/edit this knob inside `src/types` directory. Have a look at existing types for more information.

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 Kadira Inc. <hello@kadira.io>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

212
README.md Normal file
View File

@ -0,0 +1,212 @@
# Storybook Addon Knobs [![CircleCI](https://circleci.com/gh/storybooks/storybook-addon-knobs.svg?style=shield)](https://circleci.com/gh/storybooks/storybook-addon-knobs)
Knobs allow you to edit React props dynamically using the Storybook UI.
You can also use Knobs as a dynamic variable inside stories.
This is how Knobs look like:
[![Storybook Knobs Demo](docs/storybook-knobs-example.png)](https://git.io/vXdhZ)
> Checkout the above [Live Storybook](https://git.io/vXdhZ) or [watch this video](https://www.youtube.com/watch?v=kopW6vzs9dg&feature=youtu.be).
## Getting Started
First of all, you need to install knobs into your project as a dev dependency.
```js
npm i -D @kadira/storybook-addon-knobs
```
Then, configure it as an addon by adding it to your `addons.js` file (located in the Storybook config directory).
```js
// To get our default addons (actions and links)
import '@kadira/storybook/addons';
// To add the knobs addon
import '@kadira/storybook-addon-knobs/register'
```
Now, write your stories with knobs.
```js
import React from 'react';
import { storiesOf } from '@kadira/storybook';
import { withKnobs, text, boolean, number } from '@kadira/storybook-addon-knobs';
const stories = storiesOf('Storybook Knobs', module);
// Add the `withKnobs` decorator to add knobs support to your stories.
// You can also configure `withKnobs` as a global decorator.
stories.addDecorator(withKnobs);
// Knobs for React props
stories.add('with a button', () => (
<button
disabled={boolean('Disabled', false)}
>
{text('Label', 'Hello Button')}
</button>
))
// Knobs as dynamic variables.
stories.add('as dynamic variables', () => {
const name = text('Name', 'Arunoda Susiripala');
const age = number('Age', 89);
const content = `I am ${name} and I'm ${age} years old.`;
return (<div>{content}</div>);
});
```
You can see your Knobs in a Storybook panel as shown below.
![](docs/demo.png)
### Additional Links
* Introduction blog post.
* Watch this video on how to use knobs
* [Live Storybook with Knobs](https://goo.gl/uX9WLf)
* Have a look at this [sample Storybook repo](https://github.com/kadira-samples/storybook-knobs-example).
## Available Knobs
These are the knobs available for you to use. You can import these Knobs from the `@kadira/storybook-addon-knobs` module.
Here's how to import the **text** Knob.
```js
import { text } from '@kadira/storybook-addon-knobs';
```
Just like that, you can import any other following Knobs:
### text
Allows you to get some text from the user.
```js
const label = 'Your Name';
const defaultValue = 'Arunoda Susiripala';
const value = text(label, defaultValue);
```
### boolean
Allows you to get a boolean value from the user.
```js
const label = 'Agree?';
const defaultValue = false;
const value = boolean(label, defaultValue);
```
### number
Allows you to get a number from the user.
```js
const label = 'Age';
const defaultValue = 78;
const value = number(label, defaultValue);
```
### number bound by range
Allows you to get a number from the user using a range slider.
```js
const label = 'Temperature';
const defaultValue = 73;
const options = {
range: true,
min: 60,
max: 90,
step: 1,
};
const value = number(label, defaultValue, options);
```
### color
Allows you to get a color from the user.
```js
const label = 'Color';
const defaultValue = '#ff00ff';
const value = color(label, defaultValue);
```
### object
Allows you to get a JSON object from the user.
```js
const label = 'Styles';
const defaultValue = {
backgroundColor: 'red'
};
const value = object(label, defaultValue);
```
> Make sure to enter valid JSON syntax while editing values inside the knob.
### array
Allows you to get an array from the user.
```js
const label = 'Styles';
const defaultValue = ['Red']
const value = array(label, defaultValue);
```
> While editing values inside the knob, you will need to use a separator. By default it's a comma, but this can be
override by passing a separator variable.
> ```js
> const separator = ':';
> const value = array(label, defaultValue, separator);
> ```
### select
Allows you to get a value from a select box from the user.
```js
const label = 'Colors';
const options = {
red: 'Red',
blue: 'Blue',
yellow: 'Yellow',
};
const defaultValue = 'red';
const value = select(label, options, defaultValue);
```
> You can also provide options as an array like this: ['red', 'blue', 'yellow']
### date
Allow you to get date (and time) from the user.
```js
const label = 'Event Date';
const defaultValue = new Date('Jan 20 2017');
const value = date(label, defaultValue);
```
## Typescript
If you are using typescript, make sure you have the type definitions installed for the following libs:
- node
- react
You can install them using `npm i -S @types/node @types/react`, assuming you are using Typescript >2.0.

117
dist/KnobManager.js vendored Normal file
View File

@ -0,0 +1,117 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _extends2 = require('babel-runtime/helpers/extends');
var _extends3 = _interopRequireDefault(_extends2);
var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
var _createClass2 = require('babel-runtime/helpers/createClass');
var _createClass3 = _interopRequireDefault(_createClass2);
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _WrapStory = require('./components/WrapStory');
var _WrapStory2 = _interopRequireDefault(_WrapStory);
var _KnobStore = require('./KnobStore');
var _KnobStore2 = _interopRequireDefault(_KnobStore);
var _deepEqual = require('deep-equal');
var _deepEqual2 = _interopRequireDefault(_deepEqual);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
// This is used by _mayCallChannel to determine how long to wait to before triggering a panel update
var PANEL_UPDATE_INTERVAL = 400;
var KnobManager = function () {
function KnobManager() {
(0, _classCallCheck3.default)(this, KnobManager);
this.knobStore = null;
this.knobStoreMap = {};
}
(0, _createClass3.default)(KnobManager, [{
key: 'knob',
value: function knob(name, options) {
this._mayCallChannel();
var knobStore = this.knobStore;
var existingKnob = knobStore.get(name);
// We need to return the value set by the knob editor via this.
// But, if the user changes the code for the defaultValue we should set
// that value instead.
if (existingKnob && (0, _deepEqual2.default)(options.value, existingKnob.defaultValue)) {
return existingKnob.value;
}
var defaultValue = options.value;
var knobInfo = (0, _extends3.default)({}, options, {
name: name,
defaultValue: defaultValue
});
knobStore.set(name, knobInfo);
return knobStore.get(name).value;
}
}, {
key: 'wrapStory',
value: function wrapStory(channel, storyFn, context) {
this.channel = channel;
var key = context.kind + ':::' + context.story;
var knobStore = this.knobStoreMap[key];
if (!knobStore) {
knobStore = this.knobStoreMap[key] = new _KnobStore2.default();
}
this.knobStore = knobStore;
knobStore.markAllUnused();
var initialContent = storyFn(context);
var props = { context: context, storyFn: storyFn, channel: channel, knobStore: knobStore, initialContent: initialContent };
return _react2.default.createElement(_WrapStory2.default, props);
}
}, {
key: '_mayCallChannel',
value: function _mayCallChannel() {
var _this = this;
// Re rendering of the story may cause changes to the knobStore. Some new knobs maybe added and
// Some knobs may go unused. So we need to update the panel accordingly. For example remove the
// unused knobs from the panel. This function sends the `setKnobs` message to the channel
// triggering a panel re-render.
if (this.calling) {
// If a call to channel has already registered ignore this call.
// Once the previous call is completed all the changes to knobStore including the one that
// triggered this, will be added to the panel.
// This avoids emitting to the channel within very short periods of time.
return;
}
this.calling = true;
setTimeout(function () {
_this.calling = false;
// emit to the channel and trigger a panel re-render
_this.channel.emit('addon:knobs:setKnobs', _this.knobStore.getAll());
}, PANEL_UPDATE_INTERVAL);
}
}]);
return KnobManager;
}();
exports.default = KnobManager;

86
dist/KnobStore.js vendored Normal file
View File

@ -0,0 +1,86 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var _keys = require("babel-runtime/core-js/object/keys");
var _keys2 = _interopRequireDefault(_keys);
var _classCallCheck2 = require("babel-runtime/helpers/classCallCheck");
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
var _createClass2 = require("babel-runtime/helpers/createClass");
var _createClass3 = _interopRequireDefault(_createClass2);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var KnobStore = function () {
function KnobStore() {
(0, _classCallCheck3.default)(this, KnobStore);
this.store = {};
this.callbacks = [];
}
(0, _createClass3.default)(KnobStore, [{
key: "has",
value: function has(key) {
return this.store[key] !== undefined;
}
}, {
key: "set",
value: function set(key, value) {
this.store[key] = value;
this.store[key].used = true;
this.callbacks.forEach(function (cb) {
return cb();
});
}
}, {
key: "get",
value: function get(key) {
var knob = this.store[key];
if (knob) {
knob.used = true;
}
return knob;
}
}, {
key: "getAll",
value: function getAll() {
return this.store;
}
}, {
key: "reset",
value: function reset() {
this.store = {};
}
}, {
key: "markAllUnused",
value: function markAllUnused() {
var _this = this;
(0, _keys2.default)(this.store).forEach(function (knobName) {
_this.store[knobName].used = false;
});
}
}, {
key: "subscribe",
value: function subscribe(cb) {
this.callbacks.push(cb);
}
}, {
key: "unsubscribe",
value: function unsubscribe(cb) {
var index = this.callbacks.indexOf(cb);
this.callbacks.splice(index, 1);
}
}]);
return KnobStore;
}();
exports.default = KnobStore;

207
dist/components/Panel.js vendored Normal file
View File

@ -0,0 +1,207 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _extends2 = require('babel-runtime/helpers/extends');
var _extends3 = _interopRequireDefault(_extends2);
var _keys = require('babel-runtime/core-js/object/keys');
var _keys2 = _interopRequireDefault(_keys);
var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');
var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
var _createClass2 = require('babel-runtime/helpers/createClass');
var _createClass3 = _interopRequireDefault(_createClass2);
var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');
var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
var _inherits2 = require('babel-runtime/helpers/inherits');
var _inherits3 = _interopRequireDefault(_inherits2);
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _PropForm = require('./PropForm');
var _PropForm2 = _interopRequireDefault(_PropForm);
var _types = require('./types');
var _types2 = _interopRequireDefault(_types);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var styles = {
panelWrapper: {
width: '100%'
},
panel: {
padding: '5px',
width: 'auto',
position: 'relative'
},
noKnobs: {
fontFamily: '\n -apple-system, ".SFNSText-Regular", "San Francisco", "Roboto",\n "Segoe UI", "Helvetica Neue", "Lucida Grande", sans-serif\n ',
display: 'inline',
width: '100%',
textAlign: 'center',
color: 'rgb(190, 190, 190)',
padding: '10px'
},
resetButton: {
position: 'absolute',
bottom: 11,
right: 10,
border: 'none',
borderTop: 'solid 1px rgba(0, 0, 0, 0.2)',
borderLeft: 'solid 1px rgba(0, 0, 0, 0.2)',
background: 'rgba(255, 255, 255, 0.5)',
padding: '5px 10px',
borderRadius: '4px 0 0 0',
color: 'rgba(0, 0, 0, 0.5)',
outline: 'none'
}
};
var Panel = function (_React$Component) {
(0, _inherits3.default)(Panel, _React$Component);
function Panel(props) {
(0, _classCallCheck3.default)(this, Panel);
var _this = (0, _possibleConstructorReturn3.default)(this, (Panel.__proto__ || (0, _getPrototypeOf2.default)(Panel)).call(this, props));
_this.handleChange = _this.handleChange.bind(_this);
_this.setKnobs = _this.setKnobs.bind(_this);
_this.reset = _this.reset.bind(_this);
_this.state = { knobs: {} };
_this.loadedFromUrl = false;
_this.props.channel.on('addon:knobs:setKnobs', _this.setKnobs);
return _this;
}
(0, _createClass3.default)(Panel, [{
key: 'componentWillUnmount',
value: function componentWillUnmount() {
this.props.channel.removeListener('addon:knobs:setKnobs', this.setKnobs);
}
}, {
key: 'setKnobs',
value: function setKnobs(knobs) {
var _this2 = this;
var queryParams = {};
var _props = this.props,
api = _props.api,
channel = _props.channel;
(0, _keys2.default)(knobs).forEach(function (name) {
var knob = knobs[name];
// For the first time, get values from the URL and set them.
if (!_this2.loadedFromUrl) {
var urlValue = api.getQueryParam('knob-' + name);
if (urlValue !== undefined) {
// If the knob value present in url
knob.value = _types2.default[knob.type].deserialize(urlValue);
channel.emit('addon:knobs:knobChange', knob);
}
}
queryParams['knob-' + name] = _types2.default[knob.type].serialize(knob.value);
});
this.loadedFromUrl = true;
api.setQueryParams(queryParams);
this.setState({ knobs: knobs });
}
}, {
key: 'reset',
value: function reset() {
this.props.channel.emit('addon:knobs:reset');
}
}, {
key: 'handleChange',
value: function handleChange(changedKnob) {
var _props2 = this.props,
api = _props2.api,
channel = _props2.channel;
var knobs = this.state.knobs;
var name = changedKnob.name,
type = changedKnob.type,
value = changedKnob.value;
var newKnobs = (0, _extends3.default)({}, knobs);
newKnobs[name] = (0, _extends3.default)({}, newKnobs[name], changedKnob);
this.setState({ knobs: newKnobs });
var queryParams = {};
queryParams['knob-' + name] = _types2.default[type].serialize(value);
api.setQueryParams(queryParams);
channel.emit('addon:knobs:knobChange', changedKnob);
}
}, {
key: 'render',
value: function render() {
var knobs = this.state.knobs;
var knobsArray = (0, _keys2.default)(knobs).filter(function (key) {
return knobs[key].used;
}).map(function (key) {
return knobs[key];
});
if (knobsArray.length === 0) {
return _react2.default.createElement(
'div',
{ style: styles.noKnobs },
'NO KNOBS'
);
}
return _react2.default.createElement(
'div',
{ style: styles.panelWrapper },
_react2.default.createElement(
'div',
{ style: styles.panel },
_react2.default.createElement(_PropForm2.default, { knobs: knobsArray, onFieldChange: this.handleChange })
),
_react2.default.createElement(
'button',
{ style: styles.resetButton, onClick: this.reset },
'RESET'
)
);
}
}]);
return Panel;
}(_react2.default.Component);
exports.default = Panel;
Panel.propTypes = {
channel: _react2.default.PropTypes.object,
onReset: _react2.default.PropTypes.object,
api: _react2.default.PropTypes.object
};

128
dist/components/PropField.js vendored Normal file
View File

@ -0,0 +1,128 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');
var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
var _createClass2 = require('babel-runtime/helpers/createClass');
var _createClass3 = _interopRequireDefault(_createClass2);
var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');
var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
var _inherits2 = require('babel-runtime/helpers/inherits');
var _inherits3 = _interopRequireDefault(_inherits2);
var _extends2 = require('babel-runtime/helpers/extends');
var _extends3 = _interopRequireDefault(_extends2);
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _types = require('./types');
var _types2 = _interopRequireDefault(_types);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var InvalidType = function InvalidType() {
return _react2.default.createElement(
'span',
null,
'Invalid Type'
);
};
var stylesheet = {
field: {
display: 'table-row',
padding: '5px'
},
label: {
display: 'table-cell',
boxSizing: 'border-box',
verticalAlign: 'top',
paddingRight: 5,
paddingTop: 7,
textAlign: 'right',
width: 80,
fontSize: 10,
color: 'rgb(68, 68, 68)',
textTransform: 'uppercase',
fontWeight: 600
}
};
stylesheet.textarea = (0, _extends3.default)({}, stylesheet.input, {
height: '100px'
});
stylesheet.checkbox = (0, _extends3.default)({}, stylesheet.input, {
width: 'auto'
});
var PropField = function (_React$Component) {
(0, _inherits3.default)(PropField, _React$Component);
function PropField(props) {
(0, _classCallCheck3.default)(this, PropField);
var _this = (0, _possibleConstructorReturn3.default)(this, (PropField.__proto__ || (0, _getPrototypeOf2.default)(PropField)).call(this, props));
_this._onChange = _this.onChange.bind(_this);
return _this;
}
(0, _createClass3.default)(PropField, [{
key: 'onChange',
value: function onChange(e) {
this.props.onChange(e.target.value);
}
}, {
key: 'render',
value: function render() {
var _props = this.props,
onChange = _props.onChange,
knob = _props.knob;
var InputType = _types2.default[knob.type] || InvalidType;
return _react2.default.createElement(
'div',
{ style: stylesheet.field },
_react2.default.createElement(
'label',
{ htmlFor: knob.name, style: stylesheet.label },
'' + knob.name
),
_react2.default.createElement(InputType, {
knob: knob,
onChange: onChange
})
);
}
}]);
return PropField;
}(_react2.default.Component);
exports.default = PropField;
PropField.propTypes = {
onChange: _react2.default.PropTypes.func.isRequired,
knob: _react2.default.PropTypes.object
};

100
dist/components/PropForm.js vendored Normal file
View File

@ -0,0 +1,100 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');
var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
var _createClass2 = require('babel-runtime/helpers/createClass');
var _createClass3 = _interopRequireDefault(_createClass2);
var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');
var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
var _inherits2 = require('babel-runtime/helpers/inherits');
var _inherits3 = _interopRequireDefault(_inherits2);
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _PropField = require('./PropField');
var _PropField2 = _interopRequireDefault(_PropField);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var stylesheet = {
propForm: {
fontFamily: '\n -apple-system, ".SFNSText-Regular", "San Francisco", "Roboto",\n "Segoe UI", "Helvetica Neue", "Lucida Grande", sans-serif\n ',
display: 'table',
boxSizing: 'border-box',
width: '100%',
borderCollapse: 'separate',
borderSpacing: '5px'
}
};
var propForm = function (_React$Component) {
(0, _inherits3.default)(propForm, _React$Component);
function propForm() {
(0, _classCallCheck3.default)(this, propForm);
var _this = (0, _possibleConstructorReturn3.default)(this, (propForm.__proto__ || (0, _getPrototypeOf2.default)(propForm)).call(this));
_this._onFieldChange = _this.onFieldChange.bind(_this);
return _this;
}
(0, _createClass3.default)(propForm, [{
key: 'onFieldChange',
value: function onFieldChange(name, type, value) {
var change = { name: name, type: type, value: value };
this.props.onFieldChange(change);
}
}, {
key: 'render',
value: function render() {
var _this2 = this;
var knobs = this.props.knobs;
return _react2.default.createElement(
'form',
{ style: stylesheet.propForm },
knobs.map(function (knob) {
return _react2.default.createElement(_PropField2.default, {
key: knob.name,
name: knob.name,
type: knob.type,
value: knob.value,
knob: knob,
onChange: _this2._onFieldChange.bind(null, knob.name, knob.type)
});
})
);
}
}]);
return propForm;
}(_react2.default.Component);
exports.default = propForm;
propForm.displayName = 'propForm';
propForm.propTypes = {
knobs: _react2.default.PropTypes.array.isRequired,
onFieldChange: _react2.default.PropTypes.func.isRequired
};

129
dist/components/WrapStory.js vendored Normal file
View File

@ -0,0 +1,129 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');
var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
var _createClass2 = require('babel-runtime/helpers/createClass');
var _createClass3 = _interopRequireDefault(_createClass2);
var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');
var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
var _inherits2 = require('babel-runtime/helpers/inherits');
var _inherits3 = _interopRequireDefault(_inherits2);
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var WrapStory = function (_React$Component) {
(0, _inherits3.default)(WrapStory, _React$Component);
function WrapStory(props) {
(0, _classCallCheck3.default)(this, WrapStory);
var _this = (0, _possibleConstructorReturn3.default)(this, (WrapStory.__proto__ || (0, _getPrototypeOf2.default)(WrapStory)).call(this, props));
_this.knobChanged = _this.knobChanged.bind(_this);
_this.resetKnobs = _this.resetKnobs.bind(_this);
_this.setPaneKnobs = _this.setPaneKnobs.bind(_this);
_this._knobsAreReset = false;
_this.state = { storyContent: _this.props.initialContent };
return _this;
}
(0, _createClass3.default)(WrapStory, [{
key: 'componentDidMount',
value: function componentDidMount() {
// Watch for changes in knob editor.
this.props.channel.on('addon:knobs:knobChange', this.knobChanged);
// Watch for the reset event and reset knobs.
this.props.channel.on('addon:knobs:reset', this.resetKnobs);
// Watch for any change in the knobStore and set the panel again for those
// changes.
this.props.knobStore.subscribe(this.setPaneKnobs);
// Set knobs in the panel for the first time.
this.setPaneKnobs();
}
}, {
key: 'componentWillReceiveProps',
value: function componentWillReceiveProps(props) {
this.setState({ storyContent: props.initialContent });
}
}, {
key: 'componentWillUnmount',
value: function componentWillUnmount() {
this.props.channel.removeListener('addon:knobs:knobChange', this.knobChanged);
this.props.channel.removeListener('addon:knobs:reset', this.resetKnobs);
this.props.knobStore.unsubscribe(this.setPaneKnobs);
}
}, {
key: 'setPaneKnobs',
value: function setPaneKnobs() {
var _props = this.props,
channel = _props.channel,
knobStore = _props.knobStore;
channel.emit('addon:knobs:setKnobs', knobStore.getAll());
}
}, {
key: 'knobChanged',
value: function knobChanged(change) {
var name = change.name,
value = change.value;
var _props2 = this.props,
knobStore = _props2.knobStore,
storyFn = _props2.storyFn,
context = _props2.context;
// Update the related knob and it's value.
var knobOptions = knobStore.get(name);
knobOptions.value = value;
knobStore.markAllUnused();
this.setState({ storyContent: storyFn(context) });
}
}, {
key: 'resetKnobs',
value: function resetKnobs() {
var _props3 = this.props,
knobStore = _props3.knobStore,
storyFn = _props3.storyFn,
context = _props3.context;
knobStore.reset();
this.setState({ storyContent: storyFn(context) });
this.setPaneKnobs();
}
}, {
key: 'render',
value: function render() {
return this.state.storyContent;
}
}]);
return WrapStory;
}(_react2.default.Component);
exports.default = WrapStory;
WrapStory.propTypes = {
context: _react2.default.PropTypes.object,
storyFn: _react2.default.PropTypes.func,
channel: _react2.default.PropTypes.object,
knobStore: _react2.default.PropTypes.object,
initialContent: _react2.default.PropTypes.object
};

93
dist/components/types/Array.js vendored Normal file
View File

@ -0,0 +1,93 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');
var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
var _createClass2 = require('babel-runtime/helpers/createClass');
var _createClass3 = _interopRequireDefault(_createClass2);
var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');
var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
var _inherits2 = require('babel-runtime/helpers/inherits');
var _inherits3 = _interopRequireDefault(_inherits2);
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _reactTextareaAutosize = require('react-textarea-autosize');
var _reactTextareaAutosize2 = _interopRequireDefault(_reactTextareaAutosize);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var styles = {
display: 'table-cell',
boxSizing: 'border-box',
verticalAlign: 'middle',
height: '26px',
width: '100%',
outline: 'none',
border: '1px solid #f7f4f4',
borderRadius: 2,
fontSize: 11,
padding: '5px',
color: '#555'
};
var ArrayType = function (_React$Component) {
(0, _inherits3.default)(ArrayType, _React$Component);
function ArrayType() {
(0, _classCallCheck3.default)(this, ArrayType);
return (0, _possibleConstructorReturn3.default)(this, (ArrayType.__proto__ || (0, _getPrototypeOf2.default)(ArrayType)).apply(this, arguments));
}
(0, _createClass3.default)(ArrayType, [{
key: 'render',
value: function render() {
var _props = this.props,
knob = _props.knob,
_onChange = _props.onChange;
return _react2.default.createElement(_reactTextareaAutosize2.default, {
id: knob.name,
ref: 'input',
style: styles,
value: knob.value.join(knob.separator),
onChange: function onChange(e) {
return _onChange(e.target.value.split(knob.separator));
}
});
}
}]);
return ArrayType;
}(_react2.default.Component);
ArrayType.propTypes = {
knob: _react2.default.PropTypes.object,
onChange: _react2.default.PropTypes.func
};
ArrayType.serialize = function (value) {
return value;
};
ArrayType.deserialize = function (value) {
return value;
};
exports.default = ArrayType;

91
dist/components/types/Boolean.js vendored Normal file
View File

@ -0,0 +1,91 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');
var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
var _createClass2 = require('babel-runtime/helpers/createClass');
var _createClass3 = _interopRequireDefault(_createClass2);
var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');
var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
var _inherits2 = require('babel-runtime/helpers/inherits');
var _inherits3 = _interopRequireDefault(_inherits2);
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var styles = {
display: 'table-cell',
boxSizing: 'border-box',
verticalAlign: 'top',
height: 21,
outline: 'none',
border: '1px solid #ececec',
fontSize: '12px',
color: '#555'
};
var BooleanType = function (_React$Component) {
(0, _inherits3.default)(BooleanType, _React$Component);
function BooleanType() {
(0, _classCallCheck3.default)(this, BooleanType);
return (0, _possibleConstructorReturn3.default)(this, (BooleanType.__proto__ || (0, _getPrototypeOf2.default)(BooleanType)).apply(this, arguments));
}
(0, _createClass3.default)(BooleanType, [{
key: 'render',
value: function render() {
var _this2 = this;
var _props = this.props,
knob = _props.knob,
_onChange = _props.onChange;
return _react2.default.createElement('input', {
id: knob.name,
ref: 'input',
style: styles,
type: 'checkbox',
onChange: function onChange() {
return _onChange(_this2.refs.input.checked);
},
checked: knob.value
});
}
}]);
return BooleanType;
}(_react2.default.Component);
BooleanType.propTypes = {
knob: _react2.default.PropTypes.object,
onChange: _react2.default.PropTypes.func
};
BooleanType.serialize = function (value) {
return String(value);
};
BooleanType.deserialize = function (value) {
if (!value) return false;
return value.trim() === 'true';
};
exports.default = BooleanType;

152
dist/components/types/Color.js vendored Normal file
View File

@ -0,0 +1,152 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');
var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
var _createClass2 = require('babel-runtime/helpers/createClass');
var _createClass3 = _interopRequireDefault(_createClass2);
var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');
var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
var _inherits2 = require('babel-runtime/helpers/inherits');
var _inherits3 = _interopRequireDefault(_inherits2);
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _reactColor = require('react-color');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var styles = {
swatch: {
background: '#fff',
borderRadius: '1px',
border: '1px solid rgb(247, 244, 244)',
display: 'inline-block',
cursor: 'pointer',
width: '100%'
},
popover: {
position: 'absolute',
zIndex: '2'
},
cover: {
position: 'fixed',
top: '0px',
right: '0px',
bottom: '0px',
left: '0px'
}
};
var ColorType = function (_React$Component) {
(0, _inherits3.default)(ColorType, _React$Component);
function ColorType(props) {
(0, _classCallCheck3.default)(this, ColorType);
var _this = (0, _possibleConstructorReturn3.default)(this, (ColorType.__proto__ || (0, _getPrototypeOf2.default)(ColorType)).call(this, props));
_this.handleClick = _this.handleClick.bind(_this);
_this.onWindowMouseDown = _this.onWindowMouseDown.bind(_this);
_this.state = {
displayColorPicker: false
};
return _this;
}
(0, _createClass3.default)(ColorType, [{
key: 'componentDidMount',
value: function componentDidMount() {
document.addEventListener('mousedown', this.onWindowMouseDown);
}
}, {
key: 'componentWillUnmount',
value: function componentWillUnmount() {
document.removeEventListener('mousedown', this.onWindowMouseDown);
}
}, {
key: 'onWindowMouseDown',
value: function onWindowMouseDown(e) {
if (!this.state.displayColorPicker) return;
if (this.popover.contains(e.target)) return;
this.setState({
displayColorPicker: false
});
}
}, {
key: 'handleClick',
value: function handleClick() {
this.setState({
displayColorPicker: !this.state.displayColorPicker
});
}
}, {
key: 'render',
value: function render() {
var _this2 = this;
var _props = this.props,
knob = _props.knob,
_onChange = _props.onChange;
var colorStyle = {
width: 'auto',
height: '20px',
borderRadius: '2px',
margin: 5,
background: knob.value
};
return _react2.default.createElement(
'div',
{ id: knob.name },
_react2.default.createElement(
'div',
{ style: styles.swatch, onClick: this.handleClick },
_react2.default.createElement('div', { style: colorStyle })
),
this.state.displayColorPicker ? _react2.default.createElement(
'div',
{ style: styles.popover, ref: function ref(e) {
_this2.popover = e;
} },
_react2.default.createElement(_reactColor.SketchPicker, { color: knob.value, onChange: function onChange(color) {
return _onChange(color.hex);
} })
) : null
);
}
}]);
return ColorType;
}(_react2.default.Component);
ColorType.propTypes = {
knob: _react2.default.PropTypes.object,
onChange: _react2.default.PropTypes.func
};
ColorType.serialize = function (value) {
return value;
};
ColorType.deserialize = function (value) {
return value;
};
exports.default = ColorType;

95
dist/components/types/Date/index.js vendored Normal file
View File

@ -0,0 +1,95 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');
var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
var _createClass2 = require('babel-runtime/helpers/createClass');
var _createClass3 = _interopRequireDefault(_createClass2);
var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');
var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
var _inherits2 = require('babel-runtime/helpers/inherits');
var _inherits3 = _interopRequireDefault(_inherits2);
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _reactDatetime = require('react-datetime');
var _reactDatetime2 = _interopRequireDefault(_reactDatetime);
var _insertCss = require('insert-css');
var _insertCss2 = _interopRequireDefault(_insertCss);
var _styles = require('./styles');
var _styles2 = _interopRequireDefault(_styles);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var customStyle = '\n .rdt input {\n outline: 0;\n width: 100%;\n border: 1px solid #f7f4f4;\n border-radius: 2px;\n font-size: 11px;\n padding: 5px;\n color: #555;\n display: table-cell;\n box-sizing: border-box;\n }\n';
(0, _insertCss2.default)(_styles2.default);
(0, _insertCss2.default)(customStyle);
var DateType = function (_React$Component) {
(0, _inherits3.default)(DateType, _React$Component);
function DateType() {
(0, _classCallCheck3.default)(this, DateType);
return (0, _possibleConstructorReturn3.default)(this, (DateType.__proto__ || (0, _getPrototypeOf2.default)(DateType)).apply(this, arguments));
}
(0, _createClass3.default)(DateType, [{
key: 'render',
value: function render() {
var _props = this.props,
knob = _props.knob,
_onChange = _props.onChange;
return _react2.default.createElement(
'div',
null,
_react2.default.createElement(_reactDatetime2.default, {
id: knob.name,
value: knob.value ? new Date(knob.value) : null,
type: 'date',
onChange: function onChange(date) {
return _onChange(date.valueOf());
}
})
);
}
}]);
return DateType;
}(_react2.default.Component);
DateType.propTypes = {
knob: _react2.default.PropTypes.object,
onChange: _react2.default.PropTypes.func
};
DateType.serialize = function (value) {
return String(value);
};
DateType.deserialize = function (value) {
return parseFloat(value);
};
exports.default = DateType;

6
dist/components/types/Date/styles.js vendored Normal file
View File

@ -0,0 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = "\n .rdt {\n position: relative;\n }\n .rdtPicker {\n display: none;\n position: absolute;\n width: 200px;\n padding: 4px;\n margin-top: 1px;\n z-index: 99999 !important;\n background: #fff;\n box-shadow: 0 1px 3px rgba(0,0,0,.1);\n border: 1px solid #f9f9f9;\n }\n .rdtOpen .rdtPicker {\n display: block;\n }\n .rdtStatic .rdtPicker {\n box-shadow: none;\n position: static;\n }\n\n .rdtPicker .rdtTimeToggle {\n text-align: center;\n font-size:11px;\n }\n\n .rdtPicker table {\n width: 100%;\n margin: 0;\n }\n .rdtPicker td,\n .rdtPicker th {\n text-align: center;\n height: 28px;\n }\n .rdtPicker td {\n cursor: pointer;\n }\n .rdtPicker td.rdtDay:hover,\n .rdtPicker td.rdtHour:hover,\n .rdtPicker td.rdtMinute:hover,\n .rdtPicker td.rdtSecond:hover,\n .rdtPicker .rdtTimeToggle:hover {\n background: #eeeeee;\n cursor: pointer;\n }\n .rdtPicker td.rdtOld,\n .rdtPicker td.rdtNew {\n color: #999999;\n }\n .rdtPicker td.rdtToday {\n position: relative;\n }\n .rdtPicker td.rdtToday:before {\n content: '';\n display: inline-block;\n border-left: 7px solid transparent;\n border-bottom: 7px solid #428bca;\n border-top-color: rgba(0, 0, 0, 0.2);\n position: absolute;\n bottom: 4px;\n right: 4px;\n }\n .rdtPicker td.rdtActive,\n .rdtPicker td.rdtActive:hover {\n background-color: #428bca;\n color: #fff;\n text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);\n }\n .rdtPicker td.rdtActive.rdtToday:before {\n border-bottom-color: #fff;\n }\n .rdtPicker td.rdtDisabled,\n .rdtPicker td.rdtDisabled:hover {\n background: none;\n color: #999999;\n cursor: not-allowed;\n }\n\n .rdtPicker td span.rdtOld {\n color: #999999;\n }\n .rdtPicker td span.rdtDisabled,\n .rdtPicker td span.rdtDisabled:hover {\n background: none;\n color: #999999;\n cursor: not-allowed;\n }\n .rdtPicker th {\n border-bottom: 1px solid #f9f9f9;\n }\n .rdtPicker .dow {\n width: 14.2857%;\n font-size: 11px;\n border-bottom: none;\n }\n .rdtPicker th.rdtSwitch {\n width: 100px;\n font-size: 11px;\n }\n .rdtPicker th.rdtNext,\n .rdtPicker th.rdtPrev {\n font-size: 11px;\n vertical-align: top;\n }\n\n .rdtPrev span,\n .rdtNext span {\n display: block;\n -webkit-touch-callout: none; /* iOS Safari */\n -webkit-user-select: none; /* Chrome/Safari/Opera */\n -khtml-user-select: none; /* Konqueror */\n -moz-user-select: none; /* Firefox */\n -ms-user-select: none; /* Internet Explorer/Edge */\n user-select: none;\n }\n\n .rdtPicker th.rdtDisabled,\n .rdtPicker th.rdtDisabled:hover {\n background: none;\n color: #999999;\n cursor: not-allowed;\n }\n .rdtPicker thead tr:first-child th {\n cursor: pointer;\n }\n .rdtPicker thead tr:first-child th:hover {\n background: #eeeeee;\n }\n\n .rdtPicker tfoot {\n border-top: 1px solid #f9f9f9;\n }\n\n .rdtPicker button {\n border: none;\n background: none;\n cursor: pointer;\n }\n .rdtPicker button:hover {\n background-color: #eee;\n }\n\n .rdtPicker thead button {\n width: 100%;\n height: 100%;\n }\n\n td.rdtMonth,\n td.rdtYear {\n height: 50px;\n width: 25%;\n cursor: pointer;\n }\n td.rdtMonth:hover,\n td.rdtYear:hover {\n background: #eee;\n }\n\n td.rdtDay {\n font-size: 11px\n }\n\n .rdtCounters {\n display: inline-block;\n }\n\n .rdtCounters > div {\n float: left;\n }\n\n .rdtCounter {\n height: 100px;\n }\n\n .rdtCounter {\n width: 40px;\n }\n\n .rdtCounterSeparator {\n line-height: 100px;\n }\n\n .rdtCounter .rdtBtn {\n height: 40%;\n line-height: 40px;\n cursor: pointer;\n display: block;\n font-size: 11px;\n\n -webkit-touch-callout: none; /* iOS Safari */\n -webkit-user-select: none; /* Chrome/Safari/Opera */\n -khtml-user-select: none; /* Konqueror */\n -moz-user-select: none; /* Firefox */\n -ms-user-select: none; /* Internet Explorer/Edge */\n user-select: none;\n }\n .rdtCounter .rdtBtn:hover {\n background: #eee;\n }\n .rdtCounter .rdtCount {\n height: 20%;\n font-size: 11px;\n }\n\n .rdtMilli {\n vertical-align: middle;\n padding-left: 8px;\n width: 48px;\n }\n\n .rdtMilli input {\n width: 100%;\n font-size: 11px;\n margin-top: 37px;\n }\n";

130
dist/components/types/Number.js vendored Normal file
View File

@ -0,0 +1,130 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');
var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
var _createClass2 = require('babel-runtime/helpers/createClass');
var _createClass3 = _interopRequireDefault(_createClass2);
var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');
var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
var _inherits2 = require('babel-runtime/helpers/inherits');
var _inherits3 = _interopRequireDefault(_inherits2);
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var styles = {
display: 'table-cell',
boxSizing: 'border-box',
verticalAlign: 'middle',
height: '26px',
width: '100%',
outline: 'none',
border: '1px solid #f7f4f4',
borderRadius: 2,
fontSize: 11,
padding: '5px',
color: '#444'
};
var NumberType = function (_React$Component) {
(0, _inherits3.default)(NumberType, _React$Component);
function NumberType(props) {
(0, _classCallCheck3.default)(this, NumberType);
var _this = (0, _possibleConstructorReturn3.default)(this, (NumberType.__proto__ || (0, _getPrototypeOf2.default)(NumberType)).call(this, props));
_this.renderNormal = _this.renderNormal.bind(_this);
_this.renderRange = _this.renderRange.bind(_this);
return _this;
}
(0, _createClass3.default)(NumberType, [{
key: 'renderNormal',
value: function renderNormal() {
var _this2 = this;
var _props = this.props,
knob = _props.knob,
_onChange = _props.onChange;
return _react2.default.createElement('input', {
id: knob.name,
ref: 'input',
style: styles,
value: knob.value,
type: 'number',
onChange: function onChange() {
return _onChange(parseFloat(_this2.refs.input.value));
}
});
}
}, {
key: 'renderRange',
value: function renderRange() {
var _this3 = this;
var _props2 = this.props,
knob = _props2.knob,
_onChange2 = _props2.onChange;
return _react2.default.createElement('input', {
id: knob.name,
ref: 'input',
style: styles,
value: knob.value,
type: 'range',
min: knob.min,
max: knob.max,
step: knob.step,
onChange: function onChange() {
return _onChange2(parseFloat(_this3.refs.input.value));
}
});
}
}, {
key: 'render',
value: function render() {
var knob = this.props.knob;
return knob.range ? this.renderRange() : this.renderNormal();
}
}]);
return NumberType;
}(_react2.default.Component);
NumberType.propTypes = {
knob: _react2.default.PropTypes.object,
onChange: _react2.default.PropTypes.func
};
NumberType.serialize = function (value) {
return String(value);
};
NumberType.deserialize = function (value) {
return parseFloat(value);
};
exports.default = NumberType;

163
dist/components/types/Object.js vendored Normal file
View File

@ -0,0 +1,163 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _extends2 = require('babel-runtime/helpers/extends');
var _extends3 = _interopRequireDefault(_extends2);
var _stringify = require('babel-runtime/core-js/json/stringify');
var _stringify2 = _interopRequireDefault(_stringify);
var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');
var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
var _createClass2 = require('babel-runtime/helpers/createClass');
var _createClass3 = _interopRequireDefault(_createClass2);
var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');
var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
var _inherits2 = require('babel-runtime/helpers/inherits');
var _inherits3 = _interopRequireDefault(_inherits2);
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _reactTextareaAutosize = require('react-textarea-autosize');
var _reactTextareaAutosize2 = _interopRequireDefault(_reactTextareaAutosize);
var _deepEqual = require('deep-equal');
var _deepEqual2 = _interopRequireDefault(_deepEqual);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var styles = {
display: 'table-cell',
boxSizing: 'border-box',
verticalAlign: 'middle',
width: '100%',
outline: 'none',
border: '1px solid #f7f4f4',
borderRadius: 2,
fontSize: 11,
padding: '5px',
color: '#555',
fontFamily: 'monospace'
};
var ObjectType = function (_React$Component) {
(0, _inherits3.default)(ObjectType, _React$Component);
function ObjectType() {
var _ref;
(0, _classCallCheck3.default)(this, ObjectType);
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
var _this = (0, _possibleConstructorReturn3.default)(this, (_ref = ObjectType.__proto__ || (0, _getPrototypeOf2.default)(ObjectType)).call.apply(_ref, [this].concat(args)));
_this.state = {};
return _this;
}
(0, _createClass3.default)(ObjectType, [{
key: 'getJSONString',
value: function getJSONString() {
var _state = this.state,
json = _state.json,
jsonString = _state.jsonString;
var knob = this.props.knob;
// If there is an error in the JSON, we need to give that errored JSON.
if (this.failed) return jsonString;
// If the editor value and the knob value is the same, we need to return the
// editor value as it allow user to add new fields to the JSON.
if ((0, _deepEqual2.default)(json, knob.value)) return jsonString;
// If the knob's value is different from the editor, it seems like
// there's a outside change and we need to get that.
return (0, _stringify2.default)(knob.value, null, 2);
}
}, {
key: 'handleChange',
value: function handleChange(e) {
var onChange = this.props.onChange;
var newState = {
jsonString: e.target.value
};
try {
newState.json = JSON.parse(e.target.value.trim());
onChange(newState.json);
this.failed = false;
} catch (err) {
this.failed = true;
}
this.setState(newState);
}
}, {
key: 'render',
value: function render() {
var _this2 = this;
var knob = this.props.knob;
var jsonString = this.getJSONString();
var extraStyle = {};
if (this.failed) {
extraStyle.border = '1px solid #fadddd';
extraStyle.backgroundColor = '#fff5f5';
}
return _react2.default.createElement(_reactTextareaAutosize2.default, {
id: knob.name,
ref: 'input',
style: (0, _extends3.default)({}, styles, extraStyle),
value: jsonString,
onChange: function onChange(e) {
return _this2.handleChange(e);
}
});
}
}]);
return ObjectType;
}(_react2.default.Component);
ObjectType.propTypes = {
knob: _react2.default.PropTypes.object,
onChange: _react2.default.PropTypes.func
};
ObjectType.serialize = function (object) {
return (0, _stringify2.default)(object);
};
ObjectType.deserialize = function (value) {
if (!value) return {};
return JSON.parse(value);
};
exports.default = ObjectType;

130
dist/components/types/Select.js vendored Normal file
View File

@ -0,0 +1,130 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _keys = require('babel-runtime/core-js/object/keys');
var _keys2 = _interopRequireDefault(_keys);
var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');
var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
var _createClass2 = require('babel-runtime/helpers/createClass');
var _createClass3 = _interopRequireDefault(_createClass2);
var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');
var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
var _inherits2 = require('babel-runtime/helpers/inherits');
var _inherits3 = _interopRequireDefault(_inherits2);
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var styles = {
display: 'table-cell',
boxSizing: 'border-box',
verticalAlign: 'middle',
height: '26px',
width: '100%',
outline: 'none',
border: '1px solid #f7f4f4',
borderRadius: 2,
fontSize: 11,
padding: '5px',
color: '#555'
};
var SelectType = function (_React$Component) {
(0, _inherits3.default)(SelectType, _React$Component);
function SelectType() {
(0, _classCallCheck3.default)(this, SelectType);
return (0, _possibleConstructorReturn3.default)(this, (SelectType.__proto__ || (0, _getPrototypeOf2.default)(SelectType)).apply(this, arguments));
}
(0, _createClass3.default)(SelectType, [{
key: '_makeOpt',
value: function _makeOpt(key, val) {
var opts = {
key: key,
value: key
};
return _react2.default.createElement(
'option',
opts,
val
);
}
}, {
key: '_options',
value: function _options(values) {
var _this2 = this;
var data = [];
if (Array.isArray(values)) {
data = values.map(function (val) {
return _this2._makeOpt(val, val);
});
} else {
data = (0, _keys2.default)(values).map(function (key) {
return _this2._makeOpt(key, values[key]);
});
}
return data;
}
}, {
key: 'render',
value: function render() {
var _props = this.props,
knob = _props.knob,
_onChange = _props.onChange;
return _react2.default.createElement(
'select',
{
id: knob.name,
ref: 'input',
style: styles,
value: knob.value,
onChange: function onChange(e) {
return _onChange(e.target.value);
}
},
this._options(knob.options)
);
}
}]);
return SelectType;
}(_react2.default.Component);
SelectType.propTypes = {
knob: _react2.default.PropTypes.object,
onChange: _react2.default.PropTypes.func
};
SelectType.serialize = function (value) {
return value;
};
SelectType.deserialize = function (value) {
return value;
};
exports.default = SelectType;

94
dist/components/types/Text.js vendored Normal file
View File

@ -0,0 +1,94 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');
var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
var _createClass2 = require('babel-runtime/helpers/createClass');
var _createClass3 = _interopRequireDefault(_createClass2);
var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');
var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
var _inherits2 = require('babel-runtime/helpers/inherits');
var _inherits3 = _interopRequireDefault(_inherits2);
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _reactTextareaAutosize = require('react-textarea-autosize');
var _reactTextareaAutosize2 = _interopRequireDefault(_reactTextareaAutosize);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var styles = {
display: 'table-cell',
boxSizing: 'border-box',
verticalAlign: 'middle',
height: '26px',
width: '100%',
outline: 'none',
border: '1px solid #f7f4f4',
borderRadius: 2,
fontSize: 11,
padding: '5px',
color: '#555'
};
var TextType = function (_React$Component) {
(0, _inherits3.default)(TextType, _React$Component);
function TextType() {
(0, _classCallCheck3.default)(this, TextType);
return (0, _possibleConstructorReturn3.default)(this, (TextType.__proto__ || (0, _getPrototypeOf2.default)(TextType)).apply(this, arguments));
}
(0, _createClass3.default)(TextType, [{
key: 'render',
value: function render() {
var _props = this.props,
knob = _props.knob,
_onChange = _props.onChange;
return _react2.default.createElement(_reactTextareaAutosize2.default, {
id: knob.name,
ref: 'input',
style: styles,
value: knob.value,
onChange: function onChange(e) {
return _onChange(e.target.value);
}
});
}
}]);
return TextType;
}(_react2.default.Component);
TextType.propTypes = {
knob: _react2.default.PropTypes.object,
onChange: _react2.default.PropTypes.func
};
TextType.serialize = function (value) {
return value;
};
TextType.deserialize = function (value) {
return value;
};
exports.default = TextType;

50
dist/components/types/index.js vendored Normal file
View File

@ -0,0 +1,50 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _Text = require('./Text');
var _Text2 = _interopRequireDefault(_Text);
var _Number = require('./Number');
var _Number2 = _interopRequireDefault(_Number);
var _Color = require('./Color');
var _Color2 = _interopRequireDefault(_Color);
var _Boolean = require('./Boolean');
var _Boolean2 = _interopRequireDefault(_Boolean);
var _Object = require('./Object');
var _Object2 = _interopRequireDefault(_Object);
var _Select = require('./Select');
var _Select2 = _interopRequireDefault(_Select);
var _Array = require('./Array');
var _Array2 = _interopRequireDefault(_Array);
var _Date = require('./Date');
var _Date2 = _interopRequireDefault(_Date);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
exports.default = {
text: _Text2.default,
number: _Number2.default,
color: _Color2.default,
boolean: _Boolean2.default,
object: _Object2.default,
select: _Select2.default,
array: _Array2.default,
date: _Date2.default
};

94
dist/index.js vendored Normal file
View File

@ -0,0 +1,94 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _extends2 = require('babel-runtime/helpers/extends');
var _extends3 = _interopRequireDefault(_extends2);
exports.knob = knob;
exports.text = text;
exports.boolean = boolean;
exports.number = number;
exports.color = color;
exports.object = object;
exports.select = select;
exports.array = array;
exports.date = date;
exports.withKnobs = withKnobs;
var _storybookAddons = require('@kadira/storybook-addons');
var _storybookAddons2 = _interopRequireDefault(_storybookAddons);
var _KnobManager = require('./KnobManager');
var _KnobManager2 = _interopRequireDefault(_KnobManager);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var manager = new _KnobManager2.default();
function knob(name, options) {
return manager.knob(name, options);
}
function text(name, value) {
return manager.knob(name, { type: 'text', value: value });
}
function boolean(name, value) {
return manager.knob(name, { type: 'boolean', value: value });
}
function number(name, value) {
var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
var defaults = {
range: false,
min: 0,
max: 10,
step: 1
};
var mergedOptions = (0, _extends3.default)({}, defaults, options);
var finalOptions = (0, _extends3.default)({}, mergedOptions, {
type: 'number',
value: value
});
return manager.knob(name, finalOptions);
}
function color(name, value) {
return manager.knob(name, { type: 'color', value: value });
}
function object(name, value) {
return manager.knob(name, { type: 'object', value: value });
}
function select(name, options, value) {
return manager.knob(name, { type: 'select', options: options, value: value });
}
function array(name, value) {
var separator = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : ',';
return manager.knob(name, { type: 'array', value: value, separator: separator });
}
function date(name) {
var value = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : new Date();
var proxyValue = value ? value.getTime() : null;
return manager.knob(name, { type: 'date', value: proxyValue });
}
function withKnobs(storyFn, context) {
var channel = _storybookAddons2.default.getChannel();
return manager.wrapStory(channel, storyFn, context);
}

26
dist/register.js vendored Normal file
View File

@ -0,0 +1,26 @@
'use strict';
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _storybookAddons = require('@kadira/storybook-addons');
var _storybookAddons2 = _interopRequireDefault(_storybookAddons);
var _Panel = require('./components/Panel');
var _Panel2 = _interopRequireDefault(_Panel);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
_storybookAddons2.default.register('kadirahq/storybook-addon-knobs', function (api) {
var channel = _storybookAddons2.default.getChannel();
_storybookAddons2.default.addPanel('kadirahq/storybook-addon-knobs', {
title: 'Knobs',
render: function render() {
return _react2.default.createElement(_Panel2.default, { channel: channel, api: api, key: 'knobs-panel' });
}
});
});

BIN
docs/demo.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 709 KiB

BIN
docs/demo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 KiB

103
example/stories/index.js Normal file
View File

@ -0,0 +1,103 @@
import React from 'react';
import { storiesOf } from '@kadira/storybook';
import moment from 'moment';
import {
withKnobs,
number,
object,
boolean,
text,
select,
date,
array,
color,
} from '../../src';
const stories = storiesOf('Example of Knobs', module);
stories.addDecorator(withKnobs);
stories.add('simple example', () => (
<button>{text('Label', 'Hello Button')}</button>
));
stories.add('with all knobs', () => {
const name = text('Name', 'Tom Cary');
const dob = date('DOB', new Date('January 20 1887'));
const bold = boolean('Bold', false);
const selectedColor = color('Color', 'black');
const favoriteNumber = number('Favorite Number', 42);
const comfortTemp = number('Comfort Temp', 72, {range:true, min: 60, max: 90, step: 1});
const passions = array('Passions', ['Fishing', 'Skiing']);
const customStyle = object('Style', {
fontFamily: 'Arial',
padding: 20,
});
const style = {
...customStyle,
fontWeight: bold ? 800: 400,
favoriteNumber: favoriteNumber,
color: selectedColor,
};
return (
<div style={style}>
I'm {name} and I was born on "{moment(dob).format("DD MMM YYYY")}"
I like: <ul>{passions.map((p, i) => <li key={i}>{p}</li>)}</ul>
<p>My favorite number is {favoriteNumber}.</p>
<p>My most comfortable room temperature is {comfortTemp} degrees Fahrenheit.</p>
</div>
);
})
stories.add('dates Knob', () => {
const today = date('today');
const dob = date('DOB', null);
const myDob = date('My DOB', new Date('July 07 1993'));
return (
<ul style={{listStyleType:"none",listStyle:"none",paddingLeft:"15px"}}>
<li>
<p><b>Javascript Date</b> default value, passes date value</p>
<blockquote>
<code>const myDob = date('My DOB', new Date('July 07 1993'));</code>
<pre>// I was born in: "{moment(myDob).format("DD MMM YYYY")}"</pre>
</blockquote>
</li>
<li>
<p><b>undefined</b> default value passes today's date</p>
<blockquote>
<code>const today = date('today');</code>
<pre>// Today's date is: "{moment(today).format("DD MMM YYYY")}"</pre>
</blockquote>
</li>
<li>
<p><b>null</b> default value passes null value</p>
<blockquote>
<code>const dob = date('DOB', null);</code>
<pre>// You were born in: "{dob? moment(dob).format("DD MMM YYYY"): 'Please select date.'}"</pre>
</blockquote>
</li>
</ul>
)
})
stories.add('dynamic knobs', () => {
const showOptional = select('Show optional', ['yes', 'no'], 'yes')
return (
<div>
<div>
{text('compulsary', 'I must be here')}
</div>
{ showOptional==='yes' ? <div>{text('optional', 'I can disapear')}</div> : null }
</div>
)
})
stories.add('without any knob', () => (
<button>This is a button</button>
));

View File

@ -0,0 +1,99 @@
import * as React from 'react';
import { storiesOf } from '@kadira/storybook';
import * as moment from 'moment';
import {
withKnobs,
number,
color,
object,
boolean,
text,
select,
date
} from '../../storybook-addon-knobs';
const stories = storiesOf('Example of Knobs', module);
stories.addDecorator(withKnobs);
stories.add('simple example', () => (
<button>{text('Label', 'Hello Button')}</button>
));
stories.add('with all knobs', () => {
const name = text('Name', 'Tom Cary');
const dob = date('DOB', new Date('January 20 1887'));
const bold = boolean('Bold', false);
const color = color('Color', 'black');
const textDecoration = select('Decoration', {
none: 'None',
underline: 'Underline',
"line-through": 'Line-Through'
}, 'none');
const customStyle = object('Style', {
fontFamily: 'Arial',
padding: 20,
});
const style = Object.assign({}, customStyle, {
fontWeight: bold ? 800: 400,
color,
textDecoration
});
return (
<div style={style}>
I'm {name} and I was born on "{moment(dob).format("DD MMM YYYY")}"
</div>
);
});
stories.add('dates Knob', () => {
const today = date('today');
const dob = date('DOB', null);
const myDob = date('My DOB', new Date('July 07 1993'));
return (
<ul style={{listStyleType:"none",listStyle:"none",paddingLeft:"15px"}}>
<li>
<p><b>Javascript Date</b> default value, passes date value</p>
<blockquote>
<code>const myDob = date('My DOB', new Date('July 07 1993'));</code>
<pre>// I was born in: "{moment(myDob).format("DD MMM YYYY")}"</pre>
</blockquote>
</li>
<li>
<p><b>undefined</b> default value passes today's date</p>
<blockquote>
<code>const today = date('today');</code>
<pre>// Today's date is: "{moment(today).format("DD MMM YYYY")}"</pre>
</blockquote>
</li>
<li>
<p><b>null</b> default value passes null value</p>
<blockquote>
<code>const dob = date('DOB', null);</code>
<pre>// You were born in: "{dob? moment(dob).format("DD MMM YYYY"): 'Please select date.'}"</pre>
</blockquote>
</li>
</ul>
)
});
stories.add('dynamic knobs', () => {
const showOptional = select('Show optional', ['yes', 'no'], 'yes')
return (
<div>
<div>
{text('compulsary', 'I must be here')}
</div>
{ showOptional==='yes' ? <div>{text('optional', 'I can disapear')}</div> : null }
</div>
)
});
stories.add('without any knob', () => (
<button>This is a button</button>
));

73
package.json Normal file
View File

@ -0,0 +1,73 @@
{
"name": "@kadira/storybook-addon-knobs",
"version": "1.7.1",
"description": "React Storybook Addon Prop Editor Component",
"repository": {
"type": "git",
"url": "https://github.com/kadirahq/react-storybook-addon-knobs.git"
},
"license": "MIT",
"scripts": {
"start": "./example/prepublish.sh",
"prepublish": ". ./.scripts/prepublish.sh",
"lint": "eslint src",
"lintfix": "eslint src --fix",
"testonly": "mocha --require .scripts/mocha_runner src/**/tests/**/*.js",
"test": "npm run lint && npm run testonly",
"test-watch": "npm run testonly -- --watch --watch-extensions js",
"storybook": "start-storybook -p 9010",
"publish-storybook": "bash .scripts/publish_storybook.sh"
},
"devDependencies": {
"@kadira/storybook": "^2.1.0",
"@types/mocha": "^2.2.32",
"@types/node": "^6.0.46",
"@types/react": "^0.14.42",
"babel-cli": "^6.5.0",
"babel-core": "^6.5.0",
"babel-eslint": "^7.0.0",
"babel-loader": "^6.2.4",
"babel-plugin-transform-runtime": "^6.5.0",
"babel-polyfill": "^6.5.0",
"babel-preset-es2015": "^6.5.0",
"babel-preset-react": "^6.5.0",
"babel-preset-stage-2": "^6.5.0",
"chai": "^3.5.0",
"enzyme": "^2.2.0",
"eslint": "^2.7.0",
"eslint-config-airbnb": "^7.0.0",
"eslint-plugin-babel": "^3.2.0",
"eslint-plugin-jsx-a11y": "^0.6.2",
"eslint-plugin-react": "^4.3.0",
"git-url-parse": "^6.0.1",
"jsdom": "^8.3.1",
"mocha": "^3.1.0",
"raw-loader": "^0.5.1",
"react": "^15.3.2",
"react-addons-test-utils": "^15.0.0",
"react-dom": "^15.3.2",
"sinon": "^1.17.3",
"style-loader": "^0.13.1",
"typescript": "^2.0.6",
"typescript-definition-tester": "^0.0.5"
},
"peerDependencies": {
"@kadira/storybook-addons": "^1.3.0",
"react": "^0.14.7 || ^15.0.0",
"react-dom": "^0.14.7 || ^15.0.0"
},
"dependencies": {
"babel-runtime": "^6.5.0",
"deep-equal": "^1.0.1",
"insert-css": "^1.0.0",
"moment": "^2.15.0",
"react-color": "^2.4.3",
"react-datetime": "^2.6.0",
"react-textarea-autosize": "^4.0.5"
},
"main": "dist/index.js",
"engines": {
"npm": "^3.0.0"
},
"typings": "./storybook-addon-knobs.d.ts"
}

1
register.js Normal file
View File

@ -0,0 +1 @@
require('./dist/register');

75
src/KnobManager.js Normal file
View File

@ -0,0 +1,75 @@
import React from 'react';
import WrapStory from './components/WrapStory';
import KnobStore from './KnobStore';
import deepEqual from 'deep-equal';
// This is used by _mayCallChannel to determine how long to wait to before triggering a panel update
const PANEL_UPDATE_INTERVAL = 400;
export default class KnobManager {
constructor() {
this.knobStore = null;
this.knobStoreMap = {};
}
knob(name, options) {
this._mayCallChannel();
const knobStore = this.knobStore;
const existingKnob = knobStore.get(name);
// We need to return the value set by the knob editor via this.
// But, if the user changes the code for the defaultValue we should set
// that value instead.
if (existingKnob && deepEqual(options.value, existingKnob.defaultValue)) {
return existingKnob.value;
}
const defaultValue = options.value;
const knobInfo = {
...options,
name,
defaultValue,
};
knobStore.set(name, knobInfo);
return knobStore.get(name).value;
}
wrapStory(channel, storyFn, context) {
this.channel = channel;
const key = `${context.kind}:::${context.story}`;
let knobStore = this.knobStoreMap[key];
if (!knobStore) {
knobStore = this.knobStoreMap[key] = new KnobStore();
}
this.knobStore = knobStore;
knobStore.markAllUnused();
const initialContent = storyFn(context);
const props = { context, storyFn, channel, knobStore, initialContent };
return (<WrapStory {...props} />);
}
_mayCallChannel() {
// Re rendering of the story may cause changes to the knobStore. Some new knobs maybe added and
// Some knobs may go unused. So we need to update the panel accordingly. For example remove the
// unused knobs from the panel. This function sends the `setKnobs` message to the channel
// triggering a panel re-render.
if (this.calling) {
// If a call to channel has already registered ignore this call.
// Once the previous call is completed all the changes to knobStore including the one that
// triggered this, will be added to the panel.
// This avoids emitting to the channel within very short periods of time.
return;
}
this.calling = true;
setTimeout(() => {
this.calling = false;
// emit to the channel and trigger a panel re-render
this.channel.emit('addon:knobs:setKnobs', this.knobStore.getAll());
}, PANEL_UPDATE_INTERVAL);
}
}

47
src/KnobStore.js Normal file
View File

@ -0,0 +1,47 @@
export default class KnobStore {
constructor() {
this.store = {};
this.callbacks = [];
}
has(key) {
return this.store[key] !== undefined;
}
set(key, value) {
this.store[key] = value;
this.store[key].used = true;
this.callbacks.forEach(cb => cb());
}
get(key) {
const knob = this.store[key];
if (knob) {
knob.used = true;
}
return knob;
}
getAll() {
return this.store;
}
reset() {
this.store = {};
}
markAllUnused() {
Object.keys(this.store).forEach(knobName => {
this.store[knobName].used = false;
});
}
subscribe(cb) {
this.callbacks.push(cb);
}
unsubscribe(cb) {
const index = this.callbacks.indexOf(cb);
this.callbacks.splice(index, 1);
}
}

130
src/components/Panel.js Normal file
View File

@ -0,0 +1,130 @@
import React from 'react';
import PropForm from './PropForm';
import Types from './types';
const styles = {
panelWrapper: {
width: '100%',
},
panel: {
padding: '5px',
width: 'auto',
position: 'relative',
},
noKnobs: {
fontFamily: `
-apple-system, ".SFNSText-Regular", "San Francisco", "Roboto",
"Segoe UI", "Helvetica Neue", "Lucida Grande", sans-serif
`,
display: 'inline',
width: '100%',
textAlign: 'center',
color: 'rgb(190, 190, 190)',
padding: '10px',
},
resetButton: {
position: 'absolute',
bottom: 11,
right: 10,
border: 'none',
borderTop: 'solid 1px rgba(0, 0, 0, 0.2)',
borderLeft: 'solid 1px rgba(0, 0, 0, 0.2)',
background: 'rgba(255, 255, 255, 0.5)',
padding: '5px 10px',
borderRadius: '4px 0 0 0',
color: 'rgba(0, 0, 0, 0.5)',
outline: 'none',
},
};
export default class Panel extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.setKnobs = this.setKnobs.bind(this);
this.reset = this.reset.bind(this);
this.state = { knobs: {} };
this.loadedFromUrl = false;
this.props.channel.on('addon:knobs:setKnobs', this.setKnobs);
}
componentWillUnmount() {
this.props.channel.removeListener('addon:knobs:setKnobs', this.setKnobs);
}
setKnobs(knobs) {
const queryParams = {};
const { api, channel } = this.props;
Object.keys(knobs).forEach((name) => {
const knob = knobs[name];
// For the first time, get values from the URL and set them.
if (!this.loadedFromUrl) {
const urlValue = api.getQueryParam(`knob-${name}`);
if (urlValue !== undefined) { // If the knob value present in url
knob.value = Types[knob.type].deserialize(urlValue);
channel.emit('addon:knobs:knobChange', knob);
}
}
queryParams[`knob-${name}`] = Types[knob.type].serialize(knob.value);
});
this.loadedFromUrl = true;
api.setQueryParams(queryParams);
this.setState({ knobs });
}
reset() {
this.props.channel.emit('addon:knobs:reset');
}
handleChange(changedKnob) {
const { api, channel } = this.props;
const { knobs } = this.state;
const { name, type, value } = changedKnob;
const newKnobs = { ...knobs };
newKnobs[name] = {
...newKnobs[name],
...changedKnob,
};
this.setState({ knobs: newKnobs });
const queryParams = {};
queryParams[`knob-${name}`] = Types[type].serialize(value);
api.setQueryParams(queryParams);
channel.emit('addon:knobs:knobChange', changedKnob);
}
render() {
const { knobs } = this.state;
const knobsArray = Object.keys(knobs)
.filter(key => (knobs[key].used))
.map(key => (knobs[key]));
if (knobsArray.length === 0) {
return (
<div style={styles.noKnobs}>NO KNOBS</div>
);
}
return (
<div style={styles.panelWrapper}>
<div style={styles.panel}>
<PropForm knobs={knobsArray} onFieldChange={this.handleChange} />
</div>
<button style={styles.resetButton} onClick={this.reset}>RESET</button>
</div>
);
}
}
Panel.propTypes = {
channel: React.PropTypes.object,
onReset: React.PropTypes.object,
api: React.PropTypes.object,
};

View File

@ -0,0 +1,68 @@
import React from 'react';
import TypeMap from './types';
const InvalidType = () => (<span>Invalid Type</span>);
const stylesheet = {
field: {
display: 'table-row',
padding: '5px',
},
label: {
display: 'table-cell',
boxSizing: 'border-box',
verticalAlign: 'top',
paddingRight: 5,
paddingTop: 7,
textAlign: 'right',
width: 80,
fontSize: 10,
color: 'rgb(68, 68, 68)',
textTransform: 'uppercase',
fontWeight: 600,
},
};
stylesheet.textarea = {
...stylesheet.input,
height: '100px',
};
stylesheet.checkbox = {
...stylesheet.input,
width: 'auto',
};
export default class PropField extends React.Component {
constructor(props) {
super(props);
this._onChange = this.onChange.bind(this);
}
onChange(e) {
this.props.onChange(e.target.value);
}
render() {
const { onChange, knob } = this.props;
let InputType = TypeMap[knob.type] || InvalidType;
return (
<div style={stylesheet.field}>
<label htmlFor={knob.name} style={stylesheet.label}>
{`${knob.name}`}
</label>
<InputType
knob={knob}
onChange={onChange}
/>
</div>
);
}
}
PropField.propTypes = {
onChange: React.PropTypes.func.isRequired,
knob: React.PropTypes.object,
};

View File

@ -0,0 +1,55 @@
import React from 'react';
import PropField from './PropField';
const stylesheet = {
propForm: {
fontFamily: `
-apple-system, ".SFNSText-Regular", "San Francisco", "Roboto",
"Segoe UI", "Helvetica Neue", "Lucida Grande", sans-serif
`,
display: 'table',
boxSizing: 'border-box',
width: '100%',
borderCollapse: 'separate',
borderSpacing: '5px',
},
};
export default class propForm extends React.Component {
constructor() {
super();
this._onFieldChange = this.onFieldChange.bind(this);
}
onFieldChange(name, type, value) {
const change = { name, type, value };
this.props.onFieldChange(change);
}
render() {
const knobs = this.props.knobs;
return (
<form style={stylesheet.propForm}>
{knobs.map(knob => (
<PropField
key={knob.name}
name={knob.name}
type={knob.type}
value={knob.value}
knob={knob}
onChange={this._onFieldChange.bind(null, knob.name, knob.type)}
/>
))}
</form>
);
}
}
propForm.displayName = 'propForm';
propForm.propTypes = {
knobs: React.PropTypes.array.isRequired,
onFieldChange: React.PropTypes.func.isRequired,
};

View File

@ -0,0 +1,68 @@
import React from 'react';
export default class WrapStory extends React.Component {
constructor(props) {
super(props);
this.knobChanged = this.knobChanged.bind(this);
this.resetKnobs = this.resetKnobs.bind(this);
this.setPaneKnobs = this.setPaneKnobs.bind(this);
this._knobsAreReset = false;
this.state = { storyContent: this.props.initialContent };
}
componentDidMount() {
// Watch for changes in knob editor.
this.props.channel.on('addon:knobs:knobChange', this.knobChanged);
// Watch for the reset event and reset knobs.
this.props.channel.on('addon:knobs:reset', this.resetKnobs);
// Watch for any change in the knobStore and set the panel again for those
// changes.
this.props.knobStore.subscribe(this.setPaneKnobs);
// Set knobs in the panel for the first time.
this.setPaneKnobs();
}
componentWillReceiveProps(props) {
this.setState({ storyContent: props.initialContent });
}
componentWillUnmount() {
this.props.channel.removeListener('addon:knobs:knobChange', this.knobChanged);
this.props.channel.removeListener('addon:knobs:reset', this.resetKnobs);
this.props.knobStore.unsubscribe(this.setPaneKnobs);
}
setPaneKnobs() {
const { channel, knobStore } = this.props;
channel.emit('addon:knobs:setKnobs', knobStore.getAll());
}
knobChanged(change) {
const { name, value } = change;
const { knobStore, storyFn, context } = this.props;
// Update the related knob and it's value.
const knobOptions = knobStore.get(name);
knobOptions.value = value;
knobStore.markAllUnused();
this.setState({ storyContent: storyFn(context) });
}
resetKnobs() {
const { knobStore, storyFn, context } = this.props;
knobStore.reset();
this.setState({ storyContent: storyFn(context) });
this.setPaneKnobs();
}
render() {
return this.state.storyContent;
}
}
WrapStory.propTypes = {
context: React.PropTypes.object,
storyFn: React.PropTypes.func,
channel: React.PropTypes.object,
knobStore: React.PropTypes.object,
initialContent: React.PropTypes.object,
};

View File

@ -0,0 +1,18 @@
import React from 'react';
import { shallow } from 'enzyme';
import Array from '../types/Array';
import { expect } from 'chai';
import sinon from 'sinon';
const { describe, it } = global;
describe('Array', () => {
it('should subscribe to setKnobs event of channel', () => {
const onChange = sinon.spy();
const wrapper = shallow(<Array onChange={onChange}
knob={{ name: 'passions', value: ['Fishing', 'Skiing'], separator: ',' }}
/>);
wrapper.simulate('change', { target: { value: 'Fhishing,Skiing,Dancing' } });
expect(onChange.calledWith(['Fhishing', 'Skiing', 'Dancing'])).to.equal(true);
});
});

View File

@ -0,0 +1,142 @@
import React from 'react';
import { shallow } from 'enzyme';
import Panel from '../Panel';
import { expect } from 'chai';
import sinon from 'sinon';
const { describe, it } = global;
describe('Panel', () => {
it('should subscribe to setKnobs event of channel', () => {
const testChannel = { on: sinon.spy() };
shallow(<Panel channel={testChannel} />);
expect(testChannel.on.calledWith('addon:knobs:setKnobs')).to.equal(true);
});
describe('setKnobs handler', () => {
it('should read url params and set values for existing knobs', () => {
const handlers = {};
const testChannel = {
on: (e, handler) => {
handlers[e] = handler;
},
emit: sinon.spy(),
};
const testQueryParams = {
'knob-foo': 'test string',
bar: 'some other string',
};
const testApi = {
getQueryParam: key => {
return testQueryParams[key];
},
setQueryParams: sinon.spy(),
};
shallow(<Panel channel={testChannel} api={testApi} />);
const setKnobsHandler = handlers['addon:knobs:setKnobs'];
const knobs = {
foo: {
name: 'foo',
value: 'default string',
type: 'text',
},
baz: {
name: 'baz',
value: 'another knob value',
type: 'text',
},
};
setKnobsHandler(knobs);
const knobFromUrl = {
name: 'foo',
value: testQueryParams['knob-foo'],
type: 'text',
};
const e = 'addon:knobs:knobChange';
expect(testChannel.emit.calledWith(e, knobFromUrl)).to.equal(true);
});
it('should set query params when url params are already read', () => {
const handlers = {};
const testChannel = {
on: (e, handler) => {
handlers[e] = handler;
},
emit: sinon.spy(),
};
const testQueryParams = {
'knob-foo': 'test string',
bar: 'some other string',
};
const testApi = {
getQueryParam: key => {
return testQueryParams[key];
},
setQueryParams: sinon.spy(),
};
const wrapper = shallow(<Panel channel={testChannel} api={testApi} />);
const setKnobsHandler = handlers['addon:knobs:setKnobs'];
const knobs = {
foo: {
name: 'foo',
value: 'default string',
type: 'text',
},
baz: {
name: 'baz',
value: 'another knob value',
type: 'text',
},
};
// Make it act like that url params are already checked
wrapper.instance().loadedFromUrl = true;
setKnobsHandler(knobs);
const knobFromStory = {
'knob-foo': knobs.foo.value,
'knob-baz': knobs.baz.value,
};
expect(testApi.setQueryParams.calledWith(knobFromStory)).to.equal(true);
});
});
describe('handleChange()', () => {
it('should set queryParams and emit knobChange event', () => {
const testChannel = {
on: sinon.spy(),
emit: sinon.spy(),
};
const testApi = {
getQueryParam: sinon.spy(),
setQueryParams: sinon.spy(),
};
const wrapper = shallow(<Panel channel={testChannel} api={testApi} />);
const testChangedKnob = {
name: 'foo',
value: 'changed text',
type: 'text',
};
wrapper.instance().handleChange(testChangedKnob);
expect(testChannel.emit.calledWith(
'addon:knobs:knobChange', testChangedKnob)).to.equal(true);
const paramsChange = { 'knob-foo': 'changed text' };
expect(testApi.setQueryParams.calledWith(paramsChange)).to.equal(true);
});
});
});

View File

@ -0,0 +1,46 @@
import React from 'react';
import Textarea from 'react-textarea-autosize';
const styles = {
display: 'table-cell',
boxSizing: 'border-box',
verticalAlign: 'middle',
height: '26px',
width: '100%',
outline: 'none',
border: '1px solid #f7f4f4',
borderRadius: 2,
fontSize: 11,
padding: '5px',
color: '#555',
};
class ArrayType extends React.Component {
render() {
const { knob, onChange } = this.props;
return (
<Textarea
id={knob.name}
ref="input"
style={styles}
value={knob.value.join(knob.separator)}
onChange={(e) => onChange(e.target.value.split(knob.separator))}
/>
);
}
}
ArrayType.propTypes = {
knob: React.PropTypes.object,
onChange: React.PropTypes.func,
};
ArrayType.serialize = function (value) {
return value;
};
ArrayType.deserialize = function (value) {
return value;
};
export default ArrayType;

View File

@ -0,0 +1,45 @@
import React from 'react';
const styles = {
display: 'table-cell',
boxSizing: 'border-box',
verticalAlign: 'top',
height: 21,
outline: 'none',
border: '1px solid #ececec',
fontSize: '12px',
color: '#555',
};
class BooleanType extends React.Component {
render() {
const { knob, onChange } = this.props;
return (
<input
id={knob.name}
ref="input"
style={styles}
type="checkbox"
onChange={() => onChange(this.refs.input.checked)}
checked={knob.value}
/>
);
}
}
BooleanType.propTypes = {
knob: React.PropTypes.object,
onChange: React.PropTypes.func,
};
BooleanType.serialize = function (value) {
return String(value);
};
BooleanType.deserialize = function (value) {
if (!value) return false;
return value.trim() === 'true';
};
export default BooleanType;

View File

@ -0,0 +1,95 @@
import React from 'react';
import { SketchPicker } from 'react-color';
const styles = {
swatch: {
background: '#fff',
borderRadius: '1px',
border: '1px solid rgb(247, 244, 244)',
display: 'inline-block',
cursor: 'pointer',
width: '100%',
},
popover: {
position: 'absolute',
zIndex: '2',
},
cover: {
position: 'fixed',
top: '0px',
right: '0px',
bottom: '0px',
left: '0px',
},
};
class ColorType extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
this.onWindowMouseDown = this.onWindowMouseDown.bind(this);
this.state = {
displayColorPicker: false,
};
}
componentDidMount() {
document.addEventListener('mousedown', this.onWindowMouseDown);
}
componentWillUnmount() {
document.removeEventListener('mousedown', this.onWindowMouseDown);
}
onWindowMouseDown(e) {
if (!this.state.displayColorPicker) return;
if (this.popover.contains(e.target)) return;
this.setState({
displayColorPicker: false,
});
}
handleClick() {
this.setState({
displayColorPicker: !this.state.displayColorPicker,
});
}
render() {
const { knob, onChange } = this.props;
const colorStyle = {
width: 'auto',
height: '20px',
borderRadius: '2px',
margin: 5,
background: knob.value,
};
return (
<div id={knob.name}>
<div style={ styles.swatch } onClick={ this.handleClick }>
<div style={ colorStyle } />
</div>
{ this.state.displayColorPicker ? (
<div style={ styles.popover } ref={(e) => {this.popover = e;}}>
<SketchPicker color={ knob.value } onChange={ color => onChange(color.hex) } />
</div>
) : null }
</div>
);
}
}
ColorType.propTypes = {
knob: React.PropTypes.object,
onChange: React.PropTypes.func,
};
ColorType.serialize = function (value) {
return value;
};
ColorType.deserialize = function (value) {
return value;
};
export default ColorType;

View File

@ -0,0 +1,52 @@
import React from 'react';
import Datetime from 'react-datetime';
import insertCss from 'insert-css';
import style from './styles';
const customStyle = `
.rdt input {
outline: 0;
width: 100%;
border: 1px solid #f7f4f4;
border-radius: 2px;
font-size: 11px;
padding: 5px;
color: #555;
display: table-cell;
box-sizing: border-box;
}
`;
insertCss(style);
insertCss(customStyle);
class DateType extends React.Component {
render() {
const { knob, onChange } = this.props;
return (
<div>
<Datetime
id={knob.name}
value={knob.value ? new Date(knob.value) : null}
type="date"
onChange={(date) => onChange(date.valueOf())}
/>
</div>
);
}
}
DateType.propTypes = {
knob: React.PropTypes.object,
onChange: React.PropTypes.func,
};
DateType.serialize = function (value) {
return String(value);
};
DateType.deserialize = function (value) {
return parseFloat(value);
};
export default DateType;

View File

@ -0,0 +1,219 @@
export default `
.rdt {
position: relative;
}
.rdtPicker {
display: none;
position: absolute;
width: 200px;
padding: 4px;
margin-top: 1px;
z-index: 99999 !important;
background: #fff;
box-shadow: 0 1px 3px rgba(0,0,0,.1);
border: 1px solid #f9f9f9;
}
.rdtOpen .rdtPicker {
display: block;
}
.rdtStatic .rdtPicker {
box-shadow: none;
position: static;
}
.rdtPicker .rdtTimeToggle {
text-align: center;
font-size:11px;
}
.rdtPicker table {
width: 100%;
margin: 0;
}
.rdtPicker td,
.rdtPicker th {
text-align: center;
height: 28px;
}
.rdtPicker td {
cursor: pointer;
}
.rdtPicker td.rdtDay:hover,
.rdtPicker td.rdtHour:hover,
.rdtPicker td.rdtMinute:hover,
.rdtPicker td.rdtSecond:hover,
.rdtPicker .rdtTimeToggle:hover {
background: #eeeeee;
cursor: pointer;
}
.rdtPicker td.rdtOld,
.rdtPicker td.rdtNew {
color: #999999;
}
.rdtPicker td.rdtToday {
position: relative;
}
.rdtPicker td.rdtToday:before {
content: '';
display: inline-block;
border-left: 7px solid transparent;
border-bottom: 7px solid #428bca;
border-top-color: rgba(0, 0, 0, 0.2);
position: absolute;
bottom: 4px;
right: 4px;
}
.rdtPicker td.rdtActive,
.rdtPicker td.rdtActive:hover {
background-color: #428bca;
color: #fff;
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
}
.rdtPicker td.rdtActive.rdtToday:before {
border-bottom-color: #fff;
}
.rdtPicker td.rdtDisabled,
.rdtPicker td.rdtDisabled:hover {
background: none;
color: #999999;
cursor: not-allowed;
}
.rdtPicker td span.rdtOld {
color: #999999;
}
.rdtPicker td span.rdtDisabled,
.rdtPicker td span.rdtDisabled:hover {
background: none;
color: #999999;
cursor: not-allowed;
}
.rdtPicker th {
border-bottom: 1px solid #f9f9f9;
}
.rdtPicker .dow {
width: 14.2857%;
font-size: 11px;
border-bottom: none;
}
.rdtPicker th.rdtSwitch {
width: 100px;
font-size: 11px;
}
.rdtPicker th.rdtNext,
.rdtPicker th.rdtPrev {
font-size: 11px;
vertical-align: top;
}
.rdtPrev span,
.rdtNext span {
display: block;
-webkit-touch-callout: none; /* iOS Safari */
-webkit-user-select: none; /* Chrome/Safari/Opera */
-khtml-user-select: none; /* Konqueror */
-moz-user-select: none; /* Firefox */
-ms-user-select: none; /* Internet Explorer/Edge */
user-select: none;
}
.rdtPicker th.rdtDisabled,
.rdtPicker th.rdtDisabled:hover {
background: none;
color: #999999;
cursor: not-allowed;
}
.rdtPicker thead tr:first-child th {
cursor: pointer;
}
.rdtPicker thead tr:first-child th:hover {
background: #eeeeee;
}
.rdtPicker tfoot {
border-top: 1px solid #f9f9f9;
}
.rdtPicker button {
border: none;
background: none;
cursor: pointer;
}
.rdtPicker button:hover {
background-color: #eee;
}
.rdtPicker thead button {
width: 100%;
height: 100%;
}
td.rdtMonth,
td.rdtYear {
height: 50px;
width: 25%;
cursor: pointer;
}
td.rdtMonth:hover,
td.rdtYear:hover {
background: #eee;
}
td.rdtDay {
font-size: 11px
}
.rdtCounters {
display: inline-block;
}
.rdtCounters > div {
float: left;
}
.rdtCounter {
height: 100px;
}
.rdtCounter {
width: 40px;
}
.rdtCounterSeparator {
line-height: 100px;
}
.rdtCounter .rdtBtn {
height: 40%;
line-height: 40px;
cursor: pointer;
display: block;
font-size: 11px;
-webkit-touch-callout: none; /* iOS Safari */
-webkit-user-select: none; /* Chrome/Safari/Opera */
-khtml-user-select: none; /* Konqueror */
-moz-user-select: none; /* Firefox */
-ms-user-select: none; /* Internet Explorer/Edge */
user-select: none;
}
.rdtCounter .rdtBtn:hover {
background: #eee;
}
.rdtCounter .rdtCount {
height: 20%;
font-size: 11px;
}
.rdtMilli {
vertical-align: middle;
padding-left: 8px;
width: 48px;
}
.rdtMilli input {
width: 100%;
font-size: 11px;
margin-top: 37px;
}
`;

View File

@ -0,0 +1,75 @@
import React from 'react';
const styles = {
display: 'table-cell',
boxSizing: 'border-box',
verticalAlign: 'middle',
height: '26px',
width: '100%',
outline: 'none',
border: '1px solid #f7f4f4',
borderRadius: 2,
fontSize: 11,
padding: '5px',
color: '#444',
};
class NumberType extends React.Component {
constructor(props) {
super(props);
this.renderNormal = this.renderNormal.bind(this);
this.renderRange = this.renderRange.bind(this);
}
renderNormal() {
const { knob, onChange } = this.props;
return (<input
id={knob.name}
ref="input"
style={styles}
value={knob.value}
type="number"
onChange={() => onChange(parseFloat(this.refs.input.value))}
/>);
}
renderRange() {
const { knob, onChange } = this.props;
return (<input
id={knob.name}
ref="input"
style={styles}
value={knob.value}
type="range"
min={knob.min}
max={knob.max}
step={knob.step}
onChange={() => onChange(parseFloat(this.refs.input.value))}
/>);
}
render() {
const { knob } = this.props;
return knob.range ? this.renderRange() : this.renderNormal();
}
}
NumberType.propTypes = {
knob: React.PropTypes.object,
onChange: React.PropTypes.func,
};
NumberType.serialize = function (value) {
return String(value);
};
NumberType.deserialize = function (value) {
return parseFloat(value);
};
export default NumberType;

View File

@ -0,0 +1,94 @@
import React from 'react';
import Textarea from 'react-textarea-autosize';
import deepEqual from 'deep-equal';
const styles = {
display: 'table-cell',
boxSizing: 'border-box',
verticalAlign: 'middle',
width: '100%',
outline: 'none',
border: '1px solid #f7f4f4',
borderRadius: 2,
fontSize: 11,
padding: '5px',
color: '#555',
fontFamily: 'monospace',
};
class ObjectType extends React.Component {
constructor(...args) {
super(...args);
this.state = {};
}
getJSONString() {
const { json, jsonString } = this.state;
const { knob } = this.props;
// If there is an error in the JSON, we need to give that errored JSON.
if (this.failed) return jsonString;
// If the editor value and the knob value is the same, we need to return the
// editor value as it allow user to add new fields to the JSON.
if (deepEqual(json, knob.value)) return jsonString;
// If the knob's value is different from the editor, it seems like
// there's a outside change and we need to get that.
return JSON.stringify(knob.value, null, 2);
}
handleChange(e) {
const { onChange } = this.props;
const newState = {
jsonString: e.target.value,
};
try {
newState.json = JSON.parse(e.target.value.trim());
onChange(newState.json);
this.failed = false;
} catch (err) {
this.failed = true;
}
this.setState(newState);
}
render() {
const { knob } = this.props;
const jsonString = this.getJSONString();
const extraStyle = {};
if (this.failed) {
extraStyle.border = '1px solid #fadddd';
extraStyle.backgroundColor = '#fff5f5';
}
return (
<Textarea
id={knob.name}
ref="input"
style={{ ...styles, ...extraStyle }}
value={jsonString}
onChange={e => this.handleChange(e)}
/>
);
}
}
ObjectType.propTypes = {
knob: React.PropTypes.object,
onChange: React.PropTypes.func,
};
ObjectType.serialize = function (object) {
return JSON.stringify(object);
};
ObjectType.deserialize = function (value) {
if (!value) return {};
return JSON.parse(value);
};
export default ObjectType;

View File

@ -0,0 +1,66 @@
import React from 'react';
const styles = {
display: 'table-cell',
boxSizing: 'border-box',
verticalAlign: 'middle',
height: '26px',
width: '100%',
outline: 'none',
border: '1px solid #f7f4f4',
borderRadius: 2,
fontSize: 11,
padding: '5px',
color: '#555',
};
class SelectType extends React.Component {
_makeOpt(key, val) {
const opts = {
key,
value: key,
};
return <option {...opts}>{val}</option>;
}
_options(values) {
let data = [];
if (Array.isArray(values)) {
data = values.map(val => this._makeOpt(val, val));
} else {
data = Object.keys(values).map(key => this._makeOpt(key, values[key]));
}
return data;
}
render() {
const { knob, onChange } = this.props;
return (
<select
id={knob.name}
ref="input"
style={styles}
value={knob.value}
onChange={(e) => onChange(e.target.value)}
>
{this._options(knob.options)}
</select>
);
}
}
SelectType.propTypes = {
knob: React.PropTypes.object,
onChange: React.PropTypes.func,
};
SelectType.serialize = function (value) {
return value;
};
SelectType.deserialize = function (value) {
return value;
};
export default SelectType;

View File

@ -0,0 +1,47 @@
import React from 'react';
import Textarea from 'react-textarea-autosize';
const styles = {
display: 'table-cell',
boxSizing: 'border-box',
verticalAlign: 'middle',
height: '26px',
width: '100%',
outline: 'none',
border: '1px solid #f7f4f4',
borderRadius: 2,
fontSize: 11,
padding: '5px',
color: '#555',
};
class TextType extends React.Component {
render() {
const { knob, onChange } = this.props;
return (
<Textarea
id={knob.name}
ref="input"
style={styles}
value={knob.value}
onChange={(e) => onChange(e.target.value)}
/>
);
}
}
TextType.propTypes = {
knob: React.PropTypes.object,
onChange: React.PropTypes.func,
};
TextType.serialize = function (value) {
return value;
};
TextType.deserialize = function (value) {
return value;
};
export default TextType;

View File

@ -0,0 +1,19 @@
import TextType from './Text';
import NumberType from './Number';
import ColorType from './Color';
import BooleanType from './Boolean';
import ObjectType from './Object';
import SelectType from './Select';
import ArrayType from './Array';
import DateType from './Date';
export default {
text: TextType,
number: NumberType,
color: ColorType,
boolean: BooleanType,
object: ObjectType,
select: SelectType,
array: ArrayType,
date: DateType,
};

61
src/index.js Normal file
View File

@ -0,0 +1,61 @@
import addons from '@kadira/storybook-addons';
import KnobManager from './KnobManager';
const manager = new KnobManager();
export function knob(name, options) {
return manager.knob(name, options);
}
export function text(name, value) {
return manager.knob(name, { type: 'text', value });
}
export function boolean(name, value) {
return manager.knob(name, { type: 'boolean', value });
}
export function number(name, value, options = {}) {
const defaults = {
range: false,
min: 0,
max: 10,
step: 1,
};
const mergedOptions = { ...defaults, ...options };
const finalOptions = {
...mergedOptions,
type: 'number',
value,
};
return manager.knob(name, finalOptions);
}
export function color(name, value) {
return manager.knob(name, { type: 'color', value });
}
export function object(name, value) {
return manager.knob(name, { type: 'object', value });
}
export function select(name, options, value) {
return manager.knob(name, { type: 'select', options, value });
}
export function array(name, value, separator = ',') {
return manager.knob(name, { type: 'array', value, separator });
}
export function date(name, value = new Date()) {
const proxyValue = value ? value.getTime() : null;
return manager.knob(name, { type: 'date', value: proxyValue });
}
export function withKnobs(storyFn, context) {
const channel = addons.getChannel();
return manager.wrapStory(channel, storyFn, context);
}

14
src/register.js Normal file
View File

@ -0,0 +1,14 @@
import React from 'react';
import addons from '@kadira/storybook-addons';
import Panel from './components/Panel';
addons.register('kadirahq/storybook-addon-knobs', api => {
const channel = addons.getChannel();
addons.addPanel('kadirahq/storybook-addon-knobs', {
title: 'Knobs',
render: () => {
return <Panel channel={channel} api={api} key="knobs-panel" />;
},
});
});

99
src/tests/KnobManager.js Normal file
View File

@ -0,0 +1,99 @@
import React from 'react';
import sinon from 'sinon';
import { expect } from 'chai';
import { shallow } from 'enzyme';
import KnobManager from '../KnobManager';
const { describe, it, beforeEach } = global;
describe('KnobManager', () => {
describe('knob()', () => {
describe('when the knob is present in the knobStore', () => {
const testManager = new KnobManager();
beforeEach(() => {
testManager.knobStore = {
set: sinon.spy(),
get: () => ({
defaultValue: 'default value',
value: 'current value',
name: 'foo',
}),
};
});
it('should return the existing knob value when defaults match', () => {
const defaultKnob = {
name: 'foo',
value: 'default value',
};
const knob = testManager.knob('foo', defaultKnob);
expect(knob).to.equal('current value');
expect(testManager.knobStore.set.callCount).to.equal(0);
});
it('should return the new default knob value when default has changed', () => {
const defaultKnob = {
name: 'foo',
value: 'changed default value',
};
testManager.knob('foo', defaultKnob);
const newKnob = {
...defaultKnob,
defaultValue: defaultKnob.value,
};
expect(testManager.knobStore.set.calledWith('foo', newKnob)).to.equal(true);
});
});
describe('when the knob is not present in the knobStore', () => {
const testManager = new KnobManager();
beforeEach(() => {
testManager.knobStore = {
set: sinon.spy(),
get: sinon.stub(),
};
testManager.knobStore.get.onFirstCall().returns(undefined);
testManager.knobStore.get.onSecondCall().returns('normal value');
});
it('should return the new default knob value when default has changed', () => {
const defaultKnob = {
name: 'foo',
value: 'normal value',
};
testManager.knob('foo', defaultKnob);
const newKnob = {
...defaultKnob,
defaultValue: defaultKnob.value,
};
expect(testManager.knobStore.set.calledWith('foo', newKnob)).to.equal(true);
});
});
});
describe('wrapStory()', () => {
it('should contain the story and add correct props', () => {
const testManager = new KnobManager();
const testChannel = { emit: () => {} };
const testStory = () => (<div id="test-story">Test Content</div>);
const testContext = {
kind: 'Foo',
story: 'bar baz',
};
const wrappedStory = testManager.wrapStory(testChannel, testStory, testContext);
const wrapper = shallow(wrappedStory);
expect(wrapper.find('#test-story')).to.have.length(1);
const storyWrapperProps = wrappedStory.props;
expect(storyWrapperProps.channel).to.equal(testChannel);
expect(storyWrapperProps.context).to.equal(testContext);
});
});
});

16
src/tests/typescript.js Normal file
View File

@ -0,0 +1,16 @@
import * as tt from 'typescript-definition-tester';
const { describe, it } = global;
describe('TypeScript definitions', function () {
this.timeout(0);
it('should compile against index.d.ts', (done) => {
// This will test any typescript files in /examples/typescript against the type declarations.
tt.compileDirectory(
`${__dirname}/../../example/typescript`,
fileName => fileName.match(/\.ts$/),
() => done()
);
});
});

38
storybook-addon-knobs.d.ts vendored Normal file
View File

@ -0,0 +1,38 @@
import * as React from 'react';
interface KnobOption<T> {
value: T,
type: 'text' | 'boolean' | 'number' | 'color' | 'object' | 'select' | 'date',
}
interface StoryContext {
kind: string,
story: string,
}
export function knob<T>(name: string, options: KnobOption<T>): T;
export function text(name: string, value: string | null): string;
export function boolean(name: string, value: boolean): boolean;
export function number(name: string, value: number): number;
export function color(name: string, value: string): string;
export function object<T>(name: string, value: T): T;
export function select<T>(name: string, options: { [s: string]: T }, value: string): T;
export function select(name: string, options: string[], value: string): string;
export function date(name: string, value?: Date): Date;
interface IWrapStoryProps {
context?: Object;
storyFn?: Function;
channel?: Object;
knobStore?: Object;
initialContent?: Object;
}
export function withKnobs(storyFn: Function, context: StoryContext): React.ReactElement<IWrapStoryProps>;

14
tsconfig.json Normal file
View File

@ -0,0 +1,14 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"noImplicitAny": false,
"sourceMap": false,
"lib": [
"es2015",
"es2016",
"dom"
],
"jsx": "react"
}
}

5000
yarn.lock Normal file

File diff suppressed because it is too large Load Diff