diff --git a/examples/official-storybook/config.js b/examples/official-storybook/config.js index b5f8ca2e8a2b..51db2828b8b6 100644 --- a/examples/official-storybook/config.js +++ b/examples/official-storybook/config.js @@ -51,7 +51,6 @@ addParameters({ options: { hierarchySeparator: /\/|\./, hierarchyRootSeparator: '|', - // theme: themes.dark, }, viewports: { ...INITIAL_VIEWPORTS, diff --git a/examples/official-storybook/stories/core/parameters.stories.js b/examples/official-storybook/stories/core/parameters.stories.js index 41b1e09052ef..b5ab588400a5 100644 --- a/examples/official-storybook/stories/core/parameters.stories.js +++ b/examples/official-storybook/stories/core/parameters.stories.js @@ -17,7 +17,7 @@ export default { // Given we sort of control the props, should we export a prop type? export const passed = ({ // eslint-disable-next-line react/prop-types - parameters, + parameters: { options, ...parameters }, }) =>
Parameters are {JSON.stringify(parameters, null, 2)}
; passed.title = 'passed to story'; passed.parameters = { storyParameter: 'storyParameter' }; diff --git a/examples/official-storybook/tests/__snapshots__/storyshots.test.js.snap b/examples/official-storybook/tests/__snapshots__/storyshots.test.js.snap index 9e5833589004..5e5b364366f2 100644 --- a/examples/official-storybook/tests/__snapshots__/storyshots.test.js.snap +++ b/examples/official-storybook/tests/__snapshots__/storyshots.test.js.snap @@ -2653,10 +2653,6 @@ exports[`Storyshots Core|Events Force re-render 1`] = ` exports[`Storyshots Core|Parameters passed to story 1`] = `
   Parameters are {
-  "options": {
-    "hierarchyRootSeparator": "|",
-    "hierarchySeparator": {}
-  },
   "a11y": {
     "configure": {},
     "options": {
diff --git a/lib/channel-postmessage/src/index.ts b/lib/channel-postmessage/src/index.ts
index 25d672a6e238..b870100cd0e6 100644
--- a/lib/channel-postmessage/src/index.ts
+++ b/lib/channel-postmessage/src/index.ts
@@ -56,7 +56,7 @@ export class PostmsgTransport {
       });
     }
 
-    const data = stringify({ key: KEY, event }, { maxDepth: 10 });
+    const data = stringify({ key: KEY, event }, { maxDepth: 15 });
 
     // TODO: investigate http://blog.teamtreehouse.com/cross-domain-messaging-with-postmessage
     // might replace '*' with document.location ?
diff --git a/lib/theming/src/ensure.ts b/lib/theming/src/ensure.ts
index c206a73b0c9a..70fe47718127 100644
--- a/lib/theming/src/ensure.ts
+++ b/lib/theming/src/ensure.ts
@@ -9,6 +9,12 @@ import isEqual from 'lodash.isequal';
 import light from './themes/light';
 import { Theme } from './base';
 
+const base = {
+  ...light,
+  animation: {},
+  brand: {},
+};
+
 // merge with concatenating arrays, but no duplicates
 const merge = (a: any, b: any) =>
   mergeWith({}, a, b, (objValue: any, srcValue: any) => {
@@ -32,7 +38,7 @@ export const ensure = (input: any): Theme => {
   if (!input) {
     return light;
   } else {
-    const missing = deletedDiff(light, input);
+    const missing = deletedDiff(base, input);
     if (Object.keys(missing).length) {
       logger.warn(
         stripIndent`
diff --git a/lib/ui/package.json b/lib/ui/package.json
index b7166ff9b364..031d5d423e94 100644
--- a/lib/ui/package.json
+++ b/lib/ui/package.json
@@ -55,7 +55,7 @@
     "react-resize-detector": "^3.2.1",
     "recompose": "^0.30.0",
     "semver": "^5.6.0",
-    "to-camel-case": "^1.0.0",
+    "telejson": "^2.1.1",
     "util-deprecate": "^1.0.2"
   },
   "devDependencies": {
diff --git a/lib/ui/src/components/layout/container.js b/lib/ui/src/components/layout/container.js
index be43e8248087..33f23593ea94 100644
--- a/lib/ui/src/components/layout/container.js
+++ b/lib/ui/src/components/layout/container.js
@@ -270,7 +270,7 @@ class Layout extends Component {
   constructor(props) {
     super(props);
 
-    const { options } = this.props;
+    const { bounds, options } = props;
 
     const { resizerNav, resizerPanel } = persistance.get();
 
@@ -278,7 +278,10 @@ class Layout extends Component {
       isDragging: false,
       resizerNav: resizerNav || { x: 200, y: 0 },
       resizerPanel:
-        resizerPanel || (options.panelPosition === 'bottom' ? { x: 0, y: 400 } : { x: 400, y: 0 }),
+        resizerPanel ||
+        (options.panelPosition === 'bottom'
+          ? { x: 0, y: bounds.height - 400 }
+          : { x: bounds.width - 400, y: 0 }),
     };
   }
 
@@ -323,7 +326,7 @@ class Layout extends Component {
       }
     }
     if (isPanelRight && !isPanelHidden) {
-      if (bounds.width - 200 < panelX) {
+      if (bounds.width - 200 < panelX || panelX === 0) {
         mutation.resizerPanel = {
           x: bounds.width - 200,
           y: 0,
diff --git a/lib/ui/src/core/addons.js b/lib/ui/src/core/addons.js
index 996233d5467f..7ebc7cc5ac4a 100644
--- a/lib/ui/src/core/addons.js
+++ b/lib/ui/src/core/addons.js
@@ -22,14 +22,14 @@ export default ({ provider, store }) => {
       return ensurePanel(api.getPanels(), selectedPanel, selectedPanel);
     },
     setSelectedPanel: panelName => {
-      store.setState({ selectedPanel: panelName });
+      store.setState({ selectedPanel: panelName }, { persistence: 'session' });
     },
   };
 
   return {
     api,
     state: {
-      selectedPanel: ensurePanel(api.getPanels()),
+      selectedPanel: ensurePanel(api.getPanels(), store.getState().selectedPanel),
     },
   };
 };
diff --git a/lib/ui/src/core/context.js b/lib/ui/src/core/context.js
index 7e03f6a0e8f4..04e13d1d6305 100644
--- a/lib/ui/src/core/context.js
+++ b/lib/ui/src/core/context.js
@@ -50,7 +50,6 @@ export class Provider extends Component {
     // Initialize the state to be the initial (persisted) state of the store.
     // This gives the modules the chance to read the persisted state, apply their defaults
     // and override if necessary
-    this.state = store.getInitialState();
 
     const apiData = {
       navigate,
@@ -62,6 +61,8 @@ export class Provider extends Component {
       storyId,
     };
 
+    this.state = store.getInitialState();
+
     this.modules = [
       initChannel,
       initAddons,
@@ -90,7 +91,9 @@ export class Provider extends Component {
     api.on(SET_STORIES, data => {
       api.setStories(data.stories);
 
-      const options = api.getParameters(storyId, 'options');
+      const options = storyId
+        ? api.getParameters(storyId, 'options')
+        : api.getParameters(Object.keys(data.stories)[0], 'options');
 
       api.setOptions(options);
     });
diff --git a/lib/ui/src/core/init-provider-api.js b/lib/ui/src/core/init-provider-api.js
index f0267136cc62..b3414c4add6c 100644
--- a/lib/ui/src/core/init-provider-api.js
+++ b/lib/ui/src/core/init-provider-api.js
@@ -1,86 +1,6 @@
-import pick from 'lodash.pick';
-import deprecate from 'util-deprecate';
-
-import { create } from '@storybook/theming';
-
-const deprecationMessage = (optionsMap, prefix) =>
-  `The options { ${Object.keys(optionsMap).join(', ')} } are deprecated -- use ${
-    prefix ? `${prefix}'s` : ''
-  } { ${Object.values(optionsMap).join(', ')} } instead.`;
-
-const deprecatedThemeOptions = {
-  name: 'brandTitle',
-  url: 'brandUrl',
-};
-const applyDeprecatedThemeOptions = deprecate(({ name, url, theme }) => {
-  const vars = {
-    brandTitle: name,
-    brandUrl: url,
-    brandImage: null,
-  };
-
-  return { theme: create(vars, theme) };
-}, deprecationMessage(deprecatedThemeOptions));
-const checkDeprecatedThemeOptions = options => {
-  if (Object.keys(deprecatedThemeOptions).find(key => !!options[key])) {
-    return applyDeprecatedThemeOptions(options);
-  }
-  return {};
-};
-
-const deprecatedLayoutOptions = {
-  goFullScreen: 'isFullscreen',
-  showStoriesPanel: 'showNav',
-  showAddonPanel: 'showPanel',
-  addonPanelInRight: 'panelPosition',
-};
-const applyDeprecatedLayoutOptions = deprecate(options => {
-  const layoutUpdate = {};
-
-  ['goFullScreen', 'showStoriesPanel', 'showAddonPanel'].forEach(option => {
-    if (typeof options[option] !== 'undefined') {
-      layoutUpdate[deprecatedLayoutOptions[option]] = options[option];
-    }
-  });
-  if (options.addonPanelInRight) {
-    layoutUpdate.panelPosition = 'right';
-  }
-  return layoutUpdate;
-}, deprecationMessage(deprecatedLayoutOptions));
-const checkDeprecatedLayoutOptions = options => {
-  if (Object.keys(deprecatedLayoutOptions).find(key => typeof options[key] !== 'undefined')) {
-    return applyDeprecatedLayoutOptions(options);
-  }
-  return {};
-};
-
-export default ({ provider, api, store }) => {
+export default ({ provider, api }) => {
   const providerAPI = {
     ...api,
-
-    setOptions: options => {
-      const { layout, ui, selectedPanel } = store.getState();
-
-      if (options) {
-        const updatedLayout = {
-          ...layout,
-          ...pick(options, Object.keys(layout)),
-          ...checkDeprecatedLayoutOptions(options),
-        };
-
-        const updatedUi = {
-          ...ui,
-          ...pick(options, Object.keys(ui)),
-          ...checkDeprecatedThemeOptions(options),
-        };
-
-        store.setState({
-          layout: updatedLayout,
-          ui: updatedUi,
-          selectedPanel: options.panel || options.selectedPanel || selectedPanel,
-        });
-      }
-    },
   };
 
   provider.handleAPI(providerAPI);
diff --git a/lib/ui/src/core/initial-state.js b/lib/ui/src/core/initial-state.js
index 5ac502039fc2..5eee085d6286 100644
--- a/lib/ui/src/core/initial-state.js
+++ b/lib/ui/src/core/initial-state.js
@@ -1,21 +1,6 @@
-import { themes } from '@storybook/theming';
-
 import merge from '../libs/merge';
 
 const initial = {
-  ui: {
-    enableShortcuts: true,
-    sortStoriesByKind: false,
-    sidebarAnimations: true,
-    theme: themes.normal,
-  },
-  layout: {
-    isToolshown: true,
-    isFullscreen: false,
-    showPanel: true,
-    showNav: true,
-    panelPosition: 'bottom',
-  },
   customQueryParams: {},
   storiesConfigured: false,
 };
diff --git a/lib/ui/src/core/layout.js b/lib/ui/src/core/layout.js
index 80a9523899e2..d6bf8ef8ce9f 100644
--- a/lib/ui/src/core/layout.js
+++ b/lib/ui/src/core/layout.js
@@ -1,3 +1,64 @@
+import pick from 'lodash.pick';
+
+import deprecate from 'util-deprecate';
+
+import { create, themes } from '@storybook/theming';
+import merge from '../libs/merge';
+
+const deprecatedThemeOptions = {
+  name: 'brandTitle',
+  url: 'brandUrl',
+};
+const deprecatedLayoutOptions = {
+  goFullScreen: 'isFullscreen',
+  showStoriesPanel: 'showNav',
+  showAddonPanel: 'showPanel',
+  addonPanelInRight: 'panelPosition',
+};
+
+const deprecationMessage = (optionsMap, prefix) =>
+  `The options { ${Object.keys(optionsMap).join(', ')} } are deprecated -- use ${
+    prefix ? `${prefix}'s` : ''
+  } { ${Object.values(optionsMap).join(', ')} } instead.`;
+
+const applyDeprecatedThemeOptions = deprecate(({ name, url, theme }) => {
+  const vars = {
+    brandTitle: name,
+    brandUrl: url,
+    brandImage: null,
+  };
+
+  return { theme: create(vars, theme) };
+}, deprecationMessage(deprecatedThemeOptions));
+
+const applyDeprecatedLayoutOptions = deprecate(options => {
+  const layoutUpdate = {};
+
+  ['goFullScreen', 'showStoriesPanel', 'showAddonPanel'].forEach(option => {
+    if (typeof options[option] !== 'undefined') {
+      layoutUpdate[deprecatedLayoutOptions[option]] = options[option];
+    }
+  });
+  if (options.addonPanelInRight) {
+    layoutUpdate.panelPosition = 'right';
+  }
+  return layoutUpdate;
+}, deprecationMessage(deprecatedLayoutOptions));
+
+const checkDeprecatedThemeOptions = options => {
+  if (Object.keys(deprecatedThemeOptions).find(key => !!options[key])) {
+    return applyDeprecatedThemeOptions(options);
+  }
+  return {};
+};
+
+const checkDeprecatedLayoutOptions = options => {
+  if (Object.keys(deprecatedLayoutOptions).find(key => typeof options[key] !== 'undefined')) {
+    return applyDeprecatedLayoutOptions(options);
+  }
+  return {};
+};
+
 export default function({ store }) {
   const api = {
     toggleFullscreen(toggled) {
@@ -69,7 +130,54 @@ export default function({ store }) {
         };
       });
     },
+
+    setOptions: options => {
+      const { layout, ui, selectedPanel } = store.getState();
+
+      if (options) {
+        const updatedLayout = {
+          ...layout,
+          ...pick(options, Object.keys(layout)),
+          ...checkDeprecatedLayoutOptions(options),
+        };
+
+        const updatedUi = {
+          ...ui,
+          ...pick(options, Object.keys(ui)),
+          ...checkDeprecatedThemeOptions(options),
+        };
+
+        store.setState(
+          {
+            layout: updatedLayout,
+            ui: updatedUi,
+            selectedPanel: options.panel || options.selectedPanel || selectedPanel,
+          },
+          { persistence: 'permanent' }
+        );
+      }
+    },
   };
 
-  return { api };
+  const fromState = pick(store.getState(), 'layout', 'ui', 'selectedPanel');
+
+  const initial = {
+    ui: {
+      enableShortcuts: true,
+      sortStoriesByKind: false,
+      sidebarAnimations: true,
+      theme: themes.normal,
+    },
+    layout: {
+      isToolshown: true,
+      isFullscreen: false,
+      showPanel: true,
+      showNav: true,
+      panelPosition: 'bottom',
+    },
+  };
+
+  const state = merge(fromState, initial);
+
+  return { api, state };
 }
diff --git a/lib/ui/src/core/store.js b/lib/ui/src/core/store.js
index c87ada758e15..4bab42572701 100644
--- a/lib/ui/src/core/store.js
+++ b/lib/ui/src/core/store.js
@@ -1,16 +1,17 @@
 // TODO -- make this TS?
 
 import { localStorage, sessionStorage } from 'global';
+import { parse, stringify } from 'telejson';
 
 export const STORAGE_KEY = '@storybook/ui/store';
 
 function get(storage) {
   const serialized = storage.getItem(STORAGE_KEY);
-  return serialized ? JSON.parse(serialized) : {};
+  return serialized ? parse(serialized) : {};
 }
 
 function set(storage, value) {
-  storage.setItem(STORAGE_KEY, JSON.stringify(value));
+  storage.setItem(STORAGE_KEY, stringify(value, { maxDepth: 50 }));
 }
 
 function update(storage, patch) {
diff --git a/lib/ui/src/core/stories.js b/lib/ui/src/core/stories.js
index 0ca4f0d4b5da..94cfd00204be 100644
--- a/lib/ui/src/core/stories.js
+++ b/lib/ui/src/core/stories.js
@@ -1,26 +1,6 @@
-import mergeWith from 'lodash.mergewith';
-import isEqual from 'lodash.isequal';
 import { toId, sanitize } from '@storybook/router/utils';
 
-const merge = (a, b) =>
-  mergeWith({}, a, b, (objValue, srcValue) => {
-    if (Array.isArray(srcValue) && Array.isArray(objValue)) {
-      srcValue.forEach(s => {
-        const existing = objValue.find(o => o === s || isEqual(o, s));
-        if (!existing) {
-          objValue.push(s);
-        }
-      });
-
-      return objValue;
-    }
-    if (Array.isArray(objValue)) {
-      // eslint-disable-next-line no-console
-      console.log('the types mismatch, picking', objValue);
-      return objValue;
-    }
-    return undefined;
-  });
+import merge from '../libs/merge';
 
 const initStoriesApi = ({
   store,