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) => ( +
+ checked_box + {`${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 ( +
+
+ menu + +
+
+ previous +
+
+ previous +
+
+ next +
+
+ next +
+
+
+
+ ); + } + + 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);