Merge pull request #3337 from denzenin/mobile-device-view

Mobile device view: toggling stories panel with ☰ button
This commit is contained in:
Filipp Riabchun 2018-04-17 23:37:05 +03:00 committed by GitHub
commit b6178a2a96
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 328 additions and 81 deletions

View File

@ -9,7 +9,9 @@ function renderMarkdown(text, options) {
const decorator = options => { const decorator = options => {
const channel = addons.getChannel(); const channel = addons.getChannel();
return (getStory, context) => { return (getStory, context) => {
const { parameters: { notes } } = context; const {
parameters: { notes },
} = context;
const storyOptions = notes || options; const storyOptions = notes || options;
if (storyOptions) { if (storyOptions) {

View File

@ -8,7 +8,6 @@ setOptions({
name: 'CRA Kitchen Sink', name: 'CRA Kitchen Sink',
url: 'https://github.com/storybooks/storybook/tree/master/examples/cra-kitchen-sink', url: 'https://github.com/storybooks/storybook/tree/master/examples/cra-kitchen-sink',
goFullScreen: false, goFullScreen: false,
showStoriesPanel: true,
showAddonsPanel: true, showAddonsPanel: true,
showSearchBox: false, showSearchBox: false,
addonPanelInRight: true, addonPanelInRight: true,

View File

@ -1,11 +1,16 @@
import actions from './actions'; import actions from './actions';
import checkIfMobileDevice from '../ui/libs/is_mobile_device';
const { userAgent } = global.window.navigator;
const isMobileDevice = checkIfMobileDevice(userAgent);
export default { export default {
actions, actions,
defaultState: { defaultState: {
isMobileDevice,
shortcutOptions: { shortcutOptions: {
goFullScreen: false, goFullScreen: false,
showStoriesPanel: true, showStoriesPanel: !isMobileDevice,
showAddonPanel: true, showAddonPanel: true,
showSearchBox: false, showSearchBox: false,
addonPanelInRight: false, addonPanelInRight: false,

View File

@ -0,0 +1,54 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Storyshots ui/stories/Header mobile-view 1`] = `
<div
style="background:none;margin:10px 0;display:flex"
>
<button
style="text-transform:uppercase;font-size:12px;font-weight:bolder;color:rgb(130, 130, 130);border:1px solid rgb(193, 193, 193);text-align:center;border-radius:2px;cursor:pointer;display:inlineBlock;padding:0;margin:0 15px;background-color:inherit;outline:0;width:30px;flex-shrink:0;padding-bottom:2px"
>
</button>
<a
href="http://google.com"
rel="noopener noreferrer"
style="text-decoration:none;flex-grow:1;display:flex;align-items:center;justify-content:center;border:none;border-radius:2px"
target="_blank"
>
<h3
style="font-family:-apple-system, \\".SFNSText-Regular\\", \\"San Francisco\\", BlinkMacSystemFont, \\"Segoe UI\\", \\"Roboto\\", \\"Oxygen\\", \\"Ubuntu\\", \\"Cantarell\\", \\"Fira Sans\\", \\"Droid Sans\\", \\"Helvetica Neue\\", \\"Lucida Grande\\", \\"Arial\\", sans-serif;color:#828282;-webkit-font-smoothing:antialiased;text-transform:uppercase;letter-spacing:1.5px;font-size:12px;font-weight:bolder;text-align:center;cursor:pointer;padding:5px;margin:0;overflow:hidden"
>
name
</h3>
</a>
<button
style="text-transform:uppercase;font-size:12px;font-weight:bolder;color:rgb(130, 130, 130);border:1px solid rgb(193, 193, 193);text-align:center;border-radius:2px;cursor:pointer;display:inlineBlock;padding:0;margin:0 15px;background-color:inherit;outline:0;width:30px;flex-shrink:0"
>
</button>
</div>
`;
exports[`Storyshots ui/stories/Header simple 1`] = `
<div
style="background:#F7F7F7;margin:0 0 10px;display:flex"
>
<a
href="http://google.com"
rel="noopener noreferrer"
style="text-decoration:none;flex-grow:1;display:flex;align-items:center;justify-content:center;border:1px solid rgb(193, 193, 193);border-radius:2px"
target="_blank"
>
<h3
style="font-family:-apple-system, \\".SFNSText-Regular\\", \\"San Francisco\\", BlinkMacSystemFont, \\"Segoe UI\\", \\"Roboto\\", \\"Oxygen\\", \\"Ubuntu\\", \\"Cantarell\\", \\"Fira Sans\\", \\"Droid Sans\\", \\"Helvetica Neue\\", \\"Lucida Grande\\", \\"Arial\\", sans-serif;color:#828282;-webkit-font-smoothing:antialiased;text-transform:uppercase;letter-spacing:1.5px;font-size:12px;font-weight:bolder;text-align:center;cursor:pointer;padding:5px;margin:0;overflow:hidden"
>
name
</h3>
</a>
<button
style="text-transform:uppercase;font-size:12px;font-weight:bolder;color:rgb(130, 130, 130);border:1px solid rgb(193, 193, 193);text-align:center;border-radius:2px;cursor:pointer;display:inlineBlock;padding:0;margin:0 0 0 5px;background-color:inherit;outline:0;width:30px;flex-shrink:0"
>
</button>
</div>
`;

View File

@ -2,11 +2,11 @@ import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import { baseFonts } from '@storybook/components'; import { baseFonts } from '@storybook/components';
const wrapperStyle = { const wrapperStyle = isMobileDevice => ({
background: '#F7F7F7', background: isMobileDevice ? 'none' : '#F7F7F7',
marginBottom: 10, margin: isMobileDevice ? '10px 0' : '0 0 10px',
display: 'flex', display: 'flex',
}; });
const headingStyle = { const headingStyle = {
...baseFonts, ...baseFonts,
@ -22,7 +22,7 @@ const headingStyle = {
overflow: 'hidden', overflow: 'hidden',
}; };
const shortcutIconStyle = { const iconStyle = isMobileDevice => ({
textTransform: 'uppercase', textTransform: 'uppercase',
fontSize: 12, fontSize: 12,
fontWeight: 'bolder', fontWeight: 'bolder',
@ -33,30 +33,47 @@ const shortcutIconStyle = {
cursor: 'pointer', cursor: 'pointer',
display: 'inlineBlock', display: 'inlineBlock',
padding: 0, padding: 0,
margin: '0 0 0 5px', margin: isMobileDevice ? '0 15px' : '0 0 0 5px',
backgroundColor: 'inherit', backgroundColor: 'inherit',
outline: 0, outline: 0,
width: 30, width: 30,
flexShrink: 0, flexShrink: 0,
});
const burgerIconStyle = {
...iconStyle(true),
paddingBottom: 2,
}; };
const linkStyle = { const linkStyle = isMobileDevice => ({
textDecoration: 'none', textDecoration: 'none',
flexGrow: 1, flexGrow: 1,
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
border: '1px solid rgb(193, 193, 193)', border: isMobileDevice ? 'none' : '1px solid rgb(193, 193, 193)',
borderRadius: 2, borderRadius: 2,
}; });
const Header = ({ openShortcutsHelp, name, url, enableShortcutsHelp }) => ( const Header = ({
<div style={wrapperStyle}> openShortcutsHelp,
<a style={linkStyle} href={url} target="_blank" rel="noopener noreferrer"> onBurgerButtonClick,
name,
url,
enableShortcutsHelp,
isMobileDevice,
}) => (
<div style={wrapperStyle(isMobileDevice)}>
{isMobileDevice && (
<button style={burgerIconStyle} onClick={onBurgerButtonClick}>
</button>
)}
<a style={linkStyle(isMobileDevice)} href={url} target="_blank" rel="noopener noreferrer">
<h3 style={headingStyle}>{name}</h3> <h3 style={headingStyle}>{name}</h3>
</a> </a>
{enableShortcutsHelp && ( {enableShortcutsHelp && (
<button style={shortcutIconStyle} onClick={openShortcutsHelp}> <button style={iconStyle(isMobileDevice)} onClick={openShortcutsHelp}>
</button> </button>
)} )}
@ -65,15 +82,19 @@ const Header = ({ openShortcutsHelp, name, url, enableShortcutsHelp }) => (
Header.defaultProps = { Header.defaultProps = {
openShortcutsHelp: null, openShortcutsHelp: null,
onBurgerButtonClick: null,
enableShortcutsHelp: true, enableShortcutsHelp: true,
name: '', name: '',
url: '', url: '',
isMobileDevice: false,
}; };
Header.propTypes = { Header.propTypes = {
openShortcutsHelp: PropTypes.func, openShortcutsHelp: PropTypes.func,
onBurgerButtonClick: PropTypes.func,
name: PropTypes.string, name: PropTypes.string,
url: PropTypes.string, url: PropTypes.string,
isMobileDevice: PropTypes.bool,
enableShortcutsHelp: PropTypes.bool, enableShortcutsHelp: PropTypes.bool,
}; };

View File

@ -0,0 +1,19 @@
import React from 'react';
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import Header from './header';
const openShortcutsHelp = action('openShortcutsHelp');
storiesOf('ui/stories/Header', module)
.add('simple', () => (
<Header name="name" url="http://google.com" openShortcutsHelp={openShortcutsHelp} />
))
.add('mobile-view', () => (
<Header
name="name"
url="http://google.com"
openShortcutsHelp={openShortcutsHelp}
isMobileDevice
/>
));

View File

@ -130,7 +130,7 @@ class Layout extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.layerSizes = getSavedSizes(defaultSizes); this.layerSizes = getSavedSizes(defaultSizes, props.isMobileDevice);
this.state = { this.state = {
previewPanelDimensions: { previewPanelDimensions: {
@ -188,13 +188,15 @@ class Layout extends React.Component {
goFullScreen, goFullScreen,
showStoriesPanel, showStoriesPanel,
showAddonPanel, showAddonPanel,
addonPanelInRight,
addonPanel, addonPanel,
storiesPanel, storiesPanel,
preview, preview,
isMobileDevice,
} = this.props; } = this.props;
const { previewPanelDimensions } = this.state; const { previewPanelDimensions } = this.state;
const addonPanelInRight = isMobileDevice ? false : this.props.addonPanelInRight;
const storiesPanelOnTop = false; const storiesPanelOnTop = false;
let previewStyle = normalPreviewStyle; let previewStyle = normalPreviewStyle;
@ -203,7 +205,7 @@ class Layout extends React.Component {
previewStyle = fullScreenPreviewStyle; previewStyle = fullScreenPreviewStyle;
} }
const sizes = getSavedSizes(this.layerSizes); const sizes = getSavedSizes(this.layerSizes, isMobileDevice);
const storiesPanelDefaultSize = !storiesPanelOnTop const storiesPanelDefaultSize = !storiesPanelOnTop
? sizes.storiesPanel.left ? sizes.storiesPanel.left
@ -219,8 +221,8 @@ class Layout extends React.Component {
<div style={rootStyle}> <div style={rootStyle}>
<SplitPane <SplitPane
split={storiesSplit} split={storiesSplit}
allowResize={showStoriesPanel} allowResize={isMobileDevice ? false : showStoriesPanel}
minSize={150} minSize={isMobileDevice ? 0 : 150}
maxSize={-400} maxSize={-400}
size={showStoriesPanel ? storiesPanelDefaultSize : 1} size={showStoriesPanel ? storiesPanelDefaultSize : 1}
defaultSize={storiesPanelDefaultSize} defaultSize={storiesPanelDefaultSize}
@ -235,7 +237,7 @@ class Layout extends React.Component {
</div> </div>
<SplitPane <SplitPane
split={addonSplit} split={addonSplit}
allowResize={showAddonPanel} allowResize={isMobileDevice ? false : showAddonPanel}
primary="second" primary="second"
minSize={addonPanelInRight ? 200 : 100} minSize={addonPanelInRight ? 200 : 100}
maxSize={-200} maxSize={-200}
@ -285,6 +287,11 @@ Layout.propTypes = {
preview: PropTypes.func.isRequired, preview: PropTypes.func.isRequired,
addonPanel: PropTypes.func.isRequired, addonPanel: PropTypes.func.isRequired,
addonPanelInRight: PropTypes.bool.isRequired, addonPanelInRight: PropTypes.bool.isRequired,
isMobileDevice: PropTypes.bool,
};
Layout.defaultProps = {
isMobileDevice: false,
}; };
export default Layout; export default Layout;

View File

@ -1,25 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Storyshots ui/stories/Header simple 1`] = `
<div
style="background:#F7F7F7;margin-bottom:10px;display:flex"
>
<a
href="http://google.com"
rel="noopener noreferrer"
style="text-decoration:none;flex-grow:1;display:flex;align-items:center;justify-content:center;border:1px solid rgb(193, 193, 193);border-radius:2px"
target="_blank"
>
<h3
style="font-family:-apple-system, \\".SFNSText-Regular\\", \\"San Francisco\\", BlinkMacSystemFont, \\"Segoe UI\\", \\"Roboto\\", \\"Oxygen\\", \\"Ubuntu\\", \\"Cantarell\\", \\"Fira Sans\\", \\"Droid Sans\\", \\"Helvetica Neue\\", \\"Lucida Grande\\", \\"Arial\\", sans-serif;color:#828282;-webkit-font-smoothing:antialiased;text-transform:uppercase;letter-spacing:1.5px;font-size:12px;font-weight:bolder;text-align:center;cursor:pointer;padding:5px;margin:0;overflow:hidden"
>
name
</h3>
</a>
<button
style="text-transform:uppercase;font-size:12px;font-weight:bolder;color:rgb(130, 130, 130);border:1px solid rgb(193, 193, 193);text-align:center;border-radius:2px;cursor:pointer;display:inlineBlock;padding:0;margin:0 0 0 5px;background-color:inherit;outline:0;width:30px;flex-shrink:0"
>
</button>
</div>
`;

View File

@ -5,7 +5,7 @@ exports[`Storyshots ui/stories/StoriesPanel default 1`] = `
style="padding:10px 0 10px 10px" style="padding:10px 0 10px 10px"
> >
<div <div
style="background:#F7F7F7;margin-bottom:10px;display:flex" style="background:#F7F7F7;margin:0 0 10px;display:flex"
> >
<a <a
href="" href=""
@ -49,7 +49,7 @@ exports[`Storyshots ui/stories/StoriesPanel storiesHierarchies exists but is emp
style="padding:10px 0 10px 10px" style="padding:10px 0 10px 10px"
> >
<div <div
style="background:#F7F7F7;margin-bottom:10px;display:flex" style="background:#F7F7F7;margin:0 0 10px;display:flex"
> >
<a <a
href="" href=""
@ -88,12 +88,37 @@ exports[`Storyshots ui/stories/StoriesPanel storiesHierarchies exists but is emp
</div> </div>
`; `;
exports[`Storyshots ui/stories/StoriesPanel when open on mobile device 1`] = `
<div
style="padding:10px 0 10px 10px"
>
<div
style="font-family:-apple-system, \\".SFNSText-Regular\\", \\"San Francisco\\", BlinkMacSystemFont, \\"Segoe UI\\", \\"Roboto\\", \\"Oxygen\\", \\"Ubuntu\\", \\"Cantarell\\", \\"Fira Sans\\", \\"Droid Sans\\", \\"Helvetica Neue\\", \\"Lucida Grande\\", \\"Arial\\", sans-serif;color:#444;-webkit-font-smoothing:antialiased;border:1px solid #ECECEC;border-radius:2px;position:relative"
>
<div
style="background:#F7F7F7"
>
<input
name="filter-text"
placeholder="Filter"
style="font-size:12px;color:#828282;padding:5px;display:block;width:100%;box-sizing:border-box;outline:none;border:0;height:26px"
type="text"
value=""
/>
</div>
</div>
<div
style="height:calc(100vh - 105px);margin-top:10px;overflow:auto"
/>
</div>
`;
exports[`Storyshots ui/stories/StoriesPanel with storiesHierarchies prop 1`] = ` exports[`Storyshots ui/stories/StoriesPanel with storiesHierarchies prop 1`] = `
<div <div
style="padding:10px 0 10px 10px" style="padding:10px 0 10px 10px"
> >
<div <div
style="background:#F7F7F7;margin-bottom:10px;display:flex" style="background:#F7F7F7;margin:0 0 10px;display:flex"
> >
<a <a
href="" href=""

View File

@ -1,10 +0,0 @@
import React from 'react';
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import Header from './header';
const openShortcutsHelp = action('openShortcutsHelp');
storiesOf('ui/stories/Header', module).add('simple', () => (
<Header name="name" url="http://google.com" openShortcutsHelp={openShortcutsHelp} />
));

View File

@ -1,7 +1,7 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import pick from 'lodash.pick'; import pick from 'lodash.pick';
import Header from './header'; import Header from '../header';
import Stories from './stories_tree'; import Stories from './stories_tree';
import TextFilter from './text_filter'; import TextFilter from './text_filter';
@ -52,16 +52,19 @@ class StoriesPanel extends Component {
storyFilter, storyFilter,
url, url,
shortcutOptions, shortcutOptions,
isMobileDevice,
} = this.props; } = this.props;
return ( return (
<div style={mainStyle}> <div style={mainStyle}>
{!isMobileDevice && (
<Header <Header
name={name} name={name}
url={url} url={url}
openShortcutsHelp={shortcutOptions.enableShortcuts ? openShortcutsHelp : null} openShortcutsHelp={shortcutOptions.enableShortcuts ? openShortcutsHelp : null}
enableShortcutsHelp={shortcutOptions.enableShortcuts} enableShortcutsHelp={shortcutOptions.enableShortcuts}
/> />
)}
<TextFilter <TextFilter
text={storyFilter} text={storyFilter}
onClear={() => onStoryFilter('')} onClear={() => onStoryFilter('')}
@ -78,6 +81,7 @@ StoriesPanel.defaultProps = {
storyFilter: null, storyFilter: null,
onStoryFilter: () => {}, onStoryFilter: () => {},
openShortcutsHelp: null, openShortcutsHelp: null,
isMobileDevice: false,
name: '', name: '',
url: '', url: '',
shortcutOptions: { shortcutOptions: {
@ -102,6 +106,7 @@ StoriesPanel.propTypes = {
onStoryFilter: PropTypes.func, onStoryFilter: PropTypes.func,
openShortcutsHelp: PropTypes.func, openShortcutsHelp: PropTypes.func,
isMobileDevice: PropTypes.bool,
name: PropTypes.string, name: PropTypes.string,
url: PropTypes.string, url: PropTypes.string,
shortcutOptions: PropTypes.shape({ shortcutOptions: PropTypes.shape({

View File

@ -12,7 +12,7 @@ const decorator = withLifecyle({
setContext({ setContext({
clientStore: { clientStore: {
getAll() { getAll() {
return { shortcutOptions: {} }; return { shortcutOptions: {}, uiOptions: {}, actions: { ui: {} } };
}, },
subscribe() {}, subscribe() {},
}, },
@ -51,4 +51,12 @@ storiesOf('ui/stories/StoriesPanel', module)
openShortcutsHelp={openShortcutsHelp} openShortcutsHelp={openShortcutsHelp}
onStoryFilter={onStoryFilter} onStoryFilter={onStoryFilter}
/> />
))
.add('when open on mobile device', () => (
<StoriesPanel
storiesHierarchies={createHierarchies([])}
openShortcutsHelp={openShortcutsHelp}
onStoryFilter={onStoryFilter}
isMobileDevice
/>
)); ));

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
import StoriesPanel from './index'; import StoriesPanel from './index';
import Header from './header'; import Header from '../header';
import TextFilter from './text_filter'; import TextFilter from './text_filter';
import Stories from './stories_tree'; import Stories from './stories_tree';
import { createHierarchies } from '../../libs/hierarchy'; import { createHierarchies } from '../../libs/hierarchy';

View File

@ -20,8 +20,7 @@ describe('manager.ui.components.stories_panel.stories_tree', () => {
}, },
subscribe() {}, subscribe() {},
}, },
}) }));
);
afterEach(() => setContext(null)); afterEach(() => setContext(null));

View File

@ -0,0 +1,37 @@
import pick from 'lodash.pick';
import Header from '../components/header';
import genPoddaLoader from '../libs/gen_podda_loader';
import compose from '../../../compose';
export const mapper = (state, props, { actions }) => {
const currentOptions = pick(
state.shortcutOptions,
'showStoriesPanel',
'enableShortcuts',
'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,
enableShortcutsHelp: currentOptions.enableShortcuts,
openShortcutsHelp: actionMap.ui.toggleShortcutsHelp,
onBurgerButtonClick: handleBurgerButtonClick,
isMobileDevice,
addonPanelInRight,
};
};
export default compose(genPoddaLoader(mapper))(Header);

View File

@ -0,0 +1,45 @@
import { mapper } from './header';
describe('manager.ui.containers.header', () => {
describe('mapper', () => {
test('should give correct data', () => {
const uiOptions = {
name: 'foo',
url: 'bar',
};
const shortcutOptions = {
showStoriesPanel: 'aa',
enableShortcuts: true,
addonPanelInRight: true,
};
const toggleShortcutsHelp = () => 'toggleShortcutsHelp';
const props = {};
const env = {
actions: () => ({
ui: {
toggleShortcutsHelp,
},
}),
};
const state = {
uiOptions,
shortcutOptions,
};
const result = mapper(state, props, env);
expect(result.openShortcutsHelp).toBe(toggleShortcutsHelp);
expect(result.enableShortcutsHelp).toEqual(shortcutOptions.enableShortcuts);
expect(result.addonPanelInRight).toEqual(true);
const stateWhenMobileDevice = {
uiOptions,
shortcutOptions,
isMobileDevice: true,
};
const resultWhenMobileDevice = mapper(stateWhenMobileDevice, props, env);
expect(resultWhenMobileDevice.addonPanelInRight).toEqual(false);
});
});
});

View File

@ -3,7 +3,20 @@ import Layout from '../components/layout';
import genPoddaLoader from '../libs/gen_podda_loader'; import genPoddaLoader from '../libs/gen_podda_loader';
import compose from '../../../compose'; import compose from '../../../compose';
export const mapper = ({ shortcutOptions }) => export const mapper = state => {
pick(shortcutOptions, 'showStoriesPanel', 'showAddonPanel', 'goFullScreen', 'addonPanelInRight'); const { shortcutOptions, isMobileDevice } = state;
const currentOptions = pick(
shortcutOptions,
'showStoriesPanel',
'showAddonPanel',
'goFullScreen',
'addonPanelInRight'
);
return {
...currentOptions,
isMobileDevice,
};
};
export default compose(genPoddaLoader(mapper))(Layout); export default compose(genPoddaLoader(mapper))(Layout);

View File

@ -13,14 +13,23 @@ import {
export const mapper = (state, props, { actions }) => { export const mapper = (state, props, { actions }) => {
const actionMap = actions(); const actionMap = actions();
const { stories, selectedKind, selectedStory, uiOptions, storyFilter, shortcutOptions } = state;
const { const {
name, stories,
url, selectedKind,
selectedStory,
uiOptions,
storyFilter,
shortcutOptions,
isMobileDevice,
} = state;
const {
sortStoriesByKind, sortStoriesByKind,
hierarchySeparator, hierarchySeparator,
hierarchyRootSeparator, hierarchyRootSeparator,
sidebarAnimations, sidebarAnimations,
name,
url,
} = uiOptions; } = uiOptions;
const preparedStories = prepareStoriesForHierarchy( const preparedStories = prepareStoriesForHierarchy(
@ -52,9 +61,9 @@ export const mapper = (state, props, { actions }) => {
storyFilter, storyFilter,
onStoryFilter: actionMap.ui.setStoryFilter, onStoryFilter: actionMap.ui.setStoryFilter,
openShortcutsHelp: actionMap.ui.toggleShortcutsHelp, openShortcutsHelp: actionMap.ui.toggleShortcutsHelp,
sidebarAnimations, sidebarAnimations,
isMobileDevice,
name, name,
url, url,
}; };

View File

@ -12,7 +12,6 @@ describe('manager.ui.containers.stories_panel', () => {
url: 'bar', url: 'bar',
}; };
const selectStory = () => 'selectStory'; const selectStory = () => 'selectStory';
const toggleShortcutsHelp = () => 'toggleShortcutsHelp';
const setStoryFilter = () => 'setStoryFilter'; const setStoryFilter = () => 'setStoryFilter';
const props = {}; const props = {};
const env = { const env = {
@ -21,7 +20,6 @@ describe('manager.ui.containers.stories_panel', () => {
selectStory, selectStory,
}, },
ui: { ui: {
toggleShortcutsHelp,
setStoryFilter, setStoryFilter,
}, },
}), }),
@ -57,7 +55,6 @@ describe('manager.ui.containers.stories_panel', () => {
expect(result.storyFilter).toBe(null); expect(result.storyFilter).toBe(null);
expect(result.onSelectStory).toBe(selectStory); expect(result.onSelectStory).toBe(selectStory);
expect(result.onStoryFilter).toBe(setStoryFilter); expect(result.onStoryFilter).toBe(setStoryFilter);
expect(result.openShortcutsHelp).toBe(toggleShortcutsHelp);
}); });
test('should filter stories according to the given filter', () => { test('should filter stories according to the given filter', () => {

View File

@ -0,0 +1,7 @@
export default userAgent => {
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(userAgent)) {
return true;
}
return false;
};

View File

@ -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);
});
});

View File

@ -6,11 +6,12 @@ import StoriesPanel from './containers/stories_panel';
import AddonPanel from './containers/addon_panel'; import AddonPanel from './containers/addon_panel';
import ShortcutsHelp from './containers/shortcuts_help'; import ShortcutsHelp from './containers/shortcuts_help';
import SearchBox from './containers/search_box'; import SearchBox from './containers/search_box';
import Header from './containers/header';
export default function(injectDeps, { clientStore, provider, domNode }) { export default function(injectDeps, { clientStore, provider, domNode }) {
const state = clientStore.getAll();
// generate preview // generate preview
const Preview = () => { const Preview = () => {
const state = clientStore.getAll();
const preview = provider.renderPreview(state.selectedKind, state.selectedStory); const preview = provider.renderPreview(state.selectedKind, state.selectedStory);
return preview; return preview;
}; };
@ -22,6 +23,7 @@ export default function(injectDeps, { clientStore, provider, domNode }) {
const root = ( const root = (
<Container> <Container>
{state.isMobileDevice && <Header />}
<Layout <Layout
storiesPanel={() => <StoriesPanel />} storiesPanel={() => <StoriesPanel />}
preview={() => <Preview />} preview={() => <Preview />}