diff --git a/.eslintignore b/.eslintignore index e2549766926e..5c6899227ef0 100644 --- a/.eslintignore +++ b/.eslintignore @@ -6,6 +6,8 @@ node_modules app/**/demo/** docs/public +vue + *.bundle.js *.js.map diff --git a/CHANGELOG.md b/CHANGELOG.md index 89a833a7993a..5833dfabd522 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,75 @@ +# 3.2.0 + +2017-July-27 + +Storybook 3.2 is filled with new features to help make your components shine! Headline features: + +- Vue support [#1267](https://github.com/storybooks/storybook/pull/1267) +- Story Hierarchy [#1329](https://github.com/storybooks/storybook/pull/1329) +- React Native On Device UI [#1413](https://github.com/storybooks/storybook/pull/1413) + +Plus many more features, documentation improvements, and bugfixes below! + +#### Features + +- Vue support [#1267](https://github.com/storybooks/storybook/pull/1267) +- Add support for vue in addon-notes [#1278](https://github.com/storybooks/storybook/pull/1278) +- CLI support for Vue [#1287](https://github.com/storybooks/storybook/pull/1287) + + +- Story Hierarchy [#1329](https://github.com/storybooks/storybook/pull/1329) +- Story Hierarchy UI improvements [#1387](https://github.com/storybooks/storybook/pull/1387) [#1356](https://github.com/storybooks/storybook/pull/1356) +- Story Hierarchy - keyboard accessibility [#1427](https://github.com/storybooks/storybook/pull/1427) + + +- React Native - On Device UI [#1413](https://github.com/storybooks/storybook/pull/1413) +- Show first story on RN OnDeviceUI startup [#1510](https://github.com/storybooks/storybook/pull/1510) + + +- Add warning when module is missing in storiesOf [#1525](https://github.com/storybooks/storybook/pull/1525) +- Provide styling hook for Addon Info story body [#1308](https://github.com/storybooks/storybook/pull/1308) +- Implement filtering on story-level [#1432](https://github.com/storybooks/storybook/pull/1432) +- Refactoring of `addon-info` [#1452](https://github.com/storybooks/storybook/pull/1452) +- ADD storybook logo for inside terminal for future CLI or easteregg [#1499](https://github.com/storybooks/storybook/pull/1499) +- Improved error checking in global addDecorator [#1481](https://github.com/storybooks/storybook/pull/1481) + +#### Bug Fixes + +- Fix react native example and bootstrapping [#1514](https://github.com/storybooks/storybook/pull/1514) +- Fix a 'funny' hmr issue in cra-kitchen-sink [#1508](https://github.com/storybooks/storybook/pull/1508) +- When timestamps are enabled, it actually checks them before applying changes [#1405](https://github.com/storybooks/storybook/pull/1405) +- Fix issue when extending webpack config [#1468](https://github.com/storybooks/storybook/pull/1468) +- Fix addon notes [#1448](https://github.com/storybooks/storybook/pull/1448) +- Story Hierarchy - initial state bug fix [#1401](https://github.com/storybooks/storybook/pull/1401) +- Remove blue outline when node is focused [#1497](https://github.com/storybooks/storybook/pull/1497) + +#### Documentation + +- Add hierarchySeparator to README [#1445](https://github.com/storybooks/storybook/pull/1445) +- Document null addons channel in FAQ [#1507](https://github.com/storybooks/storybook/pull/1507) + +#### Maintenance + +- Revert knobs API to previous API. [#1527](https://github.com/storybooks/storybook/pull/1527) +- FIX hoist-internals: remove existing folder/link before linking [#1516](https://github.com/storybooks/storybook/pull/1516) +- Update global hook for Vue Devtools [#1376](https://github.com/storybooks/storybook/pull/1376) +- SWITCH to circleci over travisCI && CHANGE lerna bootstrap procedure: [#1486](https://github.com/storybooks/storybook/pull/1486) +- Update cra-kitchen-sink package versions for 3.2-alpha [#1434](https://github.com/storybooks/storybook/pull/1434) +- Updating 3.2 alpha release with patches [#1419](https://github.com/storybooks/storybook/pull/1419) +- Remove typescript typings for @storybook/addon-notes [#1344](https://github.com/storybooks/storybook/pull/1344) +- Remove typescript typings for @storybook/addon-options [#1343](https://github.com/storybooks/storybook/pull/1343) +- Remove typescript typings for @storybook/addon-knobs [#1339](https://github.com/storybooks/storybook/pull/1339) +- Remove typescript typings for @storybook/addon-links [#1342](https://github.com/storybooks/storybook/pull/1342) + +#### Dependency Upgrades + +- Updated babel-plugin-react-docgen version [#1526](https://github.com/storybooks/storybook/pull/1526) +- UPDATE everything (including eslint 4) [#1517](https://github.com/storybooks/storybook/pull/1517) +- Update remark-preset-lint-recommended to the latest version 🚀 [#1512](https://github.com/storybooks/storybook/pull/1512) +- Update remark-cli to the latest version 🚀 [#1498](https://github.com/storybooks/storybook/pull/1498) +- Remove upper bound on react-native peerDependency [#1424](https://github.com/storybooks/storybook/pull/1424) +- Bump `react-split-pane` version [#1495](https://github.com/storybooks/storybook/pull/1495) + # 3.1.9 2017-July-16 diff --git a/README.md b/README.md index 0327818b0d5d..45e01f83d876 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ It allows you to browse a component library, view the different states of each c Storybook runs outside of your app. This allows you to develop UI components in isolation, which can improve component reuse, testability, and development speed. You can build quickly without having to worry about application-specific dependencies. -Here are some featured examples that you can reference to see how Storybook works: https://storybook.js.org/examples/ +Here are some featured examples that you can reference to see how Storybook works: Storybook comes with a lot of [addons](https://storybook.js.org/addons/introduction/) for component design, documentation, testing, interactivity, and so on. Storybook's easy-to-use API makes it easy to configure and extend in various ways. It has even been extended to support React Native development for mobile. @@ -48,11 +48,13 @@ getstorybook Once it's installed, you can `npm run storybook` and it will run the development server on your local machine, and give you a URL to browse some sample stories. **Storybook v2.x migration note**: If you're using Storybook v2.x and want to shift to 3.x version the easiest way is: + ```sh npm i -g @storybook/cli cd my-storybook-v2-app getstorybook ``` + It runs a codemod to update all package names. Read all migration details in our [Migration Guide](MIGRATION.md) For full documentation on using Storybook visit: [storybook.js.org](https://storybook.js.org) diff --git a/addons/actions/package.json b/addons/actions/package.json index fa3d45b734d3..0a3aa9019ed8 100644 --- a/addons/actions/package.json +++ b/addons/actions/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-actions", - "version": "3.1.9", + "version": "3.2.0-alpha.10", "description": "Action Logger addon for storybook", "keywords": [ "storybook" @@ -21,7 +21,7 @@ "storybook": "start-storybook -p 9001" }, "dependencies": { - "@storybook/addons": "^3.1.6", + "@storybook/addons": "^3.2.0-alpha.10", "deep-equal": "^1.0.1", "json-stringify-safe": "^5.0.1", "prop-types": "^15.5.10", diff --git a/addons/centered/package.json b/addons/centered/package.json index 145bef873732..f039ef9e8441 100644 --- a/addons/centered/package.json +++ b/addons/centered/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-centered", - "version": "3.1.9", + "version": "3.2.0-alpha.8", "description": "Storybook decorator to center components", "license": "MIT", "author": "Muhammed Thanish ", diff --git a/addons/comments/package.json b/addons/comments/package.json index aa84a6204c5c..2a0b006f3607 100644 --- a/addons/comments/package.json +++ b/addons/comments/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-comments", - "version": "3.1.9", + "version": "3.2.0-alpha.10", "description": "Comments addon for Storybook", "keywords": [ "storybook" @@ -23,7 +23,7 @@ "storybook-remote": "start-storybook -p 3006" }, "dependencies": { - "@storybook/addons": "^3.1.6", + "@storybook/addons": "^3.2.0-alpha.10", "babel-runtime": "^6.23.0", "deep-equal": "^1.0.1", "events": "^1.1.1", diff --git a/addons/events/package.json b/addons/events/package.json index 38e38b904614..c1f7c3841f99 100644 --- a/addons/events/package.json +++ b/addons/events/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-events", - "version": "3.1.9", + "version": "3.2.0-alpha.10", "description": "Add events to your Storybook stories.", "keywords": [ "addon", @@ -20,7 +20,7 @@ "storybook": "start-storybook -p 6006" }, "dependencies": { - "@storybook/addons": "^3.1.6", + "@storybook/addons": "^3.2.0-alpha.10", "babel-runtime": "^6.23.0", "format-json": "^1.0.3", "prop-types": "^15.5.10", diff --git a/addons/graphql/package.json b/addons/graphql/package.json index c875dfcd4c81..f93758570fbc 100644 --- a/addons/graphql/package.json +++ b/addons/graphql/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-graphql", - "version": "3.1.6", + "version": "3.2.0-alpha.10", "description": "Storybook addon to display the GraphiQL IDE", "keywords": [ "storybook" diff --git a/addons/info/package.json b/addons/info/package.json index ebb90ed34f32..4716d5122771 100644 --- a/addons/info/package.json +++ b/addons/info/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-info", - "version": "3.1.9", + "version": "3.2.0-alpha.10", "description": "A Storybook addon to show additional information for your stories.", "license": "MIT", "main": "dist/index.js", @@ -14,12 +14,13 @@ "storybook": "start-storybook -p 9010" }, "dependencies": { - "@storybook/addons": "^3.1.6", + "@storybook/addons": "^3.2.0-alpha.10", "babel-runtime": "^6.23.0", "global": "^4.3.2", "marksy": "^2.0.0", "prop-types": "^15.5.10", - "react-addons-create-fragment": "^15.5.3" + "react-addons-create-fragment": "^15.5.3", + "util-deprecate": "^1.0.2" }, "devDependencies": { "git-url-parse": "^6.2.2", diff --git a/addons/info/src/components/PropVal.js b/addons/info/src/components/PropVal.js index 3cab672ea3f4..1e825f23eb2e 100644 --- a/addons/info/src/components/PropVal.js +++ b/addons/info/src/components/PropVal.js @@ -137,9 +137,16 @@ export default function PropVal(props) { ); } +PropVal.defaultProps = { + val: null, + maxPropObjectKeys: 3, + maxPropArrayLength: 3, + maxPropStringLength: 50, +}; + PropVal.propTypes = { - val: PropTypes.any.isRequired, // eslint-disable-line - maxPropObjectKeys: PropTypes.number.isRequired, - maxPropArrayLength: PropTypes.number.isRequired, - maxPropStringLength: PropTypes.number.isRequired, + val: PropTypes.any, // eslint-disable-line + maxPropObjectKeys: PropTypes.number, + maxPropArrayLength: PropTypes.number, + maxPropStringLength: PropTypes.number, }; diff --git a/addons/info/src/index.js b/addons/info/src/index.js index 4567ed366ba5..3ff306f3ee55 100644 --- a/addons/info/src/index.js +++ b/addons/info/src/index.js @@ -1,7 +1,12 @@ import React from 'react'; +import deprecate from 'util-deprecate'; import _Story from './components/Story'; import { H1, H2, H3, H4, H5, H6, Code, P, UL, A, LI } from './components/markdown'; +function addonCompose(addonFn) { + return storyFn => context => addonFn(storyFn, context); +} + export const Story = _Story; const defaultOptions = { @@ -29,59 +34,62 @@ const defaultMarksyConf = { ul: UL, }; -export default { - addWithInfo(storyName, info, storyFn, _options) { - if (typeof storyFn !== 'function') { - if (typeof info === 'function') { +export function addInfo(storyFn, context, info, _options) { + if (typeof storyFn !== 'function') { + if (typeof info === 'function') { _options = storyFn; // eslint-disable-line storyFn = info; // eslint-disable-line info = ''; // eslint-disable-line - } else { - throw new Error('No story defining function has been specified'); - } + } else { + throw new Error('No story defining function has been specified'); } + } - const options = { - ...defaultOptions, - ..._options, - }; + const options = { + ...defaultOptions, + ..._options, + }; - // props.propTables can only be either an array of components or null - // propTables option is allowed to be set to 'false' (a boolean) - // if the option is false, replace it with null to avoid react warnings - if (!options.propTables) { - options.propTables = null; - } + // props.propTables can only be either an array of components or null + // propTables option is allowed to be set to 'false' (a boolean) + // if the option is false, replace it with null to avoid react warnings + if (!options.propTables) { + options.propTables = null; + } - const marksyConf = { ...defaultMarksyConf }; - if (options && options.marksyConf) { - Object.assign(marksyConf, options.marksyConf); - } + const marksyConf = { ...defaultMarksyConf }; + if (options && options.marksyConf) { + Object.assign(marksyConf, options.marksyConf); + } + const props = { + info, + context, + showInline: Boolean(options.inline), + showHeader: Boolean(options.header), + showSource: Boolean(options.source), + propTables: options.propTables, + propTablesExclude: options.propTablesExclude, + styles: typeof options.styles === 'function' ? options.styles : s => s, + marksyConf, + maxPropObjectKeys: options.maxPropObjectKeys, + maxPropArrayLength: options.maxPropArrayLength, + maxPropsIntoLine: options.maxPropsIntoLine, + maxPropStringLength: options.maxPropStringLength, + }; + return ( + + {storyFn(context)} + + ); +} - return this.add(storyName, context => { - const props = { - info, - context, - showInline: Boolean(options.inline), - showHeader: Boolean(options.header), - showSource: Boolean(options.source), - propTables: options.propTables, - propTablesExclude: options.propTablesExclude, - styles: typeof options.styles === 'function' ? options.styles : s => s, - marksyConf, - maxPropObjectKeys: options.maxPropObjectKeys, - maxPropArrayLength: options.maxPropArrayLength, - maxPropsIntoLine: options.maxPropsIntoLine, - maxPropStringLength: options.maxPropStringLength, - }; +export const withInfo = (info, _options) => + addonCompose((storyFn, context) => addInfo(storyFn, context, info, _options)); - return ( - - {storyFn(context)} - - ); - }); - }, +export default { + addWithInfo: deprecate(function addWithInfo(storyName, info, storyFn, _options) { + return this.add(storyName, withInfo(info, _options)(storyFn)); + }, '@storybook/addon-info .addWithInfo() addon is deprecated, use withInfo() from the same package instead. \nSee https://github.com/storybooks/storybook/tree/master/addons/info'), }; export function setDefaults(newDefaults) { diff --git a/addons/info/src/index.test.js b/addons/info/src/index.test.js new file mode 100644 index 000000000000..afbae389ce2c --- /dev/null +++ b/addons/info/src/index.test.js @@ -0,0 +1,59 @@ +/* global document */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import AddonInfo, { withInfo, setDefaults, addInfo } from './'; + +/* eslint-disable */ +const TestComponent = ({ func, obj, array, number, string, bool, empty }) => +
+

