diff --git a/packages/storybook-ui/src/libs/key_events.js b/packages/storybook-ui/src/libs/key_events.js
index e9db25eb5be1..f16377906108 100755
--- a/packages/storybook-ui/src/libs/key_events.js
+++ b/packages/storybook-ui/src/libs/key_events.js
@@ -1,15 +1,17 @@
import keycode from 'keycode';
export const features = {
- FULLSCREEN: 1,
- DOWN_PANEL: 2,
- LEFT_PANEL: 3,
- SHORTCUTS_HELP: 4,
- ESCAPE: 5,
- NEXT_STORY: 6,
- PREV_STORY: 7,
- SEARCH: 8,
- DOWN_PANEL_IN_RIGHT: 9,
+ FULLSCREEN: 'FULLSCREEN',
+ DOWN_PANEL: 'DOWN_PANEL',
+ LEFT_PANEL: 'LEFT_PANEL',
+ SHORTCUTS_HELP: 'SHORTCUTS_HELP',
+ ESCAPE: 'ESCAPE',
+ NEXT_STORY: 'NEXT_STORY',
+ PREV_STORY: 'PREV_STORY',
+ SEARCH: 'SEARCH',
+ DOWN_PANEL_IN_RIGHT: 'DOWN_PANEL_IN_RIGHT',
+ NEXT_KIND: 'NEXT_KIND',
+ PREV_KIND: 'PREV_KIND',
};
export function isModifierPressed(e) {
diff --git a/packages/storybook-ui/src/libs/menu_positions.js b/packages/storybook-ui/src/libs/menu_positions.js
new file mode 100644
index 000000000000..d85c0612573f
--- /dev/null
+++ b/packages/storybook-ui/src/libs/menu_positions.js
@@ -0,0 +1,6 @@
+export const boxPositions = {
+ BOTTOM_LEFT: 'BOTTOM_LEFT',
+ BOTTOM_RIGHT: 'BOTTOM_RIGHT',
+ TOP_LEFT: 'TOP_LEFT',
+ TOP_RIGHT: 'TOP_RIGHT',
+};
diff --git a/packages/storybook-ui/src/modules/api/actions/api.js b/packages/storybook-ui/src/modules/api/actions/api.js
index f5bdc0a4aa22..dea7d4f1e523 100755
--- a/packages/storybook-ui/src/modules/api/actions/api.js
+++ b/packages/storybook-ui/src/modules/api/actions/api.js
@@ -24,6 +24,21 @@ export function jumpToStory(storyKinds, selectedKind, selectedStory, direction)
};
}
+function jumpToKind(storyKinds, selectedKind, selectedStory, direction) {
+ const currentIndex = storyKinds.findIndex(({ kind }) => kind === selectedKind);
+ if (currentIndex === -1) return { selectedKind, selectedStory };
+
+ const jumpedStoryKind = storyKinds[currentIndex + direction];
+
+ const jumpedKind = jumpedStoryKind ? jumpedStoryKind.kind : selectedKind;
+ const jumpedStory = jumpedStoryKind ? jumpedStoryKind.stories[0] : selectedStory;
+
+ return {
+ selectedKind: jumpedKind,
+ selectedStory: jumpedStory,
+ };
+}
+
export function ensureKind(storyKinds, selectedKind) {
if (!storyKinds) return selectedKind;
@@ -77,6 +92,22 @@ export default {
);
},
+ jumpToKind({ clientStore }, direction) {
+ clientStore.update(state => {
+ let { selectedKind, selectedStory } = jumpToKind(
+ state.stories,
+ state.selectedKind,
+ state.selectedStory,
+ direction,
+ );
+
+ selectedKind = ensureKind(state.stories, selectedKind);
+ selectedStory = ensureStory(state.stories, selectedKind, selectedStory);
+
+ return { selectedKind, selectedStory };
+ });
+ },
+
setOptions({ clientStore }, options) {
clientStore.update(state => {
const newOptions = pick(options, Object.keys(state.uiOptions));
diff --git a/packages/storybook-ui/src/modules/shortcuts/actions/shortcuts.js b/packages/storybook-ui/src/modules/shortcuts/actions/shortcuts.js
index bb6c4d40e4f9..755bbb5a84ec 100755
--- a/packages/storybook-ui/src/modules/shortcuts/actions/shortcuts.js
+++ b/packages/storybook-ui/src/modules/shortcuts/actions/shortcuts.js
@@ -29,6 +29,12 @@ export default {
case features.PREV_STORY:
apiActions.api.jumpToStory(context, -1);
break;
+ case features.NEXT_KIND:
+ apiActions.api.jumpToKind(context, 1);
+ break;
+ case features.PREV_KIND:
+ apiActions.api.jumpToKind(context, -1);
+ break;
default:
clientStore.update(state => {
const newOptions = keyEventToOptions(state.shortcutOptions, event);
diff --git a/packages/storybook-ui/src/modules/ui/components/assets/svg_package.js b/packages/storybook-ui/src/modules/ui/components/assets/svg_package.js
new file mode 100644
index 000000000000..9d378a03311d
--- /dev/null
+++ b/packages/storybook-ui/src/modules/ui/components/assets/svg_package.js
@@ -0,0 +1,175 @@
+/* eslint-disable */
+(function webpackUniversalModuleDefinition(root, factory) {
+ if(typeof exports === 'object' && typeof module === 'object')
+ module.exports = factory();
+ else if(typeof define === 'function' && define.amd)
+ define([], factory);
+ else if(typeof exports === 'object')
+ exports["svg_package"] = factory();
+ else
+ root["svg_package"] = factory();
+})(this, function() {
+return /******/ (function(modules) { // webpackBootstrap
+/******/ // The module cache
+/******/ var installedModules = {};
+
+/******/ // The require function
+/******/ function __webpack_require__(moduleId) {
+
+/******/ // Check if module is in cache
+/******/ if(installedModules[moduleId])
+/******/ return installedModules[moduleId].exports;
+
+/******/ // Create a new module (and put it into the cache)
+/******/ var module = installedModules[moduleId] = {
+/******/ i: moduleId,
+/******/ l: false,
+/******/ exports: {}
+/******/ };
+
+/******/ // Execute the module function
+/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
+
+/******/ // Flag the module as loaded
+/******/ module.l = true;
+
+/******/ // Return the exports of the module
+/******/ return module.exports;
+/******/ }
+
+
+/******/ // expose the modules object (__webpack_modules__)
+/******/ __webpack_require__.m = modules;
+
+/******/ // expose the module cache
+/******/ __webpack_require__.c = installedModules;
+
+/******/ // identity function for calling harmony imports with the correct context
+/******/ __webpack_require__.i = function(value) { return value; };
+
+/******/ // define getter function for harmony exports
+/******/ __webpack_require__.d = function(exports, name, getter) {
+/******/ if(!__webpack_require__.o(exports, name)) {
+/******/ Object.defineProperty(exports, name, {
+/******/ configurable: false,
+/******/ enumerable: true,
+/******/ get: getter
+/******/ });
+/******/ }
+/******/ };
+
+/******/ // getDefaultExport function for compatibility with non-harmony modules
+/******/ __webpack_require__.n = function(module) {
+/******/ var getter = module && module.__esModule ?
+/******/ function getDefault() { return module['default']; } :
+/******/ function getModuleExports() { return module; };
+/******/ __webpack_require__.d(getter, 'a', getter);
+/******/ return getter;
+/******/ };
+
+/******/ // Object.prototype.hasOwnProperty.call
+/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
+
+/******/ // __webpack_public_path__
+/******/ __webpack_require__.p = "";
+
+/******/ // Load entry module and return exports
+/******/ return __webpack_require__(__webpack_require__.s = 8);
+/******/ })
+/************************************************************************/
+/******/ ([
+/* 0 */
+/***/ function(module, exports) {
+
+module.exports = "data:image/svg+xml;base64,PHN2ZyBmaWxsPSIjMDAwMDAwIiBoZWlnaHQ9IjI0IiB2aWV3Qm94PSIwIDAgMjQgMjQiIHdpZHRoPSIyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICAgIDxwYXRoIGQ9Ik0wIDBoMjR2MjRIMHoiIGZpbGw9Im5vbmUiLz4KICAgIDxwYXRoIGQ9Ik0xOSAzSDVjLTEuMTEgMC0yIC45LTIgMnYxNGMwIDEuMS44OSAyIDIgMmgxNGMxLjExIDAgMi0uOSAyLTJWNWMwLTEuMS0uODktMi0yLTJ6bS05IDE0bC01LTUgMS40MS0xLjQxTDEwIDE0LjE3bDcuNTktNy41OUwxOSA4bC05IDl6Ii8+Cjwvc3ZnPg=="
+
+/***/ },
+/* 1 */
+/***/ function(module, exports) {
+
+module.exports = "data:image/svg+xml;base64,PHN2ZyBmaWxsPSIjMDAwMDAwIiBoZWlnaHQ9IjI0IiB2aWV3Qm94PSIwIDAgMjQgMjQiIHdpZHRoPSIyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICAgIDxwYXRoIGQ9Ik0xOSA1djE0SDVWNWgxNG0wLTJINWMtMS4xIDAtMiAuOS0yIDJ2MTRjMCAxLjEuOSAyIDIgMmgxNGMxLjEgMCAyLS45IDItMlY1YzAtMS4xLS45LTItMi0yeiIvPgogICAgPHBhdGggZD0iTTAgMGgyNHYyNEgweiIgZmlsbD0ibm9uZSIvPgo8L3N2Zz4="
+
+/***/ },
+/* 2 */
+/***/ function(module, exports) {
+
+module.exports = "data:image/svg+xml;base64,PHN2ZyBmaWxsPSIjMDAwMDAwIiBoZWlnaHQ9IjE4IiB2aWV3Qm94PSIwIDAgMjQgMjQiIHdpZHRoPSIxOCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICAgIDxwYXRoIGQ9Ik00IDE4bDguNS02TDQgNnYxMnptOS0xMnYxMmw4LjUtNkwxMyA2eiIvPgogICAgPHBhdGggZD0iTTAgMGgyNHYyNEgweiIgZmlsbD0ibm9uZSIvPgo8L3N2Zz4="
+
+/***/ },
+/* 3 */
+/***/ function(module, exports) {
+
+module.exports = "data:image/svg+xml;base64,PHN2ZyBmaWxsPSIjMDAwMDAwIiBoZWlnaHQ9IjE4IiB2aWV3Qm94PSIwIDAgMjQgMjQiIHdpZHRoPSIxOCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICAgIDxwYXRoIGQ9Ik0xMSAxOFY2bC04LjUgNiA4LjUgNnptLjUtNmw4LjUgNlY2bC04LjUgNnoiLz4KICAgIDxwYXRoIGQ9Ik0wIDBoMjR2MjRIMHoiIGZpbGw9Im5vbmUiLz4KPC9zdmc+"
+
+/***/ },
+/* 4 */
+/***/ function(module, exports) {
+
+module.exports = "data:image/svg+xml;base64,PHN2ZyBmaWxsPSIjMDAwMDAwIiBoZWlnaHQ9IjE4IiB2aWV3Qm94PSIwIDAgMjQgMjQiIHdpZHRoPSIxOCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICAgIDxwYXRoIGQ9Ik0wIDBoMjR2MjRIMHoiIGZpbGw9Im5vbmUiLz4KICAgIDxwYXRoIGQ9Ik0xMSAxOGgydi0yaC0ydjJ6bTEtMTZDNi40OCAyIDIgNi40OCAyIDEyczQuNDggMTAgMTAgMTAgMTAtNC40OCAxMC0xMFMxNy41MiAyIDEyIDJ6bTAgMThjLTQuNDEgMC04LTMuNTktOC04czMuNTktOCA4LTggOCAzLjU5IDggOC0zLjU5IDgtOCA4em0wLTE0Yy0yLjIxIDAtNCAxLjc5LTQgNGgyYzAtMS4xLjktMiAyLTJzMiAuOSAyIDJjMCAyLTMgMS43NS0zIDVoMmMwLTIuMjUgMy0yLjUgMy01IDAtMi4yMS0xLjc5LTQtNC00eiIvPgo8L3N2Zz4="
+
+/***/ },
+/* 5 */
+/***/ function(module, exports) {
+
+module.exports = "data:image/svg+xml;base64,PHN2ZyBmaWxsPSIjMDAwMDAwIiBoZWlnaHQ9IjE4IiB2aWV3Qm94PSIwIDAgMjQgMjQiIHdpZHRoPSIxOCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICAgIDxwYXRoIGQ9Ik0wIDBoMjR2MjRIMHoiIGZpbGw9Im5vbmUiLz4KICAgIDxwYXRoIGQ9Ik0zIDE4aDE4di0ySDN2MnptMC01aDE4di0ySDN2MnptMC03djJoMThWNkgzeiIvPgo8L3N2Zz4="
+
+/***/ },
+/* 6 */
+/***/ function(module, exports) {
+
+module.exports = "data:image/svg+xml;base64,PHN2ZyBmaWxsPSIjMDAwMDAwIiBoZWlnaHQ9IjE4IiB2aWV3Qm94PSIwIDAgMjQgMjQiIHdpZHRoPSIxOCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICAgIDxwYXRoIGQ9Ik02IDE4bDguNS02TDYgNnYxMnpNMTYgNnYxMmgyVjZoLTJ6Ii8+CiAgICA8cGF0aCBkPSJNMCAwaDI0djI0SDB6IiBmaWxsPSJub25lIi8+Cjwvc3ZnPg=="
+
+/***/ },
+/* 7 */
+/***/ function(module, exports) {
+
+module.exports = "data:image/svg+xml;base64,PHN2ZyBmaWxsPSIjMDAwMDAwIiBoZWlnaHQ9IjE4IiB2aWV3Qm94PSIwIDAgMjQgMjQiIHdpZHRoPSIxOCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICAgIDxwYXRoIGQ9Ik02IDZoMnYxMkg2em0zLjUgNmw4LjUgNlY2eiIvPgogICAgPHBhdGggZD0iTTAgMGgyNHYyNEgweiIgZmlsbD0ibm9uZSIvPgo8L3N2Zz4="
+
+/***/ },
+/* 8 */
+/***/ function(module, exports, __webpack_require__) {
+
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__ic_check_box_black_24px_svg__ = __webpack_require__(0);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__ic_check_box_black_24px_svg___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_0__ic_check_box_black_24px_svg__);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__ic_check_box_outline_blank_black_24px_svg__ = __webpack_require__(1);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__ic_check_box_outline_blank_black_24px_svg___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_1__ic_check_box_outline_blank_black_24px_svg__);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__ic_fast_forward_black_18px_svg__ = __webpack_require__(2);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__ic_fast_forward_black_18px_svg___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_2__ic_fast_forward_black_18px_svg__);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_3__ic_fast_rewind_black_18px_svg__ = __webpack_require__(3);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_3__ic_fast_rewind_black_18px_svg___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_3__ic_fast_rewind_black_18px_svg__);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_4__ic_menu_black_18px_svg__ = __webpack_require__(5);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_4__ic_menu_black_18px_svg___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_4__ic_menu_black_18px_svg__);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_5__ic_skip_next_black_18px_svg__ = __webpack_require__(6);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_5__ic_skip_next_black_18px_svg___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_5__ic_skip_next_black_18px_svg__);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_6__ic_skip_previous_black_18px_svg__ = __webpack_require__(7);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_6__ic_skip_previous_black_18px_svg___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_6__ic_skip_previous_black_18px_svg__);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_7__ic_help_outline_black_18px_svg__ = __webpack_require__(4);
+/* harmony import */ var __WEBPACK_IMPORTED_MODULE_7__ic_help_outline_black_18px_svg___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_7__ic_help_outline_black_18px_svg__);
+
+
+
+
+
+
+
+
+
+const images = {
+ checked_box: __WEBPACK_IMPORTED_MODULE_0__ic_check_box_black_24px_svg___default.a,
+ unchecked_box: __WEBPACK_IMPORTED_MODULE_1__ic_check_box_outline_blank_black_24px_svg___default.a,
+ fast_forward: __WEBPACK_IMPORTED_MODULE_2__ic_fast_forward_black_18px_svg___default.a,
+ fast_rewind: __WEBPACK_IMPORTED_MODULE_3__ic_fast_rewind_black_18px_svg___default.a,
+ menu: __WEBPACK_IMPORTED_MODULE_4__ic_menu_black_18px_svg___default.a,
+ skip_next: __WEBPACK_IMPORTED_MODULE_5__ic_skip_next_black_18px_svg___default.a,
+ skip_previous: __WEBPACK_IMPORTED_MODULE_6__ic_skip_previous_black_18px_svg___default.a,
+ help: __WEBPACK_IMPORTED_MODULE_7__ic_help_outline_black_18px_svg___default.a,
+};
+
+/* harmony default export */ exports["default"] = images;
+
+
+/***/ }
+/******/ ]);
+});
diff --git a/packages/storybook-ui/src/modules/ui/components/core_menu/floating_block.js b/packages/storybook-ui/src/modules/ui/components/core_menu/floating_block.js
new file mode 100644
index 000000000000..80392eba22c8
--- /dev/null
+++ b/packages/storybook-ui/src/modules/ui/components/core_menu/floating_block.js
@@ -0,0 +1,51 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import CoreMenu from '../../containers/core_menu';
+import { colorScheme, floating } from '../theme';
+import { boxPositions } from '../../../../libs/menu_positions';
+
+const rootStyle = {
+ position: 'absolute',
+
+ backgroundColor: colorScheme.block,
+ borderRadius: 2,
+ paddingBottom: 2,
+ ...floating,
+};
+
+function getPosition(pos) {
+ switch (pos) {
+ case boxPositions.BOTTOM_RIGHT:
+ return {
+ right: 10,
+ bottom: 20,
+ };
+ case boxPositions.TOP_LEFT:
+ return {
+ left: 10,
+ top: 20,
+ };
+ case boxPositions.TOP_RIGHT:
+ return {
+ right: 10,
+ top: 20,
+ };
+ default:
+ return {
+ left: 10,
+ bottom: 20,
+ };
+ }
+}
+
+const FloatingBlock = ({ position }) => (
+
+
+
+);
+
+FloatingBlock.propTypes = {
+ position: PropTypes.string,
+};
+
+export default FloatingBlock;
diff --git a/packages/storybook-ui/src/modules/ui/components/core_menu/index.js b/packages/storybook-ui/src/modules/ui/components/core_menu/index.js
new file mode 100644
index 000000000000..e5d9c1aebc99
--- /dev/null
+++ b/packages/storybook-ui/src/modules/ui/components/core_menu/index.js
@@ -0,0 +1,247 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { features } from '../../../../libs/key_events';
+import { colorScheme, baseFonts } from '../theme';
+import svg from '../assets/svg_package';
+
+const TRANSITION = '100ms linear 0ms';
+
+const rootStyle = {
+ ...baseFonts,
+ fontSize: 12,
+ backgroundColor: colorScheme.block,
+ paddingTop: 4,
+ maxWidth: 400,
+ display: 'flex',
+ flexDirection: 'column',
+ justifyContent: 'space-between',
+ transition: `height ${TRANSITION}`,
+};
+
+function getOptionsList(shortcutOptions, platform) {
+ // manage two separate shortcut keys for
+ // 'mac' & other (windows, linux) platforms
+ const isMac = platform && platform.indexOf('mac') !== -1;
+ const keyTemplate = isMac ? ['⌘ ⇧ ', '⌃ ⇧ '] : ['Ctrl + Shift + '];
+ const getKeys = key => keyTemplate.map(val => val.concat(key)).join(' / ');
+
+ const OptionsList = [
+ {
+ name: 'Fullscreen Mode',
+ keys: getKeys('F'),
+ event: features.FULLSCREEN,
+ value: shortcutOptions.goFullScreen,
+ },
+ {
+ name: 'Addons Panel',
+ keys: getKeys('D'),
+ event: features.DOWN_PANEL,
+ value: shortcutOptions.showDownPanel,
+ },
+ {
+ name: 'Stories Panel',
+ keys: getKeys('L'),
+ event: features.LEFT_PANEL,
+ value: shortcutOptions.showLeftPanel,
+ },
+ {
+ name: 'Addons Panel on the right',
+ keys: getKeys('J'),
+ event: features.DOWN_PANEL_IN_RIGHT,
+ value: shortcutOptions.downPanelInRight,
+ },
+ {
+ name: 'Search Box',
+ keys: getKeys('P'),
+ event: features.SEARCH,
+ value: shortcutOptions.showSearchBox,
+ },
+ ];
+ return OptionsList;
+}
+
+class CoreMenu extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ collapsed: true,
+ short: false,
+ };
+
+ this.hadnleMenu = this.hadnleMenu.bind(this);
+ }
+
+ handleOption(event) {
+ return () => {
+ this.props.emulShortcuts(event);
+ };
+ }
+
+ hadnleMenu() {
+ this.setState({ collapsed: !this.state.collapsed });
+ }
+
+ renderOptions() {
+ const itemStyle = {
+ cursor: 'pointer',
+ userSelect: 'none',
+ backgroundColor: colorScheme.canvasAlt,
+ margin: 4,
+ marginBottom: 1,
+ marginTop: 1,
+ padding: 1,
+ paddingRight: 8,
+ paddingLeft: 4,
+ display: 'flex',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ };
+
+ const iconStyle = {
+ width: 18,
+ opacity: colorScheme.iconsOpacity,
+ marginRight: 8,
+ };
+
+ const blockStyle = {
+ overflowY: 'hidden',
+ backgroundColor: colorScheme.layoutAlt,
+ };
+
+ const option = (val, key) => (
+
+
+
{`${val.name}`}
+
+ );
+
+ const shorcuts = (
+
+
+
Keyboard Shortcuts
+
+ );
+
+ return (
+
+ {
}
+ {getOptionsList(this.props.shortcutOptions).map((val, ind) => option(val, ind))}
+ {shorcuts}
+ {
}
+
+ );
+ }
+
+ renderNavigation() {
+ const blockStyle = {
+ background: colorScheme.block,
+ height: 22,
+ paddingLeft: 6,
+ display: 'flex',
+ justifyContent: 'space-between',
+ };
+
+ const btnsStyle = {
+ display: 'flex',
+ justifyContent: 'space-between',
+ marginRight: 8,
+ };
+
+ const iconStyle = {
+ width: 22,
+ margin: '0px 6px',
+ opacity: colorScheme.iconsOpacity,
+ cursor: 'pointer',
+ };
+
+ const menuStyle = {
+ ...iconStyle,
+ margin: 0,
+ transition: `transform ${TRANSITION}`,
+ transform: this.state.collapsed ? 'rotate(-0.25turn)' : '',
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+
+ render() {
+ const blockStyle = {
+ ...rootStyle,
+ height: this.state.collapsed ? 22 : 158, // note: 158 = 26 + 22 * num_of_items
+ };
+ const content = [this.renderOptions(), this.renderNavigation()];
+
+ return (
+
+ {this.props.downDirection ? content.reverse() : content}
+
+ );
+ }
+}
+
+CoreMenu.propTypes = {
+ selectedKind: PropTypes.string,
+ selectedStory: PropTypes.string,
+ shortcutOptions: PropTypes.object,
+ emulShortcuts: PropTypes.func,
+ openShortcutsHelp: PropTypes.func,
+ downDirection: PropTypes.bool,
+};
+
+CoreMenu.defaultProps = {
+ downDirection: false,
+};
+
+export default CoreMenu;
diff --git a/packages/storybook-ui/src/modules/ui/components/core_menu/index.test.js b/packages/storybook-ui/src/modules/ui/components/core_menu/index.test.js
new file mode 100644
index 000000000000..ccb0fccf9d72
--- /dev/null
+++ b/packages/storybook-ui/src/modules/ui/components/core_menu/index.test.js
@@ -0,0 +1,80 @@
+const { describe, it, beforeEach } = global;
+import React from 'react';
+import { shallow } from 'enzyme';
+import Menu from './floating_block.js';
+import CoreMenu from './';
+import { colorScheme } from '../theme';
+import { boxPositions } from '../../../../libs/menu_positions';
+import { features } from '../../../../libs/key_events';
+
+describe('manager.ui.components.core_menu.floating_block', () => {
+ it('should have theme based background color', () => {
+ const wrap = shallow();
+ const root = wrap.find('div').first();
+ expect(root.props().style.backgroundColor).toEqual(colorScheme.block);
+ });
+
+ it('should have the right position', () => {
+ const wrap = shallow();
+ const root = wrap.find('div').first();
+ expect(root.props().style.right).toEqual(10);
+ expect(root.props().style.bottom).toEqual(20);
+ });
+});
+
+describe('manager.ui.components.core_menu', () => {
+ describe('should fire api callbacks when clicked on shortcut buttons:', () => {
+ let emulShortcuts = jest.fn();
+ let openShortcutsHelp = jest.fn();
+ let wrap;
+
+ beforeEach(() => {
+ emulShortcuts = jest.fn();
+ openShortcutsHelp = jest.fn();
+ wrap = shallow(
+ ,
+ );
+ });
+
+ it('features.FULLSCREEN', () => {
+ const button = wrap.find('div.floating-menu-button').at(0);
+ button.simulate('click');
+ expect(emulShortcuts.mock.calls[0]).toEqual([features.FULLSCREEN]);
+ });
+
+ it('features.DOWN_PANEL', () => {
+ const button = wrap.find('div.floating-menu-button').at(1);
+ button.simulate('click');
+ expect(emulShortcuts.mock.calls[0]).toEqual([features.DOWN_PANEL]);
+ });
+
+ it('features.LEFT_PANEL', () => {
+ const button = wrap.find('div.floating-menu-button').at(2);
+ button.simulate('click');
+ expect(emulShortcuts.mock.calls[0]).toEqual([features.LEFT_PANEL]);
+ });
+
+ it('features.DOWN_PANEL_IN_RIGHT', () => {
+ const button = wrap.find('div.floating-menu-button').at(3);
+ button.simulate('click');
+ expect(emulShortcuts.mock.calls[0]).toEqual([features.DOWN_PANEL_IN_RIGHT]);
+ });
+
+ it('features.SEARCH', () => {
+ const button = wrap.find('div.floating-menu-button').at(4);
+ // console.log(button.props())
+ button.simulate('click');
+ expect(emulShortcuts.mock.calls[0]).toEqual([features.SEARCH]);
+ });
+
+ it('Open Shortcuts Help', () => {
+ const button = wrap.find('div.floating-menu-button').last();
+ button.simulate('click');
+ expect(openShortcutsHelp.mock.calls.length).toEqual(1);
+ });
+ });
+});
diff --git a/packages/storybook-ui/src/modules/ui/components/layout/index.js b/packages/storybook-ui/src/modules/ui/components/layout/index.js
index 3fbb897a80ae..55c90712ceba 100755
--- a/packages/storybook-ui/src/modules/ui/components/layout/index.js
+++ b/packages/storybook-ui/src/modules/ui/components/layout/index.js
@@ -5,6 +5,8 @@ import USplit from './usplit';
import Dimensions from './dimensions';
import SplitPane from 'react-split-pane';
+import FloatingBlock from '../core_menu/floating_block';
+
const rootStyle = {
height: '100vh',
backgroundColor: '#F7F7F7',
@@ -167,10 +169,13 @@ class Layout extends React.Component {
downPanel,
leftPanel,
preview,
+ showFloatingBox,
+ floatingBoxPosition,
} = this.props;
- const { previewPanelDimensions } = this.state;
+ const { previewPanelDimensions } = this.state;
const leftPanelOnTop = false;
+ const showFloatingBlock = showFloatingBox && (goFullScreen || !showLeftPanel);
let previewStyle = normalPreviewStyle;
@@ -230,6 +235,7 @@ class Layout extends React.Component {
}}
>
{preview()}
+ {showFloatingBlock ? : null}
@@ -255,6 +261,8 @@ Layout.propTypes = {
preview: PropTypes.func.isRequired,
downPanel: PropTypes.func.isRequired,
downPanelInRight: PropTypes.bool.isRequired,
+ showFloatingBox: React.PropTypes.bool.isRequired,
+ floatingBoxPosition: React.PropTypes.string.isRequired,
};
export default Layout;
diff --git a/packages/storybook-ui/src/modules/ui/components/left_panel/header.js b/packages/storybook-ui/src/modules/ui/components/left_panel/header.js
index 7d647cf7536f..fc87052c81c8 100755
--- a/packages/storybook-ui/src/modules/ui/components/left_panel/header.js
+++ b/packages/storybook-ui/src/modules/ui/components/left_panel/header.js
@@ -24,33 +24,12 @@ const headingStyle = {
overflow: 'hidden',
};
-const shortcutIconStyle = {
- textTransform: 'uppercase',
- letterSpacing: '3.5px',
- fontSize: 12,
- fontWeight: 'bolder',
- color: 'rgb(130, 130, 130)',
- border: '1px solid rgb(193, 193, 193)',
- textAlign: 'center',
- borderRadius: 2,
- padding: 5,
- cursor: 'pointer',
- margin: 0,
- display: 'inlineBlock',
- paddingLeft: 8,
- float: 'right',
- marginLeft: 5,
- backgroundColor: 'inherit',
- outline: 0,
-};
-
const linkStyle = {
textDecoration: 'none',
};
-const Header = ({ openShortcutsHelp, name, url }) => (
+const Header = ({ name, url }) => (
-
{name}
@@ -58,7 +37,6 @@ const Header = ({ openShortcutsHelp, name, url }) => (
);
Header.propTypes = {
- openShortcutsHelp: PropTypes.func,
name: PropTypes.string,
url: PropTypes.string,
};
diff --git a/packages/storybook-ui/src/modules/ui/components/left_panel/header.test.js b/packages/storybook-ui/src/modules/ui/components/left_panel/header.test.js
index 506044fb1986..aba5c96dd7b3 100755
--- a/packages/storybook-ui/src/modules/ui/components/left_panel/header.test.js
+++ b/packages/storybook-ui/src/modules/ui/components/left_panel/header.test.js
@@ -3,12 +3,13 @@ import { shallow } from 'enzyme';
import Header from './header.js';
describe('manager.ui.components.left_panel.header', () => {
- test('should fire openShortcutsHelp when clicked on shortcut button', () => {
- const openShortcutsHelp = jest.fn();
- const wrap = shallow(
);
-
- wrap.find('button').simulate('click');
-
- expect(openShortcutsHelp).toHaveBeenCalled();
+ it('should render the Title and URL', () => {
+ const title = 'Storybook UI';
+ const url = 'www.example.com';
+ const wrap = shallow(
);
+ const h3 = wrap.find('h3').first();
+ expect(h3.text()).toEqual(title);
+ const link = wrap.find('a').first();
+ expect(link.props().href).toEqual(url);
});
});
diff --git a/packages/storybook-ui/src/modules/ui/components/left_panel/index.js b/packages/storybook-ui/src/modules/ui/components/left_panel/index.js
index 974a5dc69263..7df82e433314 100755
--- a/packages/storybook-ui/src/modules/ui/components/left_panel/index.js
+++ b/packages/storybook-ui/src/modules/ui/components/left_panel/index.js
@@ -1,30 +1,38 @@
import PropTypes from 'prop-types';
import React from 'react';
import Header from './header';
+import Menu from './menu';
import Stories from './stories';
import TextFilter from './text_filter';
import pick from 'lodash.pick';
const scrollStyle = {
- height: 'calc(100vh - 105px)',
+ flexGrow: 1,
marginTop: 10,
+ marginLeft: 10,
overflowY: 'auto',
};
const mainStyle = {
- padding: '10px 0 10px 10px',
+ display: 'flex',
+ flexDirection: 'column',
+ justifyContent: 'space-between',
+ height: '100%',
};
const storyProps = ['stories', 'selectedKind', 'selectedStory', 'onSelectStory'];
const LeftPanel = props => (
-
-
props.onStoryFilter('')}
- onChange={text => props.onStoryFilter(text)}
- />
+
+
+ props.onStoryFilter('')}
+ onChange={text => props.onStoryFilter(text)}
+ />
+
+
{props.stories ? : null}
diff --git a/packages/storybook-ui/src/modules/ui/components/left_panel/menu.js b/packages/storybook-ui/src/modules/ui/components/left_panel/menu.js
new file mode 100644
index 000000000000..8fc33ebc94ad
--- /dev/null
+++ b/packages/storybook-ui/src/modules/ui/components/left_panel/menu.js
@@ -0,0 +1,18 @@
+import React from 'react';
+import CoreMenu from '../../containers/core_menu';
+import { colorScheme } from '../theme';
+
+const rootStyle = {
+ backgroundColor: colorScheme.block,
+ padding: 6,
+ paddingTop: 2,
+ marginTop: 10,
+};
+
+const Menu = () => (
+
+
+
+);
+
+export default Menu;
diff --git a/packages/storybook-ui/src/modules/ui/components/left_panel/menu.test.js b/packages/storybook-ui/src/modules/ui/components/left_panel/menu.test.js
new file mode 100644
index 000000000000..8c8694312fc5
--- /dev/null
+++ b/packages/storybook-ui/src/modules/ui/components/left_panel/menu.test.js
@@ -0,0 +1,14 @@
+const { describe, it } = global;
+import React from 'react';
+import { shallow } from 'enzyme';
+import Menu from './menu.js';
+import { colorScheme } from '../theme';
+
+describe('manager.ui.components.left_panel.menu', () => {
+ it('should have theme based background color', () => {
+ const wrap = shallow();
+
+ const root = wrap.find('div').first();
+ expect(root.props().style.backgroundColor).toEqual(colorScheme.block);
+ });
+});
diff --git a/packages/storybook-ui/src/modules/ui/components/theme.js b/packages/storybook-ui/src/modules/ui/components/theme.js
index 5f2962469ba1..82ac468685db 100755
--- a/packages/storybook-ui/src/modules/ui/components/theme.js
+++ b/packages/storybook-ui/src/modules/ui/components/theme.js
@@ -1,7 +1,23 @@
-export const baseFonts = {
- fontFamily: `
- -apple-system, ".SFNSText-Regular", "San Francisco", "Roboto",
- "Segoe UI", "Helvetica Neue", "Lucida Grande", sans-serif
- `,
- color: '#444',
-};
+export const colorScheme = {
+ block: '#e2e2e2',
+ layoutAlt: '#f7f7f7',
+ canvasAlt: '#ffffff',
+
+ text: '#444',
+ layout: '#f7f7f7',
+ canvas: '#ffffff',
+ iconsOpacity: 0.6,
+};
+
+export const baseFonts = {
+ fontFamily: `
+ -apple-system, ".SFNSText-Regular", "San Francisco", "Roboto",
+ "Segoe UI", "Helvetica Neue", "Lucida Grande", sans-serif
+ `,
+ color: colorScheme.text,
+};
+
+export const floating = {
+ zIndex: 10,
+ boxShadow: '1px 1px 6px rgba(0, 0, 0, 0.4)',
+};
diff --git a/packages/storybook-ui/src/modules/ui/containers/core_menu.js b/packages/storybook-ui/src/modules/ui/containers/core_menu.js
new file mode 100644
index 000000000000..d78d5814cca8
--- /dev/null
+++ b/packages/storybook-ui/src/modules/ui/containers/core_menu.js
@@ -0,0 +1,20 @@
+import CoreMenu from '../components/core_menu';
+import genPoddaLoader from '../libs/gen_podda_loader';
+import compose from '../../../compose';
+
+export const mapper = (state, props, { actions }) => {
+ const actionMap = actions();
+ const { selectedKind, selectedStory, shortcutOptions } = state;
+
+ const data = {
+ selectedKind,
+ selectedStory,
+ shortcutOptions,
+ emulShortcuts: actionMap.shortcuts.handleEvent,
+ openShortcutsHelp: actionMap.ui.toggleShortcutsHelp,
+ };
+
+ return data;
+};
+
+export default compose(genPoddaLoader(mapper))(CoreMenu);
diff --git a/packages/storybook-ui/src/modules/ui/containers/core_menu.test.js b/packages/storybook-ui/src/modules/ui/containers/core_menu.test.js
new file mode 100644
index 000000000000..fb38817c7cdb
--- /dev/null
+++ b/packages/storybook-ui/src/modules/ui/containers/core_menu.test.js
@@ -0,0 +1,33 @@
+const { describe, it } = global;
+import { mapper } from './core_menu';
+
+describe('manager.ui.containers.core_menu', () => {
+ describe('mapper', () => {
+ it('should give correct data', () => {
+ const toggleShortcutsHelp = () => 'toggleShortcutsHelp';
+ const handleEvent = () => 'handleEvent';
+
+ const state = {
+ selectedKind: 'aa',
+ selectedStory: 'bb',
+ shortcutOptions: { actions: 'cc' },
+ emulShortcuts: handleEvent,
+ openShortcutsHelp: toggleShortcutsHelp,
+ };
+ const props = {};
+ const env = {
+ actions: () => ({
+ ui: {
+ toggleShortcutsHelp,
+ },
+ shortcuts: {
+ handleEvent,
+ },
+ }),
+ };
+
+ const data = mapper(state, props, env);
+ expect(data).toEqual(state);
+ });
+ });
+});
diff --git a/packages/storybook-ui/src/modules/ui/containers/layout.js b/packages/storybook-ui/src/modules/ui/containers/layout.js
index 2abed63b41d1..4d4e89d82d6a 100755
--- a/packages/storybook-ui/src/modules/ui/containers/layout.js
+++ b/packages/storybook-ui/src/modules/ui/containers/layout.js
@@ -1,9 +1,11 @@
-import pick from 'lodash.pick';
-import Layout from '../components/layout';
-import genPoddaLoader from '../libs/gen_podda_loader';
-import compose from '../../../compose';
-
-export const mapper = ({ shortcutOptions }) =>
- pick(shortcutOptions, 'showLeftPanel', 'showDownPanel', 'goFullScreen', 'downPanelInRight');
-
-export default compose(genPoddaLoader(mapper))(Layout);
+import pick from 'lodash.pick';
+import Layout from '../components/layout';
+import genPoddaLoader from '../libs/gen_podda_loader';
+import compose from '../../../compose';
+
+export const mapper = ({ shortcutOptions, floatingBoxOptions }) => ({
+ ...pick(shortcutOptions, 'showLeftPanel', 'showDownPanel', 'goFullScreen', 'downPanelInRight'),
+ ...pick(floatingBoxOptions, 'showFloatingBox', 'floatingBoxPosition'),
+});
+
+export default compose(genPoddaLoader(mapper))(Layout);
diff --git a/packages/storybook-ui/src/modules/ui/index.js b/packages/storybook-ui/src/modules/ui/index.js
index 321a4c535a0c..416ddd178602 100755
--- a/packages/storybook-ui/src/modules/ui/index.js
+++ b/packages/storybook-ui/src/modules/ui/index.js
@@ -3,12 +3,17 @@ import actions from './actions';
import initPanels from './configs/init_panels';
import handleRouting from './configs/handle_routing';
import handleKeyEvents from './configs/handle_keyevents';
+import { boxPositions } from '../../libs/menu_positions';
export default {
routes,
actions,
defaultState: {
showShortcutsHelp: false,
+ floatingBoxOptions: {
+ showFloatingBox: true,
+ floatingBoxPosition: boxPositions.BOTTOM_LEFT,
+ },
},
load(c, a) {
initPanels(c, a);