Skip to content

Commit

Permalink
Merge pull request #24227 from storybookjs/charles-mobile-1
Browse files Browse the repository at this point in the history
UI: Add the new structure for the mobile layout
  • Loading branch information
cdedreuille authored Sep 19, 2023
2 parents a01591e + ee81c81 commit 385a406
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 53 deletions.
47 changes: 33 additions & 14 deletions code/ui/manager/src/components/hooks/useMedia.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,38 @@
import { useState, useEffect } from 'react';
import { useEffect, useState } from 'react';

const useMediaQuery = (query: string) => {
const [matches, setMatches] = useState(false);
// The hook is taken from this library:
// https://usehooks-ts.com/react-hook/use-media-query
// The good thing about it is that is uses window.matchMedia

useEffect(() => {
const media = window.matchMedia(query);
if (media.matches !== matches) {
setMatches(media.matches);
export function useMediaQuery(query: string): boolean {
const getMatches = (queryMatch: string): boolean => {
// Prevents SSR issues
if (typeof window !== 'undefined') {
return window.matchMedia(queryMatch).matches;
}
const listener = () => setMatches(media.matches);
window.addEventListener('resize', listener);
return () => window.removeEventListener('resize', listener);
}, [matches, query]);
return false;
};

return matches;
};
const [matches, setMatches] = useState<boolean>(getMatches(query));

function handleChange() {
setMatches(getMatches(query));
}

useEffect(() => {
const matchMedia = window.matchMedia(query);

export default useMediaQuery;
// Triggered at the first client-side load and if query changes
handleChange();

// Listen matchMedia
matchMedia.addEventListener('change', handleChange);

return () => {
matchMedia.removeEventListener('change', handleChange);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [query]);

return matches;
}
7 changes: 0 additions & 7 deletions code/ui/manager/src/components/layout/Layout.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,6 @@ const meta = {
theme: 'light',
layout: 'fullscreen',
},
decorators: [
(Story) => (
<div style={{ height: '100vh', width: '100vw' }}>
<Story />
</div>
),
],
render: (args) => {
const [managerLayoutState, setManagerLayoutState] = useState(args.managerLayoutState);

Expand Down
83 changes: 52 additions & 31 deletions code/ui/manager/src/components/layout/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import React, { useEffect, useLayoutEffect, useState } from 'react';
import { styled } from '@storybook/theming';
import type { API_Layout, API_ViewMode } from '@storybook/types';
import { useDragging } from './useDragging';
import { useMediaQuery } from '../hooks/useMedia';
import { MobileNavigation } from '../mobile-navigation/MobileNavigation';
import { BREAKPOINT_MIN_600 } from '../../constants';

interface InternalLayoutState {
isDragging: boolean;
Expand Down Expand Up @@ -110,6 +113,9 @@ const useLayoutSyncingState = (
};

export const Layout = ({ managerLayoutState, setManagerLayoutState, ...slots }: Props) => {
const isDesktop = useMediaQuery(BREAKPOINT_MIN_600);
const isMobile = !isDesktop;

const {
navSize,
rightPanelWidth,
Expand All @@ -133,47 +139,57 @@ export const Layout = ({ managerLayoutState, setManagerLayoutState, ...slots }:
>
{showPages && <PagesContainer>{slots.slotPages}</PagesContainer>}
<ContentContainer>{slots.slotMain}</ContentContainer>
<SidebarContainer>
<Drag ref={sidebarResizerRef} />
{slots.slotSidebar}
</SidebarContainer>
{showPanel && (
<PanelContainer position={panelPosition}>
<Drag
orientation={panelPosition === 'bottom' ? 'horizontal' : 'vertical'}
position={panelPosition === 'bottom' ? 'left' : 'right'}
ref={panelResizerRef}
/>
{slots.slotPanel}
</PanelContainer>
{isDesktop && (
<>
<SidebarContainer>
<Drag ref={sidebarResizerRef} />
{slots.slotSidebar}
</SidebarContainer>
{showPanel && (
<PanelContainer position={panelPosition}>
<Drag
orientation={panelPosition === 'bottom' ? 'horizontal' : 'vertical'}
position={panelPosition === 'bottom' ? 'left' : 'right'}
ref={panelResizerRef}
/>
{slots.slotPanel}
</PanelContainer>
)}
</>
)}
{isMobile && <MobileNavigation />}
</LayoutContainer>
);
};

const LayoutContainer = styled.div<LayoutState>(
({ navSize, rightPanelWidth, bottomPanelHeight, viewMode, panelPosition, isDragging }) => {
({ navSize, rightPanelWidth, bottomPanelHeight, viewMode, panelPosition }) => {
return {
width: '100%',
height: '100vh',
display: 'grid',
height: '100svh', // We are using svh to use the minimum space on mobile
overflow: 'hidden',
gap: 0,
gridTemplateColumns: `minmax(0, ${navSize}px) minmax(${MINIMUM_CONTENT_WIDTH_PX}px, 1fr) minmax(0, ${rightPanelWidth}px)`,
gridTemplateRows: `1fr ${bottomPanelHeight}px`,
gridTemplateAreas: (() => {
if (viewMode === 'docs') {
// remove panel in docs viewMode
return `"sidebar content content"
display: 'flex',
flexDirection: 'column',

[`@media ${BREAKPOINT_MIN_600}`]: {
display: 'grid',
gap: 0,
gridTemplateColumns: `minmax(0, ${navSize}px) minmax(${MINIMUM_CONTENT_WIDTH_PX}px, 1fr) minmax(0, ${rightPanelWidth}px)`,
gridTemplateRows: `1fr ${bottomPanelHeight}px`,
gridTemplateAreas: (() => {
if (viewMode === 'docs') {
// remove panel in docs viewMode
return `"sidebar content content"
"sidebar content content"`;
}
if (panelPosition === 'right') {
return `"sidebar content panel"
}
if (panelPosition === 'right') {
return `"sidebar content panel"
"sidebar content panel"`;
}
return `"sidebar content content"
}
return `"sidebar content content"
"sidebar panel panel"`;
})(),
})(),
},
};
}
);
Expand All @@ -186,10 +202,15 @@ const SidebarContainer = styled.div(({ theme }) => ({
}));

const ContentContainer = styled.div(({ theme }) => ({
display: 'grid',
flex: 1,
position: 'relative',
backgroundColor: theme.background.content,
gridArea: 'content',
display: 'grid', // This is needed to make the content container fill the available space

[`@media ${BREAKPOINT_MIN_600}`]: {
flex: 'auto',
gridArea: 'content',
},
}));

const PagesContainer = styled.div(({ theme }) => ({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { FC } from 'react';
import React from 'react';
import { styled } from '@storybook/theming';

const Container = styled.div(({ theme }) => ({
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
bottom: 0,
left: 0,
width: '100%',
height: 40,
zIndex: 10,
background: theme.background.content,
padding: '0 6px',
borderTop: `1px solid ${theme.appBorderColor}`,
}));

export const MobileNavigation: FC = () => {
return <Container />;
};
2 changes: 1 addition & 1 deletion code/ui/manager/src/components/panel/Panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Tabs, Icons, IconButton } from '@storybook/components';
import type { State } from '@storybook/manager-api';
import { shortcutToHumanString } from '@storybook/manager-api';
import type { Addon_BaseType } from '@storybook/types';
import useMediaQuery from '../hooks/useMedia';
import { useMediaQuery } from '../hooks/useMedia';

export interface SafeTabProps {
title: Addon_BaseType['title'];
Expand Down
1 change: 1 addition & 0 deletions code/ui/manager/src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const BREAKPOINT_MIN_600 = '(min-width: 600px)';

0 comments on commit 385a406

Please sign in to comment.