{func}

+

{obj.toString()}

+

{array}

+

{number}

+
{string}
+
{bool}
+

{empty}

+ test + storiesOf + +
  • 1
  • +
  • 2
  • +
    +
    ; +/* eslint-enable */ + +const testContext = { kind: 'addon_info', story: 'jest_test' }; +const testOptions = { propTables: false }; + +describe('addon Info', () => { + const story = context => +
    + It's a {context.story} story: + x + 1} + obj={{ a: 'a', b: 'b' }} + array={[1, 2, 3]} + number={7} + string={'seven'} + bool + /> +
    ; + const api = { + add: (name, fn) => fn(testContext), + }; + it('should render and markdown', () => { + const Info = withInfo( + '# Test story \n## with markdown info \ncontaing **bold**, *cursive* text and `code`' + )(story); + ReactDOM.render(, document.createElement('div')); + }); + it('should render with missed info', () => { + setDefaults(testOptions); + addInfo(null, testContext, story, testOptions); + }); + it('should show deprecation warning', () => { + const addWithInfo = AddonInfo.addWithInfo.bind(api); + addWithInfo('jest', story); + }); +}); diff --git a/addons/knobs/README.md b/addons/knobs/README.md index b7e530db4778..4f2a69ccceef 100644 --- a/addons/knobs/README.md +++ b/addons/knobs/README.md @@ -233,10 +233,11 @@ const value = date(label, defaultValue); If you feel like this addon is not performing well enough there is an option to use `withKnobsOptions` instead of `withKnobs`. Usage: -``` + +```js story.addDecorator(withKnobsOptions({ - debounce: { wait: number, leading: boolean}, // Same as lodash debounce. - timestamps: true // Doesn't emit events while user is typing. + debounce: { wait: number, leading: boolean}, // Same as lodash debounce. + timestamps: true // Doesn't emit events while user is typing. })); ``` diff --git a/addons/knobs/package.json b/addons/knobs/package.json index 753f5b258739..790a92492724 100644 --- a/addons/knobs/package.json +++ b/addons/knobs/package.json @@ -1,10 +1,9 @@ { "name": "@storybook/addon-knobs", - "version": "3.1.9", + "version": "3.2.0-alpha.10", "description": "Storybook Addon Prop Editor Component", "license": "MIT", "main": "dist/index.js", - "typings": "./storybook-addon-knobs.d.ts", "repository": { "type": "git", "url": "https://github.com/storybooks/storybook.git" @@ -16,7 +15,7 @@ "storybook": "start-storybook -p 9010" }, "dependencies": { - "@storybook/addons": "^3.1.6", + "@storybook/addons": "^3.2.0-alpha.10", "babel-runtime": "^6.23.0", "deep-equal": "^1.0.1", "global": "^4.3.2", @@ -26,7 +25,8 @@ "prop-types": "^15.5.10", "react-color": "^2.11.4", "react-datetime": "^2.8.10", - "react-textarea-autosize": "^4.3.0" + "react-textarea-autosize": "^4.3.0", + "util-deprecate": "^1.0.2" }, "devDependencies": { "@types/node": "^7.0.12", @@ -37,7 +37,8 @@ "react-dom": "^15.6.1", "style-loader": "^0.17.0", "typescript": "^2.2.2", - "typescript-definition-tester": "^0.0.5" + "typescript-definition-tester": "^0.0.5", + "vue": "^2.4.1" }, "peerDependencies": { "react": "*", diff --git a/addons/knobs/src/KnobManager.js b/addons/knobs/src/KnobManager.js index b314b16c7166..73ae03687e07 100644 --- a/addons/knobs/src/KnobManager.js +++ b/addons/knobs/src/KnobManager.js @@ -1,17 +1,14 @@ /* eslint no-underscore-dangle: 0 */ - -import React from 'react'; import deepEqual from 'deep-equal'; -import WrapStory from './components/WrapStory'; import KnobStore from './KnobStore'; // 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 = {}; + constructor(channel) { + this.channel = channel; + this.knobStore = new KnobStore(); } knob(name, options) { @@ -37,22 +34,6 @@ export default class KnobManager { 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(); // eslint-disable-line - } - - this.knobStore = knobStore; - knobStore.markAllUnused(); - const initialContent = storyFn(context); - const props = { context, storyFn, channel, knobStore, initialContent }; - return ; - } - _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 diff --git a/addons/knobs/src/KnobManager.test.js b/addons/knobs/src/KnobManager.test.js index 85337a73ad7f..5cf64a26a477 100644 --- a/addons/knobs/src/KnobManager.test.js +++ b/addons/knobs/src/KnobManager.test.js @@ -1,4 +1,3 @@ -import React from 'react'; import { shallow } from 'enzyme'; // eslint-disable-line import KnobManager from './KnobManager'; @@ -74,24 +73,4 @@ describe('KnobManager', () => { }); }); }); - - describe('wrapStory()', () => { - it('should contain the story and add correct props', () => { - const testManager = new KnobManager(); - - const testChannel = { emit: () => {} }; - const testStory = () =>
    Test Content
    ; - const testContext = { - kind: 'Foo', - story: 'bar baz', - }; - const wrappedStory = testManager.wrapStory(testChannel, testStory, testContext); - const wrapper = shallow(wrappedStory); - expect(wrapper.find('#test-story').length).toBe(1); - - const storyWrapperProps = wrappedStory.props; - expect(storyWrapperProps.channel).toEqual(testChannel); - expect(storyWrapperProps.context).toEqual(testContext); - }); - }); }); diff --git a/addons/knobs/src/components/Panel.js b/addons/knobs/src/components/Panel.js index 8c4a0bf15783..caeda402b827 100644 --- a/addons/knobs/src/components/Panel.js +++ b/addons/knobs/src/components/Panel.js @@ -57,10 +57,16 @@ export default class Panel extends React.Component { this.loadedFromUrl = false; this.props.channel.on('addon:knobs:setKnobs', this.setKnobs); this.props.channel.on('addon:knobs:setOptions', this.setOptions); + + this.stopListeningOnStory = this.props.api.onStory(() => { + this.setState({ knobs: [] }); + this.props.channel.emit('addon:knobs:reset'); + }); } componentWillUnmount() { this.props.channel.removeListener('addon:knobs:setKnobs', this.setKnobs); + this.stopListeningOnStory(); } setOptions(options = { debounce: false, timestamps: false }) { @@ -156,6 +162,7 @@ Panel.propTypes = { }).isRequired, onReset: PropTypes.object, // eslint-disable-line api: PropTypes.shape({ + onStory: PropTypes.func, getQueryParam: PropTypes.func, setQueryParams: PropTypes.func, }).isRequired, diff --git a/addons/knobs/src/components/__tests__/Panel.js b/addons/knobs/src/components/__tests__/Panel.js index 05bf727945f7..6994937e810b 100644 --- a/addons/knobs/src/components/__tests__/Panel.js +++ b/addons/knobs/src/components/__tests__/Panel.js @@ -5,7 +5,17 @@ import Panel from '../Panel'; describe('Panel', () => { it('should subscribe to setKnobs event of channel', () => { const testChannel = { on: jest.fn() }; - shallow(); + const testApi = { onStory: jest.fn() }; + shallow(); + expect(testChannel.on).toHaveBeenCalledWith('addon:knobs:setKnobs', jasmine.any(Function)); + }); + + it('should subscribe to onStory event', () => { + const testChannel = { on: jest.fn() }; + const testApi = { onStory: jest.fn() }; + shallow(); + + expect(testApi.onStory).toHaveBeenCalled(); expect(testChannel.on).toHaveBeenCalledWith('addon:knobs:setKnobs', jasmine.any(Function)); }); @@ -28,6 +38,7 @@ describe('Panel', () => { const testApi = { getQueryParam: key => testQueryParams[key], setQueryParams: jest.fn(), + onStory: jest.fn(), }; shallow(); @@ -74,6 +85,7 @@ describe('Panel', () => { const testApi = { getQueryParam: key => testQueryParams[key], setQueryParams: jest.fn(), + onStory: jest.fn(), }; const wrapper = shallow(); @@ -115,6 +127,7 @@ describe('Panel', () => { const testApi = { getQueryParam: jest.fn(), setQueryParams: jest.fn(), + onStory: jest.fn(), }; const wrapper = shallow(); diff --git a/addons/knobs/src/index.js b/addons/knobs/src/index.js index 8e8239d244cd..d508841a8ae4 100644 --- a/addons/knobs/src/index.js +++ b/addons/knobs/src/index.js @@ -1,7 +1,11 @@ +import { window } from 'global'; import addons from '@storybook/addons'; import KnobManager from './KnobManager'; +import { vueHandler } from './vue'; +import { reactHandler } from './react'; -const manager = new KnobManager(); +const channel = addons.getChannel(); +const manager = new KnobManager(channel); export function knob(name, options) { return manager.knob(name, options); @@ -55,16 +59,29 @@ export function date(name, value = new Date()) { return manager.knob(name, { type: 'date', value: proxyValue }); } +// "Higher order component" / wrapper style API +// In 3.3, this will become `withKnobs`, once our decorator API supports it. +// See https://github.com/storybooks/storybook/pull/1527 +function wrapperKnobs(options) { + if (options) channel.emit('addon:knobs:setOptions', options); + + switch (window.STORYBOOK_ENV) { + case 'vue': { + return vueHandler(channel, manager.knobStore); + } + case 'react': { + return reactHandler(channel, manager.knobStore); + } + default: { + return reactHandler(channel, manager.knobStore); + } + } +} + export function withKnobs(storyFn, context) { - const channel = addons.getChannel(); - return manager.wrapStory(channel, storyFn, context); + return wrapperKnobs()(storyFn)(context); } export function withKnobsOptions(options = {}) { - return (...args) => { - const channel = addons.getChannel(); - channel.emit('addon:knobs:setOptions', options); - - return withKnobs(...args); - }; + return (storyFn, context) => wrapperKnobs(options)(storyFn)(context); } diff --git a/addons/knobs/src/components/WrapStory.js b/addons/knobs/src/react/WrapStory.js similarity index 100% rename from addons/knobs/src/components/WrapStory.js rename to addons/knobs/src/react/WrapStory.js diff --git a/addons/knobs/src/react/index.js b/addons/knobs/src/react/index.js new file mode 100644 index 000000000000..0b093d446ddc --- /dev/null +++ b/addons/knobs/src/react/index.js @@ -0,0 +1,11 @@ +import React from 'react'; +import WrapStory from './WrapStory'; + +/** + * Handles a react story + */ +export const reactHandler = (channel, knobStore) => getStory => context => { + const initialContent = getStory(context); + const props = { context, storyFn: getStory, channel, knobStore, initialContent }; + return ; +}; diff --git a/addons/knobs/src/react/index.test.js b/addons/knobs/src/react/index.test.js new file mode 100644 index 000000000000..298cf1379d79 --- /dev/null +++ b/addons/knobs/src/react/index.test.js @@ -0,0 +1,27 @@ +import React from 'react'; +import { reactHandler } from './index'; +import { shallow } from 'enzyme'; // eslint-disable-line +import KnobStore from '../KnobStore'; + +describe('React Handler', () => { + describe('wrapStory', () => { + it('should contain the story and add correct props', () => { + const testChannel = { emit: () => {} }; + const testStory = () =>
    Test Content
    ; + const testContext = { + kind: 'Foo', + story: 'bar baz', + }; + + const testStore = new KnobStore(); + + const wrappedStory = reactHandler(testChannel, testStore)(testStory)(testContext); + const wrapper = shallow(wrappedStory); + expect(wrapper.find('#test-story').length).toBe(1); + + const storyWrapperProps = wrappedStory.props; + expect(storyWrapperProps.channel).toEqual(testChannel); + expect(storyWrapperProps.context).toEqual(testContext); + }); + }); +}); diff --git a/addons/knobs/src/vue/index.js b/addons/knobs/src/vue/index.js new file mode 100644 index 000000000000..1c76ff5fcb72 --- /dev/null +++ b/addons/knobs/src/vue/index.js @@ -0,0 +1,37 @@ +export const vueHandler = (channel, knobStore) => getStory => context => ({ + render(h) { + return h(getStory(context)); + }, + + methods: { + onKnobChange(change) { + const { name, value } = change; + // Update the related knob and it's value. + const knobOptions = knobStore.get(name); + knobOptions.value = value; + this.$forceUpdate(); + }, + + onKnobReset() { + knobStore.reset(); + this.setPaneKnobs(false); + this.$forceUpdate(); + }, + + setPaneKnobs(timestamp = +new Date()) { + channel.emit('addon:knobs:setKnobs', { knobs: knobStore.getAll(), timestamp }); + }, + }, + + created() { + channel.on('addon:knobs:reset', this.onKnobReset); + channel.on('addon:knobs:knobChange', this.onKnobChange); + knobStore.subscribe(this.setPaneKnobs); + }, + + beforeDestroy(){ + channel.removeListener('addon:knobs:reset', this.onKnobReset); + channel.removeListener('addon:knobs:knobChange', this.onKnobChange); + knobStore.unsubscribe(this.setPaneKnobs); + } +}); \ No newline at end of file diff --git a/addons/knobs/src/vue/index.test.js b/addons/knobs/src/vue/index.test.js new file mode 100644 index 000000000000..a43e1f4b9a76 --- /dev/null +++ b/addons/knobs/src/vue/index.test.js @@ -0,0 +1,39 @@ +import Vue from 'vue'; +import { vueHandler } from './index'; +import KnobStore from '../KnobStore'; + +describe('Vue handler', () => { + it('Returns a component with a created function', () => { + const testChannel = { emit: () => {} }; + const testStory = () => ({ template: '
    testStory
    ' }); + const testContext = { + kind: 'Foo', + story: 'bar baz', + }; + + const testStore = new KnobStore(); + const component = vueHandler(testChannel, testStore)(testStory)(testContext); + + expect(component).toMatchObject({ + created: expect.any(Function), + beforeDestroy: expect.any(Function), + render: expect.any(Function), + }); + }); + + it('Subscribes to the channel on creation', () => { + const testChannel = { emit: () => {}, on: jest.fn() }; + const testStory = () => ({ render: (h) => h('div', ['testStory']) }); + const testContext = { + kind: 'Foo', + story: 'bar baz', + }; + + const testStore = new KnobStore(); + const component = new Vue(vueHandler(testChannel, testStore)(testStory)(testContext)).$mount(); + + expect(testChannel.on).toHaveBeenCalledTimes(2); + expect(testChannel.on).toHaveBeenCalledWith('addon:knobs:reset', expect.any(Function)); + expect(testChannel.on).toHaveBeenCalledWith('addon:knobs:knobChange', expect.any(Function)); + }); +}); diff --git a/addons/knobs/storybook-addon-knobs.d.ts b/addons/knobs/storybook-addon-knobs.d.ts deleted file mode 100644 index 37d0f8e751d3..000000000000 --- a/addons/knobs/storybook-addon-knobs.d.ts +++ /dev/null @@ -1,46 +0,0 @@ -import * as React from 'react'; - -interface KnobOption { - value: T, - type: 'text' | 'boolean' | 'number' | 'color' | 'object' | 'select' | 'date', -} - -interface StoryContext { - kind: string, - story: string, -} - -interface NumberOptions { - range: boolean, - min: number, - max: number, - step: number, -} - -export function knob(name: string, options: KnobOption): 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, options?: NumberOptions): number; - -export function color(name: string, value: string): string; - -export function object(name: string, value: T): T; - -export function select(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; -export function withKnobsOptions(options: Object): (storyFn: Function, context: StoryContext) => React.ReactElement; diff --git a/addons/links/package.json b/addons/links/package.json index e5b9eea66598..4520d8443b98 100644 --- a/addons/links/package.json +++ b/addons/links/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-links", - "version": "3.1.6", + "version": "3.2.0-alpha.10", "description": "Story Links addon for storybook", "keywords": [ "storybook" @@ -11,7 +11,6 @@ }, "license": "MIT", "main": "dist/index.js", - "typings": "./storybook-addon-links.d.ts", "repository": { "type": "git", "url": "https://github.com/storybooks/storybook.git" @@ -22,7 +21,7 @@ "storybook": "start-storybook -p 9001" }, "dependencies": { - "@storybook/addons": "^3.1.6" + "@storybook/addons": "^3.2.0-alpha.10" }, "devDependencies": { "react": "^15.6.1", diff --git a/addons/links/storybook-addon-links.d.ts b/addons/links/storybook-addon-links.d.ts deleted file mode 100644 index d1b3a01bf2ed..000000000000 --- a/addons/links/storybook-addon-links.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -import * as React from 'react'; - -export function linkTo(book: string, kind?: string): React.MouseEventHandler; diff --git a/addons/notes/README.md b/addons/notes/README.md index 94826bc20f08..dffefb10732a 100644 --- a/addons/notes/README.md +++ b/addons/notes/README.md @@ -31,17 +31,11 @@ import '@storybook/addon-notes/register'; Then write your stories like this: ```js -import React from 'react'; - import { storiesOf } from '@storybook/react'; -import { WithNotes } from '@storybook/addon-notes'; +import { withNotes } from '@storybook/addon-notes'; import Component from './Component'; storiesOf('Component', module) - .add('with some emoji', () => ( - - - - )); + .add('with some emoji', withNotes({ notes: 'A very simple component'})(() => )); ``` diff --git a/addons/notes/package.json b/addons/notes/package.json index 9a76631e8d17..4d0e3dfffc45 100644 --- a/addons/notes/package.json +++ b/addons/notes/package.json @@ -1,11 +1,11 @@ { "name": "@storybook/addon-notes", - "version": "3.1.6", + "version": "3.2.0-alpha.10", "description": "Write notes for your Storybook stories.", "keywords": [ "addon", - "react", - "storybook" + "storybook", + "notes" ], "license": "MIT", "main": "dist/index.js", @@ -19,12 +19,13 @@ "storybook": "start-storybook -p 9010" }, "dependencies": { - "@storybook/addons": "^3.1.6", + "@storybook/addons": "^3.2.0-alpha.10", "babel-runtime": "^6.23.0", - "prop-types": "^15.5.10" + "util-deprecate": "^1.0.2" }, "devDependencies": { "git-url-parse": "^6.2.2", + "prop-types": "^15.5.10", "react": "^15.6.1", "react-addons-test-utils": "^15.5.1", "react-dom": "^15.6.1" diff --git a/addons/notes/src/index.js b/addons/notes/src/index.js index 818e3ffb39f3..99808f7ed40b 100644 --- a/addons/notes/src/index.js +++ b/addons/notes/src/index.js @@ -1,24 +1,22 @@ -import React from 'react'; -import PropTypes from 'prop-types'; +import deprecate from 'util-deprecate'; import addons from '@storybook/addons'; +import { WithNotes as ReactWithNotes } from './react'; -export class WithNotes extends React.Component { - render() { - const { children, notes } = this.props; - const channel = addons.getChannel(); +export const withNotes = ({ notes }) => { + const channel = addons.getChannel(); - // send the notes to the channel. + return getStory => context => { + // send the notes to the channel before the story is rendered channel.emit('storybook/notes/add_notes', notes); - // return children elements. - return children; - } -} - -WithNotes.propTypes = { - children: PropTypes.node, - notes: PropTypes.string, -}; -WithNotes.defaultProps = { - children: null, - notes: '', + return getStory(context); + }; }; + +Object.defineProperty(exports, 'WithNotes', { + configurable: true, + enumerable: true, + get: deprecate( + () => ReactWithNotes, + '@storybook/addon-notes WithNotes Component is deprecated, use withNotes() instead. See https://github.com/storybooks/storybook/tree/master/addons/notes' + ), +}); diff --git a/addons/notes/src/react.js b/addons/notes/src/react.js new file mode 100644 index 000000000000..818e3ffb39f3 --- /dev/null +++ b/addons/notes/src/react.js @@ -0,0 +1,24 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import addons from '@storybook/addons'; + +export class WithNotes extends React.Component { + render() { + const { children, notes } = this.props; + const channel = addons.getChannel(); + + // send the notes to the channel. + channel.emit('storybook/notes/add_notes', notes); + // return children elements. + return children; + } +} + +WithNotes.propTypes = { + children: PropTypes.node, + notes: PropTypes.string, +}; +WithNotes.defaultProps = { + children: null, + notes: '', +}; diff --git a/addons/notes/storybook-addon-notes.d.ts b/addons/notes/storybook-addon-notes.d.ts deleted file mode 100644 index fdfdaa918083..000000000000 --- a/addons/notes/storybook-addon-notes.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -import * as React from 'react'; - -export interface WithNotesProps extends React.HTMLProps { - notes?: string; -} - -export const WithNotes: React.StatelessComponent; diff --git a/addons/options/README.md b/addons/options/README.md index c85a55af5c58..7d5042011740 100644 --- a/addons/options/README.md +++ b/addons/options/README.md @@ -44,6 +44,7 @@ setOptions({ showSearchBox: false, downPanelInRight: false, sortStoriesByKind: false, + hierarchySeparator: /\//, }); storybook.configure(() => require('./stories'), module); diff --git a/addons/options/package.json b/addons/options/package.json index 84df95e197ee..92c82a53cb4b 100644 --- a/addons/options/package.json +++ b/addons/options/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-options", - "version": "3.1.6", + "version": "3.2.0-alpha.10", "description": "Options addon for storybook", "keywords": [ "storybook" @@ -11,7 +11,6 @@ }, "license": "MIT", "main": "preview.js", - "typings": "./storybook-addon-options.d.ts", "repository": { "type": "git", "url": "https://github.com/storybooks/storybook.git" @@ -21,7 +20,7 @@ "storybook": "start-storybook -p 9001" }, "dependencies": { - "@storybook/addons": "^3.1.6" + "@storybook/addons": "^3.2.0-alpha.10" }, "devDependencies": { "react": "^15.6.1", diff --git a/addons/options/storybook-addon-options.d.ts b/addons/options/storybook-addon-options.d.ts deleted file mode 100644 index 5efbcead76e5..000000000000 --- a/addons/options/storybook-addon-options.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -interface Options { - name?: string; - url?: string; - goFullScreen?: boolean; - showLeftPanel?: boolean; - showDownPanel?: boolean; - showSearchBox?: boolean; - downPanelInRight?: boolean; - sortStoriesByKind?: boolean; -} - -export function setOptions(options: Options): void; diff --git a/addons/storyshots/package.json b/addons/storyshots/package.json index 5ba4ede552c6..4f5c1295549a 100644 --- a/addons/storyshots/package.json +++ b/addons/storyshots/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/addon-storyshots", - "version": "3.1.9", + "version": "3.2.0-alpha.11", "description": "StoryShots is a Jest Snapshot Testing Addon for Storybook.", "license": "MIT", "main": "dist/index.js", @@ -20,9 +20,9 @@ "read-pkg-up": "^2.0.0" }, "devDependencies": { - "@storybook/addons": "^3.1.6", - "@storybook/channels": "^3.1.6", - "@storybook/react": "^3.1.9", + "@storybook/addons": "^3.2.0-alpha.10", + "@storybook/channels": "^3.2.0-alpha.10", + "@storybook/react": "^3.2.0-alpha.11", "babel-cli": "^6.24.1", "babel-plugin-transform-runtime": "^6.23.0", "babel-preset-es2015": "^6.24.1", @@ -31,9 +31,9 @@ "react-dom": "^15.6.1" }, "peerDependencies": { - "@storybook/addons": "^3.1.6", - "@storybook/channels": "^3.1.6", - "@storybook/react": "^3.1.9", + "@storybook/addons": "^3.2.0-alpha.10", + "@storybook/channels": "^3.2.0-alpha.10", + "@storybook/react": "^3.2.0-alpha.11", "babel-core": "^6.25.0", "react": "*", "react-test-renderer": "*" diff --git a/app/react-native/package.json b/app/react-native/package.json index 03e4fcfc7fb3..584e413325ec 100644 --- a/app/react-native/package.json +++ b/app/react-native/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/react-native", - "version": "3.1.9", + "version": "3.2.0-alpha.11", "description": "A better way to develop React Native Components for your app", "keywords": [ "react", @@ -24,11 +24,11 @@ "prepublish": "node ../../scripts/prepublish.js" }, "dependencies": { - "@storybook/addon-actions": "^3.1.9", - "@storybook/addon-links": "^3.1.6", - "@storybook/addons": "^3.1.6", - "@storybook/channel-websocket": "^3.1.6", - "@storybook/ui": "^3.1.9", + "@storybook/addon-actions": "^3.2.0-alpha.10", + "@storybook/addon-links": "^3.2.0-alpha.10", + "@storybook/addons": "^3.2.0-alpha.10", + "@storybook/channel-websocket": "^3.2.0-alpha.10", + "@storybook/ui": "^3.2.0-alpha.11", "autoprefixer": "^7.1.1", "babel-core": "^6.25.0", "babel-loader": "^7.0.0", diff --git a/app/react-native/src/manager/components/PreviewHelp.js b/app/react-native/src/manager/components/PreviewHelp.js index eb7b581c711e..be3822695969 100644 --- a/app/react-native/src/manager/components/PreviewHelp.js +++ b/app/react-native/src/manager/components/PreviewHelp.js @@ -52,7 +52,7 @@ const PreviewHelp = () => For react-native init apps:

    -
    npm run <platform>
    +
    react-native run-<platform>
    ; diff --git a/app/react-native/src/preview/components/OnDeviceUI/index.js b/app/react-native/src/preview/components/OnDeviceUI/index.js new file mode 100644 index 000000000000..d52f3f3d787f --- /dev/null +++ b/app/react-native/src/preview/components/OnDeviceUI/index.js @@ -0,0 +1,37 @@ +import React, { PropTypes } from 'react'; +import { View } from 'react-native'; +import style from './style'; +import StoryListView from '../StoryListView'; +import StoryView from '../StoryView'; + +export default function OnDeviceUI(props) { + const { stories, events, url } = props; + + return ( + + + + + + + + + + + ); +} + +OnDeviceUI.propTypes = { + stories: PropTypes.shape({ + dumpStoryBook: PropTypes.func.isRequired, + on: PropTypes.func.isRequired, + emit: PropTypes.func.isRequired, + removeListener: PropTypes.func.isRequired, + }).isRequired, + events: PropTypes.shape({ + on: PropTypes.func.isRequired, + emit: PropTypes.func.isRequired, + removeListener: PropTypes.func.isRequired, + }).isRequired, + url: PropTypes.string.isRequired, +}; diff --git a/app/react-native/src/preview/components/OnDeviceUI/style.js b/app/react-native/src/preview/components/OnDeviceUI/style.js new file mode 100644 index 000000000000..819d4cd6449c --- /dev/null +++ b/app/react-native/src/preview/components/OnDeviceUI/style.js @@ -0,0 +1,28 @@ +import { StyleSheet } from 'react-native'; + +export default { + main: { + flex: 1, + flexDirection: 'row', + paddingTop: 20, + backgroundColor: 'rgba(247, 247, 247, 1)', + }, + leftPanel: { + flex: 1, + maxWidth: 250, + paddingHorizontal: 8, + paddingBottom: 8, + }, + rightPanel: { + flex: 1, + backgroundColor: 'rgba(255, 255, 255, 1)', + borderWidth: StyleSheet.hairlineWidth, + borderColor: 'rgba(236, 236, 236, 1)', + borderRadius: 4, + marginBottom: 8, + marginHorizontal: 8, + }, + preview: { + ...StyleSheet.absoluteFillObject, + }, +}; diff --git a/app/react-native/src/preview/components/StoryListView/index.js b/app/react-native/src/preview/components/StoryListView/index.js new file mode 100644 index 000000000000..b8a0bb179b9e --- /dev/null +++ b/app/react-native/src/preview/components/StoryListView/index.js @@ -0,0 +1,126 @@ +import React, { Component, PropTypes } from 'react'; +import { SectionList, View, Text, TouchableOpacity } from 'react-native'; +import style from './style'; + +const SectionHeader = ({ title, selected }) => + + + {title} + + ; + +SectionHeader.propTypes = { + title: PropTypes.string.isRequired, + selected: PropTypes.bool.isRequired, +}; + +const ListItem = ({ title, selected, onPress }) => + + + {title} + + ; + +ListItem.propTypes = { + title: PropTypes.string.isRequired, + onPress: PropTypes.func.isRequired, + selected: PropTypes.bool.isRequired, +}; + +export default class StoryListView extends Component { + constructor(props, ...args) { + super(props, ...args); + this.state = { + sections: [], + selectedKind: null, + selectedStory: null, + }; + + this.storyAddedHandler = this.handleStoryAdded.bind(this); + this.storyChangedHandler = this.handleStoryChanged.bind(this); + this.changeStoryHandler = this.changeStory.bind(this); + + this.props.stories.on('storyAdded', this.storyAddedHandler); + this.props.events.on('story', this.storyChangedHandler); + } + + componentDidMount() { + this.handleStoryAdded(); + const dump = this.props.stories.dumpStoryBook(); + const nonEmptyKind = dump.find(kind => kind.stories.length > 0); + if (nonEmptyKind) { + this.changeStory(nonEmptyKind.kind, nonEmptyKind.stories[0]); + } + } + + componentWillUnmount() { + this.props.stories.removeListener('storyAdded', this.storiesHandler); + this.props.events.removeListener('story', this.storyChangedHandler); + } + + handleStoryAdded() { + if (this.props.stories) { + const data = this.props.stories.dumpStoryBook(); + this.setState({ + sections: data.map(section => ({ + key: section.kind, + title: section.kind, + data: section.stories.map(story => ({ + key: story, + kind: section.kind, + name: story, + })), + })), + }); + } + } + + handleStoryChanged(storyFn, selection) { + const { kind, story } = selection; + this.setState({ + selectedKind: kind, + selectedStory: story, + }); + } + + changeStory(kind, story) { + this.props.events.emit('setCurrentStory', { kind, story }); + } + + render() { + return ( + + this.changeStory(item.kind, item.name)} + />} + renderSectionHeader={({ section }) => + } + sections={this.state.sections} + stickySectionHeadersEnabled={false} + /> + ); + } +} + +StoryListView.propTypes = { + stories: PropTypes.shape({ + dumpStoryBook: PropTypes.func.isRequired, + on: PropTypes.func.isRequired, + emit: PropTypes.func.isRequired, + removeListener: PropTypes.func.isRequired, + }).isRequired, + events: PropTypes.shape({ + on: PropTypes.func.isRequired, + emit: PropTypes.func.isRequired, + removeListener: PropTypes.func.isRequired, + }).isRequired, +}; diff --git a/app/react-native/src/preview/components/StoryListView/style.js b/app/react-native/src/preview/components/StoryListView/style.js new file mode 100644 index 000000000000..00b79d358308 --- /dev/null +++ b/app/react-native/src/preview/components/StoryListView/style.js @@ -0,0 +1,26 @@ +export default { + list: { + flex: 1, + maxWidth: 250, + }, + header: { + paddingTop: 24, + paddingBottom: 4, + }, + headerText: { + fontSize: 16, + }, + headerTextSelected: { + fontWeight: 'bold', + }, + item: { + paddingVertical: 4, + paddingHorizontal: 16, + }, + itemText: { + fontSize: 14, + }, + itemTextSelected: { + fontWeight: 'bold', + }, +}; diff --git a/app/react-native/src/preview/index.js b/app/react-native/src/preview/index.js index c5d253887083..e54a07a38d8a 100644 --- a/app/react-native/src/preview/index.js +++ b/app/react-native/src/preview/index.js @@ -6,6 +6,7 @@ import createChannel from '@storybook/channel-websocket'; import { EventEmitter } from 'events'; import StoryStore from './story_store'; import StoryKindApi from './story_kind'; +import OnDeviceUI from './components/OnDeviceUI'; import StoryView from './components/StoryView'; export default class Preview { @@ -70,11 +71,14 @@ export default class Preview { } channel.on('getStories', () => this._sendSetStories()); channel.on('setCurrentStory', d => this._selectStory(d)); + this._events.on('setCurrentStory', d => this._selectStory(d)); this._sendSetStories(); this._sendGetCurrentStory(); // finally return the preview component - return ; + return params.onDeviceUI + ? + : ; }; } diff --git a/app/react-native/src/preview/story_store.js b/app/react-native/src/preview/story_store.js index 86021172b09f..07e91876b125 100644 --- a/app/react-native/src/preview/story_store.js +++ b/app/react-native/src/preview/story_store.js @@ -1,8 +1,11 @@ /* eslint no-underscore-dangle: 0 */ +import { EventEmitter } from 'events'; + let count = 0; -export default class StoryStore { +export default class StoryStore extends EventEmitter { constructor() { + super(); this._data = {}; } @@ -21,6 +24,8 @@ export default class StoryStore { index: count, fn, }; + + this.emit('storyAdded', kind, name, fn); } getStoryKinds() { diff --git a/app/react/package.json b/app/react/package.json index 7cc8d69d299f..b13cccf58bed 100644 --- a/app/react/package.json +++ b/app/react/package.json @@ -1,6 +1,6 @@ { "name": "@storybook/react", - "version": "3.1.9", + "version": "3.2.0-alpha.11", "description": "Storybook for React: Develop React Component in isolation with Hot Reloading.", "homepage": "https://github.com/storybooks/storybook/tree/master/apps/react", "bugs": { @@ -22,11 +22,11 @@ "prepublish": "node ../../scripts/prepublish.js" }, "dependencies": { - "@storybook/addon-actions": "^3.1.9", - "@storybook/addon-links": "^3.1.6", - "@storybook/addons": "^3.1.6", - "@storybook/channel-postmessage": "^3.1.6", - "@storybook/ui": "^3.1.9", + "@storybook/addon-actions": "^3.2.0-alpha.10", + "@storybook/addon-links": "^3.2.0-alpha.10", + "@storybook/addons": "^3.2.0-alpha.10", + "@storybook/channel-postmessage": "^3.2.0-alpha.10", + "@storybook/ui": "^3.2.0-alpha.11", "airbnb-js-shims": "^1.1.1", "autoprefixer": "^7.1.1", "babel-core": "^6.25.0", diff --git a/app/react/src/client/preview/client_api.js b/app/react/src/client/preview/client_api.js index 8370cd487669..cf12a4196898 100644 --- a/app/react/src/client/preview/client_api.js +++ b/app/react/src/client/preview/client_api.js @@ -34,6 +34,13 @@ export default class ClientApi { throw new Error('Invalid or missing kind provided for stories, should be a string'); } + if (!m) { + // eslint-disable-next-line no-console + console.warn( + `Missing 'module' parameter for story with a kind of '${kind}'. It will break your HMR` + ); + } + if (m && m.hot) { m.hot.dispose(() => { this._storyStore.removeStoryKind(kind); diff --git a/app/react/src/server/config/globals.js b/app/react/src/server/config/globals.js index ee4d9597bf2a..e95a663712b5 100644 --- a/app/react/src/server/config/globals.js +++ b/app/react/src/server/config/globals.js @@ -1,3 +1,4 @@ /* globals window */ window.STORYBOOK_REACT_CLASSES = {}; +window.STORYBOOK_ENV = 'react'; diff --git a/app/vue/.babelrc b/app/vue/.babelrc new file mode 100644 index 000000000000..845c3cf4d757 --- /dev/null +++ b/app/vue/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": ["env", "stage-0", "react"] +} diff --git a/app/vue/.npmignore b/app/vue/.npmignore new file mode 100644 index 000000000000..329fc8d67ad9 --- /dev/null +++ b/app/vue/.npmignore @@ -0,0 +1,3 @@ +docs +src +.babelrc diff --git a/app/vue/README.md b/app/vue/README.md new file mode 100644 index 000000000000..8b6cc94db144 --- /dev/null +++ b/app/vue/README.md @@ -0,0 +1,40 @@ +# Storybook for Vue + +[![Greenkeeper badge](https://badges.greenkeeper.io/storybooks/storybook.svg)](https://greenkeeper.io/) +[![Build Status on CircleCI](https://circleci.com/gh/storybooks/storybook.svg?style=shield)](https://circleci.com/gh/storybooks/storybook) +[![CodeFactor](https://www.codefactor.io/repository/github/storybooks/storybook/badge)](https://www.codefactor.io/repository/github/storybooks/storybook) +[![Known Vulnerabilities](https://snyk.io/test/github/storybooks/storybook/8f36abfd6697e58cd76df3526b52e4b9dc894847/badge.svg)](https://snyk.io/test/github/storybooks/storybook/8f36abfd6697e58cd76df3526b52e4b9dc894847) +[![BCH compliance](https://bettercodehub.com/edge/badge/storybooks/storybook)](https://bettercodehub.com/results/storybooks/storybook) [![codecov](https://codecov.io/gh/storybooks/storybook/branch/master/graph/badge.svg)](https://codecov.io/gh/storybooks/storybook) +[![Storybook Slack](https://now-examples-slackin-nqnzoygycp.now.sh/badge.svg)](https://now-examples-slackin-nqnzoygycp.now.sh/) +[![Backers on Open Collective](https://opencollective.com/storybook/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/storybook/sponsors/badge.svg)](#sponsors) + +* * * + +Storybook for Vue is a UI development environment for your React components. +With it, you can visualize different states of your UI components and develop them interactively. + +![Storybook Screenshot](https://github.com/storybooks/storybook/blob/master/app/react/docs/demo.gif) + +Storybook runs outside of your app. +So you can develop UI components in isolation without worrying about app specific dependencies and requirements. + +## Getting Started + +```sh +npm i -g @storybook/cli +cd my-vue-app +getstorybook +``` + +For more information visit: [storybook.js.org](https://storybook.js.org) + +* * * + +Storybook also comes with a lot of [addons](https://storybook.js.org/addons/introduction) and a great API to customize as you wish. +You can also build a [static version](https://storybook.js.org/basics/exporting-storybook) of your storybook and deploy it anywhere you want. + + +## Vue Notes + +- When using global custom components or extension (e.g `Vue.use`). You will need to declare those in the `./storybook/config.js`. + diff --git a/app/vue/addons.js b/app/vue/addons.js new file mode 100644 index 000000000000..1c94a19459e0 --- /dev/null +++ b/app/vue/addons.js @@ -0,0 +1,8 @@ +// import deprecate from 'util-deprecate'; +// import '@storybook/addon-actions/register'; +// import '@storybook/addon-links/register'; + +// deprecate( +// () => {}, +// '@storybook/react/addons is deprecated. See https://storybook.js.org/addons/using-addons/' +// )(); diff --git a/app/vue/docs/demo.gif b/app/vue/docs/demo.gif new file mode 100644 index 000000000000..c8366f8534a6 Binary files /dev/null and b/app/vue/docs/demo.gif differ diff --git a/app/vue/docs/react_storybook_screenshot.png b/app/vue/docs/react_storybook_screenshot.png new file mode 100644 index 000000000000..9763382042b2 Binary files /dev/null and b/app/vue/docs/react_storybook_screenshot.png differ diff --git a/app/vue/docs/storybooks_io_logo.png b/app/vue/docs/storybooks_io_logo.png new file mode 100644 index 000000000000..3dd9b09f3a95 Binary files /dev/null and b/app/vue/docs/storybooks_io_logo.png differ diff --git a/app/vue/package.json b/app/vue/package.json new file mode 100644 index 000000000000..ba6b1e7a7822 --- /dev/null +++ b/app/vue/package.json @@ -0,0 +1,83 @@ +{ + "name": "@storybook/vue", + "version": "3.2.0-alpha.11", + "description": "Storybook for Vue: Develop Vue Component in isolation with Hot Reloading.", + "homepage": "https://github.com/storybooks/storybook/tree/master/apps/vue", + "bugs": { + "url": "https://github.com/storybooks/storybook/issues" + }, + "license": "MIT", + "main": "dist/client/index.js", + "bin": { + "build-storybook": "./dist/server/build.js", + "start-storybook": "./dist/server/index.js", + "storybook-server": "./dist/server/index.js" + }, + "repository": { + "type": "git", + "url": "https://github.com/storybooks/storybook.git" + }, + "scripts": { + "dev": "DEV_BUILD=1 nodemon --watch ./src --exec 'npm run prepublish'", + "prepublish": "node ../../scripts/prepublish.js" + }, + "dependencies": { + "@storybook/addon-actions": "^3.2.0-alpha.10", + "@storybook/addon-links": "^3.2.0-alpha.10", + "@storybook/addons": "^3.2.0-alpha.10", + "@storybook/channel-postmessage": "^3.2.0-alpha.10", + "@storybook/ui": "^3.2.0-alpha.11", + "airbnb-js-shims": "^1.1.1", + "autoprefixer": "^7.1.1", + "babel-core": "^6.25.0", + "babel-loader": "^7.0.0", + "babel-plugin-react-docgen": "^1.6.0", + "babel-preset-env": "^1.6.0", + "babel-preset-react": "^6.24.1", + "babel-preset-react-app": "^3.0.0", + "babel-preset-stage-0": "^6.24.1", + "babel-runtime": "^6.23.0", + "case-sensitive-paths-webpack-plugin": "^2.0.0", + "chalk": "^2.0.1", + "commander": "^2.9.0", + "common-tags": "^1.4.0", + "configstore": "^3.1.0", + "css-loader": "^0.28.1", + "express": "^4.15.3", + "file-loader": "^0.11.1", + "find-cache-dir": "^1.0.0", + "global": "^4.3.2", + "json-loader": "^0.5.4", + "json-stringify-safe": "^5.0.1", + "json5": "^0.5.1", + "lodash.pick": "^4.4.0", + "postcss-flexbugs-fixes": "^3.0.0", + "postcss-loader": "^2.0.5", + "prop-types": "^15.5.10", + "qs": "^6.4.0", + "react": "^15.6.1", + "react-dom": "^15.6.1", + "react-modal": "^1.7.7", + "redux": "^3.6.0", + "request": "^2.81.0", + "serve-favicon": "^2.4.3", + "shelljs": "^0.7.8", + "style-loader": "^0.17.0", + "url-loader": "^0.5.8", + "util-deprecate": "^1.0.2", + "uuid": "^3.1.0", + "vue": "^2.4.1", + "vue-hot-reload-api": "^2.1.0", + "vue-loader": "^12.2.1", + "vue-style-loader": "^3.0.1", + "vue-template-compiler": "^2.4.1", + "webpack": "^2.5.1 || ^3.0.0", + "webpack-dev-middleware": "^1.10.2", + "webpack-hot-middleware": "^2.18.0" + }, + "devDependencies": { + "babel-cli": "^6.24.1", + "mock-fs": "^4.3.0", + "nodemon": "^1.11.0" + } +} diff --git a/app/vue/src/client/index.js b/app/vue/src/client/index.js new file mode 100644 index 000000000000..f9a446eea867 --- /dev/null +++ b/app/vue/src/client/index.js @@ -0,0 +1,23 @@ +import deprecate from 'util-deprecate'; + +// NOTE export these to keep backwards compatibility +// import { action as deprecatedAction } from '@storybook/addon-actions'; +// import { linkTo as deprecatedLinkTo } from '@storybook/addon-links'; + +import * as previewApi from './preview'; + +export const storiesOf = previewApi.storiesOf; +export const setAddon = previewApi.setAddon; +export const addDecorator = previewApi.addDecorator; +export const configure = previewApi.configure; +export const getStorybook = previewApi.getStorybook; + +// export const action = deprecate( +// deprecatedAction, +// '@storybook/react action is deprecated. See: https://github.com/storybooks/storybook/tree/master/addon/actions' +// ); + +// export const linkTo = deprecate( +// deprecatedLinkTo, +// '@storybook/react linkTo is deprecated. See: https://github.com/storybooks/storybook/tree/master/addon/links' +// ); diff --git a/app/vue/src/client/manager/index.js b/app/vue/src/client/manager/index.js new file mode 100644 index 000000000000..24082de7ca54 --- /dev/null +++ b/app/vue/src/client/manager/index.js @@ -0,0 +1,7 @@ +/* global document */ + +import renderStorybookUI from '@storybook/ui'; +import Provider from './provider'; + +const rootEl = document.getElementById('root'); +renderStorybookUI(rootEl, new Provider()); diff --git a/app/vue/src/client/manager/preview.js b/app/vue/src/client/manager/preview.js new file mode 100644 index 000000000000..4f8e792d9641 --- /dev/null +++ b/app/vue/src/client/manager/preview.js @@ -0,0 +1,39 @@ +import PropTypes from 'prop-types'; +import React, { Component } from 'react'; + +const iframeStyle = { + width: '100%', + height: '100%', + border: 0, + margin: 0, + padding: 0, +}; + +class Preview extends Component { + shouldComponentUpdate() { + // When the manager is re-rendered, due to changes in the layout (going full screen / changing + // addon panel to right) Preview section will update. If its re-rendered the whole html page + // inside the html is re-rendered making the story to re-mount. + // We dont have to re-render this component for any reason since changes are communicated to + // story using the channel and necessary changes are done by it. + return false; + } + + render() { + return ( +