diff --git a/examples/cra-kitchen-sink/.storybook/config.js b/examples/cra-kitchen-sink/.storybook/config.js index 3ee683d25cd..f0d45642698 100644 --- a/examples/cra-kitchen-sink/.storybook/config.js +++ b/examples/cra-kitchen-sink/.storybook/config.js @@ -8,14 +8,14 @@ setOptions({ name: 'CRA Kitchen Sink', url: 'https://github.com/storybooks/storybook/tree/master/examples/cra-kitchen-sink', goFullScreen: false, - showStoriesPanel: true, + // showStoriesPanel: true, showAddonsPanel: true, showSearchBox: false, addonPanelInRight: true, sortStoriesByKind: false, hierarchySeparator: /\./, hierarchyRootSeparator: /\|/, -}); +}, 'shit'); // deprecated usage of infoAddon setAddon(infoAddon); diff --git a/lib/ui/src/modules/shortcuts/index.js b/lib/ui/src/modules/shortcuts/index.js index b8c3b8fa659..c24a03e05b3 100755 --- a/lib/ui/src/modules/shortcuts/index.js +++ b/lib/ui/src/modules/shortcuts/index.js @@ -1,11 +1,16 @@ import actions from './actions'; +import checkIfMobileDevice from '../ui/libs/is_mobile_device'; + +const { userAgent } = global.window.navigator; +const isMobileDevice = checkIfMobileDevice(userAgent); export default { actions, defaultState: { + isMobileDevice, shortcutOptions: { goFullScreen: false, - showStoriesPanel: true, + showStoriesPanel: !isMobileDevice, showAddonPanel: true, showSearchBox: false, addonPanelInRight: false, diff --git a/lib/ui/src/modules/ui/components/__snapshots__/header.stories.storyshot b/lib/ui/src/modules/ui/components/__snapshots__/header.stories.storyshot new file mode 100644 index 00000000000..e52a73ab1c0 --- /dev/null +++ b/lib/ui/src/modules/ui/components/__snapshots__/header.stories.storyshot @@ -0,0 +1,30 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots ui/stories/Header simple 1`] = ` +
+ + +

+ name +

+
+ +
+`; diff --git a/lib/ui/src/modules/ui/components/stories_panel/header.js b/lib/ui/src/modules/ui/components/header.js similarity index 52% rename from lib/ui/src/modules/ui/components/stories_panel/header.js rename to lib/ui/src/modules/ui/components/header.js index 35a941ac8d8..91750c3b1e1 100755 --- a/lib/ui/src/modules/ui/components/stories_panel/header.js +++ b/lib/ui/src/modules/ui/components/header.js @@ -1,12 +1,13 @@ import PropTypes from 'prop-types'; import React from 'react'; import { baseFonts } from '@storybook/components'; +// import isMobileDevice from '../libs/is_mobile_device'; -const wrapperStyle = { - background: '#F7F7F7', - marginBottom: 10, +const wrapperStyle = isMobileDevice => ({ + background: isMobileDevice ? 'none' : '#F7F7F7', + margin: isMobileDevice ? '10px 0' : '0 0 10px', display: 'flex', -}; +}); const headingStyle = { ...baseFonts, @@ -22,7 +23,7 @@ const headingStyle = { overflow: 'hidden', }; -const shortcutIconStyle = { +const iconStyle = isMobileDevice => ({ textTransform: 'uppercase', fontSize: 12, fontWeight: 'bolder', @@ -33,29 +34,39 @@ const shortcutIconStyle = { cursor: 'pointer', display: 'inlineBlock', padding: 0, - margin: '0 0 0 5px', + margin: isMobileDevice ? '0 15px' : '0 0 0 5px', backgroundColor: 'inherit', outline: 0, width: 30, flexShrink: 0, +}); + +const burgerIconStyle = { + ...iconStyle(true), + paddingBottom: 2, }; -const linkStyle = { +const linkStyle = isMobileDevice => ({ textDecoration: 'none', flexGrow: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', - border: '1px solid rgb(193, 193, 193)', + border: isMobileDevice ? 'none' : '1px solid rgb(193, 193, 193)', borderRadius: 2, -}; +}); -const Header = ({ openShortcutsHelp, name, url }) => ( -
- +const Header = ({ openShortcutsHelp, onBurgerButtonClick, name, url, isMobileDevice }) => ( +
+ {isMobileDevice && ( + + )} +

{name}

-
@@ -63,14 +74,18 @@ const Header = ({ openShortcutsHelp, name, url }) => ( Header.defaultProps = { openShortcutsHelp: null, + onBurgerButtonClick: null, name: '', url: '', + isMobileDevice: false, }; Header.propTypes = { openShortcutsHelp: PropTypes.func, + onBurgerButtonClick: PropTypes.func, name: PropTypes.string, url: PropTypes.string, + isMobileDevice: PropTypes.bool, }; export default Header; diff --git a/lib/ui/src/modules/ui/components/stories_panel/header.stories.js b/lib/ui/src/modules/ui/components/header.stories.js similarity index 100% rename from lib/ui/src/modules/ui/components/stories_panel/header.stories.js rename to lib/ui/src/modules/ui/components/header.stories.js diff --git a/lib/ui/src/modules/ui/components/stories_panel/header.test.js b/lib/ui/src/modules/ui/components/header.test.js similarity index 100% rename from lib/ui/src/modules/ui/components/stories_panel/header.test.js rename to lib/ui/src/modules/ui/components/header.test.js diff --git a/lib/ui/src/modules/ui/components/layout/index.js b/lib/ui/src/modules/ui/components/layout/index.js index e3e3864356f..94ebb0f6b77 100755 --- a/lib/ui/src/modules/ui/components/layout/index.js +++ b/lib/ui/src/modules/ui/components/layout/index.js @@ -98,7 +98,11 @@ const defaultSizes = { }, }; -const saveSizes = sizes => { +const saveSizes = (sizes, isMobileDevice) => { + if (isMobileDevice) { + return true; + } + try { localStorage.setItem('panelSizes', JSON.stringify(sizes)); return true; @@ -107,7 +111,11 @@ const saveSizes = sizes => { } }; -const getSavedSizes = sizes => { +const getSavedSizes = (sizes, isMobileDevice) => { + if (isMobileDevice) { + return sizes; + } + try { const panelSizes = localStorage.getItem('panelSizes'); if (panelSizes) { @@ -125,7 +133,7 @@ class Layout extends React.Component { constructor(props) { super(props); - this.layerSizes = getSavedSizes(defaultSizes); + this.layerSizes = getSavedSizes(defaultSizes, props.isMobileDevice); this.state = { previewPanelDimensions: { @@ -159,7 +167,7 @@ class Layout extends React.Component { onResize(pane, mode) { return size => { this.layerSizes[pane][mode] = size; - saveSizes(this.layerSizes); + saveSizes(this.layerSizes, this.props.isMobileDevice); const { clientWidth, clientHeight } = this.previewPanelRef; @@ -177,13 +185,15 @@ class Layout extends React.Component { goFullScreen, showStoriesPanel, showAddonPanel, - addonPanelInRight, addonPanel, storiesPanel, preview, + isMobileDevice, } = this.props; const { previewPanelDimensions } = this.state; + const addonPanelInRight = isMobileDevice ? 0 : this.props.addonPanelInRight; + const storiesPanelOnTop = false; let previewStyle = normalPreviewStyle; @@ -192,7 +202,7 @@ class Layout extends React.Component { previewStyle = fullScreenPreviewStyle; } - const sizes = getSavedSizes(this.layerSizes); + const sizes = getSavedSizes(this.layerSizes, isMobileDevice); const storiesPanelDefaultSize = !storiesPanelOnTop ? sizes.storiesPanel.left @@ -209,7 +219,7 @@ class Layout extends React.Component { -
+ {!isMobileDevice &&
} onStoryFilter('')} diff --git a/lib/ui/src/modules/ui/components/stories_panel/index.test.js b/lib/ui/src/modules/ui/components/stories_panel/index.test.js index f88a6705777..70ffd9fbd5d 100755 --- a/lib/ui/src/modules/ui/components/stories_panel/index.test.js +++ b/lib/ui/src/modules/ui/components/stories_panel/index.test.js @@ -1,7 +1,7 @@ import React from 'react'; import { shallow } from 'enzyme'; import StoriesPanel from './index'; -import Header from './header'; +import Header from '../header'; import TextFilter from './text_filter'; import Stories from './stories_tree'; import { createHierarchies } from '../../libs/hierarchy'; diff --git a/lib/ui/src/modules/ui/containers/header.js b/lib/ui/src/modules/ui/containers/header.js new file mode 100755 index 00000000000..6637dcc049e --- /dev/null +++ b/lib/ui/src/modules/ui/containers/header.js @@ -0,0 +1,38 @@ +import pick from 'lodash.pick'; +import Header from '../components/header'; +import genPoddaLoader from '../libs/gen_podda_loader'; +import compose from '../../../compose'; + +export const headerMapper = (state, props, { actions }) => { + const currentOptions = pick( + state.shortcutOptions, + 'showStoriesPanel', + 'showAddonPanel', + 'goFullScreen', + 'addonPanelInRight' + ); + + const actionMap = actions(); + const { uiOptions, isMobileDevice } = state; + const { name, url } = uiOptions; + + const handleBurgerButtonClick = () => { + actionMap.shortcuts.setOptions({ + showStoriesPanel: !currentOptions.showStoriesPanel, + }); + }; + + const addonPanelInRight = isMobileDevice ? false : currentOptions.addonPanelInRight; + + return { + name, + url, + ...currentOptions, + openShortcutsHelp: actionMap.ui.toggleShortcutsHelp, + onBurgerButtonClick: handleBurgerButtonClick, + isMobileDevice, + addonPanelInRight, + }; +}; + +export default compose(genPoddaLoader(headerMapper))(Header); diff --git a/lib/ui/src/modules/ui/containers/header.test.js b/lib/ui/src/modules/ui/containers/header.test.js new file mode 100755 index 00000000000..734ea22f60a --- /dev/null +++ b/lib/ui/src/modules/ui/containers/header.test.js @@ -0,0 +1,39 @@ +import { headerMapper } from './header'; + +describe('manager.ui.containers.header', () => { + describe('headerMapper', () => { + test('should give correct data', () => { + const uiOptions = { + name: 'foo', + url: 'bar', + }; + const shortcutOptions = { + showStoriesPanel: 'aa', + showAddonPanel: 'bb', + goFullScreen: 'cc', + addonPanelInRight: true, + }; + const toggleShortcutsHelp = () => 'toggleShortcutsHelp'; + const props = {}; + const env = { + actions: () => ({ + ui: { + toggleShortcutsHelp, + }, + }), + }; + const state = { + uiOptions, + shortcutOptions, + }; + + const result = headerMapper(state, props, env); + + expect(result.showStoriesPanel).toEqual('aa'); + expect(result.showAddonPanel).toEqual('bb'); + expect(result.goFullScreen).toEqual('cc'); + expect(result.addonPanelInRight).toEqual('lalala'); + expect(result.openShortcutsHelp).toBe(toggleShortcutsHelp); + }); + }); +}); diff --git a/lib/ui/src/modules/ui/containers/layout.js b/lib/ui/src/modules/ui/containers/layout.js index cb89879735e..4ed41635229 100755 --- a/lib/ui/src/modules/ui/containers/layout.js +++ b/lib/ui/src/modules/ui/containers/layout.js @@ -2,8 +2,21 @@ import pick from 'lodash.pick'; import Layout from '../components/layout'; import genPoddaLoader from '../libs/gen_podda_loader'; import compose from '../../../compose'; +import isMobileDevice from '../libs/is_mobile_device'; -export const mapper = ({ shortcutOptions }) => - pick(shortcutOptions, 'showStoriesPanel', 'showAddonPanel', 'goFullScreen', 'addonPanelInRight'); +export const shortcutMapper = state => { + const currentOptions = pick( + state.shortcutOptions, + 'showStoriesPanel', + 'showAddonPanel', + 'goFullScreen', + 'addonPanelInRight' + ); -export default compose(genPoddaLoader(mapper))(Layout); + return { + ...currentOptions, + isMobileDevice, + }; +}; + +export default compose(genPoddaLoader(shortcutMapper))(Layout); diff --git a/lib/ui/src/modules/ui/containers/layout.test.js b/lib/ui/src/modules/ui/containers/layout.test.js index 2aabf74f37d..e656cccdff3 100755 --- a/lib/ui/src/modules/ui/containers/layout.test.js +++ b/lib/ui/src/modules/ui/containers/layout.test.js @@ -1,7 +1,7 @@ -import { mapper } from './layout'; +import { shortcutMapper } from './layout'; describe('manager.ui.containers.layout', () => { - describe('mapper', () => { + describe('shortcutMapper', () => { test('should give correct data', () => { const state = { shortcutOptions: { @@ -10,7 +10,7 @@ describe('manager.ui.containers.layout', () => { goFullScreen: 'cc', }, }; - const data = mapper(state); + const data = shortcutMapper(state); expect(data).toEqual(state.shortcutOptions); }); diff --git a/lib/ui/src/modules/ui/containers/stories_panel.js b/lib/ui/src/modules/ui/containers/stories_panel.js index bf0f0a6f07a..5dbd7f4a471 100755 --- a/lib/ui/src/modules/ui/containers/stories_panel.js +++ b/lib/ui/src/modules/ui/containers/stories_panel.js @@ -15,8 +15,6 @@ export const mapper = (state, props, { actions }) => { const { stories, selectedKind, selectedStory, uiOptions, storyFilter } = state; const { - name, - url, sortStoriesByKind, hierarchySeparator, hierarchyRootSeparator, @@ -52,10 +50,7 @@ export const mapper = (state, props, { actions }) => { storyFilter, onStoryFilter: actionMap.ui.setStoryFilter, - openShortcutsHelp: actionMap.ui.toggleShortcutsHelp, sidebarAnimations, - name, - url, }; }; diff --git a/lib/ui/src/modules/ui/containers/stories_panel.test.js b/lib/ui/src/modules/ui/containers/stories_panel.test.js index a95ff17d36a..0623be9ff58 100755 --- a/lib/ui/src/modules/ui/containers/stories_panel.test.js +++ b/lib/ui/src/modules/ui/containers/stories_panel.test.js @@ -12,7 +12,6 @@ describe('manager.ui.containers.stories_panel', () => { url: 'bar', }; const selectStory = () => 'selectStory'; - const toggleShortcutsHelp = () => 'toggleShortcutsHelp'; const setStoryFilter = () => 'setStoryFilter'; const props = {}; const env = { @@ -21,7 +20,6 @@ describe('manager.ui.containers.stories_panel', () => { selectStory, }, ui: { - toggleShortcutsHelp, setStoryFilter, }, }), @@ -57,7 +55,6 @@ describe('manager.ui.containers.stories_panel', () => { expect(result.storyFilter).toBe(null); expect(result.onSelectStory).toBe(selectStory); expect(result.onStoryFilter).toBe(setStoryFilter); - expect(result.openShortcutsHelp).toBe(toggleShortcutsHelp); }); test('should filter stories according to the given filter', () => { diff --git a/lib/ui/src/modules/ui/libs/is_mobile_device.js b/lib/ui/src/modules/ui/libs/is_mobile_device.js new file mode 100644 index 00000000000..ede024a60b8 --- /dev/null +++ b/lib/ui/src/modules/ui/libs/is_mobile_device.js @@ -0,0 +1,7 @@ +export default userAgent => { + if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(userAgent)) { + return true; + } + + return false; +}; diff --git a/lib/ui/src/modules/ui/libs/is_mobile_device.test.js b/lib/ui/src/modules/ui/libs/is_mobile_device.test.js new file mode 100644 index 00000000000..379759f80db --- /dev/null +++ b/lib/ui/src/modules/ui/libs/is_mobile_device.test.js @@ -0,0 +1,28 @@ +import isMobileDevice from './is_mobile_device'; + +describe('manager.ui.libs.is_mobile_device', () => { + it('should detect if storybook is open on mobile device', () => { + // chrome + expect( + isMobileDevice( + 'Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19' + ) + ).toEqual(true); + + expect( + isMobileDevice('Mozilla/5.0 (Android 4.4; Mobile; rv:41.0) Gecko/41.0 Firefox/41.0') + ).toEqual(true); + + expect( + isMobileDevice( + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.162 Safari/537.36' + ) + ).toEqual(false); + + expect( + isMobileDevice( + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/604.5.6 (KHTML, like Gecko) Version/11.0.3 Safari/604.5.6' + ) + ).toEqual(false); + }); +}); diff --git a/lib/ui/src/modules/ui/routes.js b/lib/ui/src/modules/ui/routes.js index 79929c70408..dccbae7cdb4 100755 --- a/lib/ui/src/modules/ui/routes.js +++ b/lib/ui/src/modules/ui/routes.js @@ -6,11 +6,12 @@ import StoriesPanel from './containers/stories_panel'; import AddonPanel from './containers/addon_panel'; import ShortcutsHelp from './containers/shortcuts_help'; import SearchBox from './containers/search_box'; +import Header from './containers/header'; export default function(injectDeps, { clientStore, provider, domNode }) { + const state = clientStore.getAll(); // generate preview const Preview = () => { - const state = clientStore.getAll(); const preview = provider.renderPreview(state.selectedKind, state.selectedStory); return preview; }; @@ -20,6 +21,7 @@ export default function(injectDeps, { clientStore, provider, domNode }) { const root = (
+ {state.isMobileDevice &&
} } preview={() => }