mirror of
https://github.com/storybookjs/storybook.git
synced 2025-03-19 05:02:40 +08:00
Merge branch 'master' of github.com:storybooks/storybook-addon-knobs
# Conflicts: # .gitignore
This commit is contained in:
commit
e50269fd96
17
.eslintrc
Normal file
17
.eslintrc
Normal 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
1
.gitignore
vendored
@ -3,3 +3,4 @@ node_modules
|
||||
.idea
|
||||
npm-shrinkwrap.json
|
||||
dist
|
||||
.tern-port
|
||||
|
2
.npmignore
Normal file
2
.npmignore
Normal file
@ -0,0 +1,2 @@
|
||||
src
|
||||
.babelrc
|
11
.scripts/get_gh_pages_url.js
Normal file
11
.scripts/get_gh_pages_url.js
Normal 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
33
.scripts/mocha_runner.js
Normal 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
16
.scripts/prepublish.sh
Normal 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
|
47
.scripts/publish_storybook.sh
Normal file
47
.scripts/publish_storybook.sh
Normal 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`"
|
1
.scripts/user/prepublish.sh
Normal file
1
.scripts/user/prepublish.sh
Normal file
@ -0,0 +1 @@
|
||||
# Use this file to your own code to run at NPM `prepublish` event.
|
1
.scripts/user/pretest.js
Normal file
1
.scripts/user/pretest.js
Normal file
@ -0,0 +1 @@
|
||||
// Use this file to setup any test utilities.
|
2
.storybook/addons.js
Normal file
2
.storybook/addons.js
Normal file
@ -0,0 +1,2 @@
|
||||
import '../src/register';
|
||||
import '@kadira/storybook/addons'
|
12
.storybook/config.js
Normal file
12
.storybook/config.js
Normal 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);
|
4
.storybook/user/modify_webpack_config.js
Normal file
4
.storybook/user/modify_webpack_config.js
Normal 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.
|
||||
};
|
23
.storybook/webpack.config.js
Normal file
23
.storybook/webpack.config.js
Normal 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
88
CHANGELOG.md
Normal 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
24
CONTRIBUTING.md
Normal 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
21
LICENSE
Normal 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
212
README.md
Normal file
@ -0,0 +1,212 @@
|
||||
# Storybook Addon Knobs [](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:
|
||||
|
||||
[](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.
|
||||
|
||||

|
||||
|
||||
### 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
117
dist/KnobManager.js
vendored
Normal 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
86
dist/KnobStore.js
vendored
Normal 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
207
dist/components/Panel.js
vendored
Normal 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
128
dist/components/PropField.js
vendored
Normal 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
100
dist/components/PropForm.js
vendored
Normal 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
129
dist/components/WrapStory.js
vendored
Normal 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
93
dist/components/types/Array.js
vendored
Normal 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
91
dist/components/types/Boolean.js
vendored
Normal 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
152
dist/components/types/Color.js
vendored
Normal 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
95
dist/components/types/Date/index.js
vendored
Normal 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
6
dist/components/types/Date/styles.js
vendored
Normal 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
130
dist/components/types/Number.js
vendored
Normal 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
163
dist/components/types/Object.js
vendored
Normal 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
130
dist/components/types/Select.js
vendored
Normal 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
94
dist/components/types/Text.js
vendored
Normal 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
50
dist/components/types/index.js
vendored
Normal 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
94
dist/index.js
vendored
Normal 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
26
dist/register.js
vendored
Normal 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
BIN
docs/demo.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 709 KiB |
BIN
docs/demo.png
Normal file
BIN
docs/demo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 143 KiB |
BIN
docs/storybook-knobs-example.png
Normal file
BIN
docs/storybook-knobs-example.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 230 KiB |
103
example/stories/index.js
Normal file
103
example/stories/index.js
Normal 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>
|
||||
));
|
99
example/typescript/index.tsx
Normal file
99
example/typescript/index.tsx
Normal 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
73
package.json
Normal 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
1
register.js
Normal file
@ -0,0 +1 @@
|
||||
require('./dist/register');
|
75
src/KnobManager.js
Normal file
75
src/KnobManager.js
Normal 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
47
src/KnobStore.js
Normal 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
130
src/components/Panel.js
Normal 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,
|
||||
};
|
68
src/components/PropField.js
Normal file
68
src/components/PropField.js
Normal 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,
|
||||
};
|
55
src/components/PropForm.js
Normal file
55
src/components/PropForm.js
Normal 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,
|
||||
};
|
68
src/components/WrapStory.js
Normal file
68
src/components/WrapStory.js
Normal 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,
|
||||
};
|
18
src/components/tests/Array.js
Normal file
18
src/components/tests/Array.js
Normal 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);
|
||||
});
|
||||
});
|
142
src/components/tests/Panel.js
Normal file
142
src/components/tests/Panel.js
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
46
src/components/types/Array.js
Normal file
46
src/components/types/Array.js
Normal 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;
|
45
src/components/types/Boolean.js
Normal file
45
src/components/types/Boolean.js
Normal 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;
|
95
src/components/types/Color.js
Normal file
95
src/components/types/Color.js
Normal 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;
|
52
src/components/types/Date/index.js
Normal file
52
src/components/types/Date/index.js
Normal 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;
|
219
src/components/types/Date/styles.js
Normal file
219
src/components/types/Date/styles.js
Normal 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;
|
||||
}
|
||||
`;
|
75
src/components/types/Number.js
Normal file
75
src/components/types/Number.js
Normal 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;
|
94
src/components/types/Object.js
Normal file
94
src/components/types/Object.js
Normal 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;
|
66
src/components/types/Select.js
Normal file
66
src/components/types/Select.js
Normal 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;
|
47
src/components/types/Text.js
Normal file
47
src/components/types/Text.js
Normal 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;
|
19
src/components/types/index.js
Normal file
19
src/components/types/index.js
Normal 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
61
src/index.js
Normal 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
14
src/register.js
Normal 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
99
src/tests/KnobManager.js
Normal 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
16
src/tests/typescript.js
Normal 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
38
storybook-addon-knobs.d.ts
vendored
Normal 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
14
tsconfig.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"noImplicitAny": false,
|
||||
"sourceMap": false,
|
||||
"lib": [
|
||||
"es2015",
|
||||
"es2016",
|
||||
"dom"
|
||||
],
|
||||
"jsx": "react"
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user