From 4ca1c09e35adaf7f1dc676fe4b6dfb860121fcd2 Mon Sep 17 00:00:00 2001 From: Cyprien P Date: Tue, 12 Jan 2021 16:11:28 +0100 Subject: [PATCH] (core) Adds scafoldings for the narrow screen layout Summary: This commit adds a bottom bar with 2 openers button for each side panels. It takes care of showing/hiding other openers when appropriate. It also implements the new style for narrow screen with the side panels overlapping the main pane. Remaining tasks: - adjust content of the breadcrumbs for narrow screen. - content of the bottom bar Test Plan: Adds test to projects/PagePanels Reviewers: paulfitz Reviewed By: paulfitz Subscribers: dsagal Differential Revision: https://phab.getgrist.com/D2701 --- app/client/ui/AppUI.ts | 1 + app/client/ui/PagePanels.ts | 152 ++++++++++++++++++++++++++++++++--- app/client/ui2018/cssVars.ts | 11 +++ 3 files changed, 153 insertions(+), 11 deletions(-) diff --git a/app/client/ui/AppUI.ts b/app/client/ui/AppUI.ts index 50d62755..ad0c7c29 100644 --- a/app/client/ui/AppUI.ts +++ b/app/client/ui/AppUI.ts @@ -147,5 +147,6 @@ function pagePanelsDoc(owner: IDisposableOwner, appModel: AppModel, appObj: App) contentMain: dom.maybe(pageModel.gristDoc, (gristDoc) => gristDoc.buildDom()), onResize, testId, + optimizeNarrowScreen: true, }); } diff --git a/app/client/ui/PagePanels.ts b/app/client/ui/PagePanels.ts index 851325bc..6c499072 100644 --- a/app/client/ui/PagePanels.ts +++ b/app/client/ui/PagePanels.ts @@ -3,7 +3,7 @@ */ import {resizeFlexVHandle} from 'app/client/ui/resizeHandle'; import {transition} from 'app/client/ui/transitions'; -import {colors} from 'app/client/ui2018/cssVars'; +import {colors, cssHideForNarrowScreen, maxNarrowScreenWidth} from 'app/client/ui2018/cssVars'; import {icon} from 'app/client/ui2018/icons'; import {dom, DomArg, noTestId, Observable, styled, TestId} from "grainjs"; @@ -26,6 +26,7 @@ export interface PageContents { onResize?: () => void; // Callback for when either pane is opened, closed, or resized. testId?: TestId; + optimizeNarrowScreen?: boolean; // If true, show an optimized layout when screen is narrow. } export function pagePanels(page: PageContents) { @@ -33,8 +34,11 @@ export function pagePanels(page: PageContents) { const left = page.leftPanel; const right = page.rightPanel; const onResize = page.onResize || (() => null); + const optimizeNarrowScreen = Boolean(page.optimizeNarrowScreen); - return cssPageContainer( + return [cssPageContainer( + cssPageContainer.cls('-optimizeNarrowScreen', optimizeNarrowScreen), + optimizeNarrowScreen ? dom.style('min-width', '240px') : null, cssLeftPane( testId('left-panel'), cssTopHeader(left.header), @@ -56,10 +60,13 @@ export function pagePanels(page: PageContents) { cssResizeFlexVHandle( {target: 'left', onSave: (val) => { left.panelWidth.set(val); onResize(); }}, testId('left-resizer'), - dom.show(left.panelOpen)), + dom.show(left.panelOpen), + cssHideForNarrowScreen.cls('', optimizeNarrowScreen)), // Show plain border when the resize handle is hidden. - cssResizeDisabledBorder(dom.hide(left.panelOpen)), + cssResizeDisabledBorder( + dom.hide(left.panelOpen), + cssHideForNarrowScreen.cls('', optimizeNarrowScreen)), cssMainPane( cssTopHeader( @@ -67,7 +74,8 @@ export function pagePanels(page: PageContents) { (left.hideOpener ? null : cssPanelOpener('PanelRight', cssPanelOpener.cls('-open', left.panelOpen), testId('left-opener'), - dom.on('click', () => toggleObs(left.panelOpen))) + dom.on('click', () => toggleObs(left.panelOpen)), + cssHideForNarrowScreen.cls('', optimizeNarrowScreen)) ), page.headerMain, @@ -75,17 +83,20 @@ export function pagePanels(page: PageContents) { (!right || right.hideOpener ? null : cssPanelOpener('PanelLeft', cssPanelOpener.cls('-open', right.panelOpen), testId('right-opener'), - dom.on('click', () => toggleObs(right.panelOpen))) + dom.on('click', () => toggleObs(right.panelOpen)), + cssHideForNarrowScreen.cls('', optimizeNarrowScreen)) ), ), page.contentMain, + testId('main-pane'), ), (right ? [ // Resizer for the right pane. cssResizeFlexVHandle( {target: 'right', onSave: (val) => { right.panelWidth.set(val); onResize(); }}, testId('right-resizer'), - dom.show(right.panelOpen)), + dom.show(right.panelOpen), + cssHideForNarrowScreen.cls('', optimizeNarrowScreen)), cssRightPane( testId('right-panel'), @@ -103,7 +114,44 @@ export function pagePanels(page: PageContents) { }), )] : null ), - ); + cssContentOverlay( + dom.show((use) => use(left.panelOpen) || Boolean(right && use(right.panelOpen))), + testId('overlay')) + ), ( + !optimizeNarrowScreen ? null : + cssBottomFooter( + testId('bottom-footer'), + (left.hideOpener ? null : + cssPanelOpenerNarrowScreenBtn( + cssPanelOpenerNarrowScreen( + 'FieldTextbox', + dom.on('click', () => { + if (right) { + right.panelOpen.set(false); + } + toggleObs(left.panelOpen); + }), + testId('left-opener-ns') + ), + cssPanelOpenerNarrowScreenBtn.cls('-open', left.panelOpen) + ) + ), + dom('div', cssFlexSpacer.cls('')), + (!right || right.hideOpener ? null : + cssPanelOpenerNarrowScreenBtn( + cssPanelOpenerNarrowScreen( + 'Settings', + dom.on('click', () => { + left.panelOpen.set(false); + toggleObs(right.panelOpen); + }), + testId('right-opener-ns') + ), + cssPanelOpenerNarrowScreenBtn.cls('-open', right.panelOpen), + ) + ), + ) + )]; } function toggleObs(boolObs: Observable) { @@ -117,16 +165,26 @@ const cssVBox = styled('div', ` const cssHBox = styled('div', ` display: flex; `); +const cssFlexSpacer = styled('div', ` + flex-grow: 1; +`); const cssPageContainer = styled(cssHBox, ` position: absolute; isolation: isolate; /* Create a new stacking context */ z-index: 0; /* As of March 2019, isolation does not have Edge support, so force one with z-index */ top: 0; left: 0; - width: 100%; - height: 100%; - min-width: 600px; + right: 0; + bottom: 0; + min-width: 600px background-color: ${colors.lightGrey}; + + @media (max-width: ${maxNarrowScreenWidth}px) { + &-optimizeNarrowScreen { + bottom: 48px; + } + } + `); export const cssLeftPane = styled(cssVBox, ` position: relative; @@ -135,6 +193,18 @@ export const cssLeftPane = styled(cssVBox, ` margin-right: 0px; overflow: hidden; transition: margin-right 0.4s; + @media (max-width: ${maxNarrowScreenWidth}px) { + .${cssPageContainer.className}-optimizeNarrowScreen & { + width: 0px; + position: absolute; + z-index: 10; + top: 0; + bottom: 0; + left: 0; + box-shadow: 10px 0 5px rgba(0, 0, 0, 0.2); + border-bottom: 1px solid ${colors.mediumGrey}; + } + } &-open { width: 240px; min-width: 160px; @@ -164,6 +234,17 @@ const cssRightPane = styled(cssVBox, ` overflow: hidden; transition: margin-left 0.4s; z-index: 0; + @media (max-width: ${maxNarrowScreenWidth}px) { + .${cssPageContainer.className}-optimizeNarrowScreen & { + position: absolute; + z-index: 10; + top: 0; + bottom: 0; + right: 0; + box-shadow: -10px 0 5px rgba(0, 0, 0, 0.2); + border-bottom: 1px solid ${colors.mediumGrey}; + } + } &-open { width: 240px; min-width: 240px; @@ -196,6 +277,23 @@ const cssTopHeader = styled('div', ` display: none; } `); +const cssBottomFooter = styled ('div', ` + height: 48px; + background-color: white; + z-index: 20; + display: flex; + flex-direction: row; + padding: 8px 16px; + position: absolute; + bottom: 0; + left: 0; + right: 0; + @media (min-width: ${maxNarrowScreenWidth}px) { + & { + display: none; + } + } +`); const cssResizeFlexVHandle = styled(resizeFlexVHandle, ` --resize-handle-color: ${colors.mediumGrey}; --resize-handle-highlight: ${colors.lightGreen}; @@ -224,3 +322,35 @@ const cssPanelOpener = styled(icon, ` &:hover { background-color: ${colors.darkGreen}; } &-open { transform: rotateY(180deg); } `); +const cssPanelOpenerNarrowScreenBtn = styled('div', ` + width: 32px; + height: 32px; + --icon-color: ${colors.slate}; + cursor: pointer; + border-radius: 4px; + &-open { + background-color: ${colors.lightGreen}; + --icon-color: white; + } +`); +const cssPanelOpenerNarrowScreen = styled(icon, ` + width: 24px; + height: 24px; + margin: 4px; +`); +const cssContentOverlay = styled('div', ` + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + background-color: grey; + opacity: 0.5; + display: none; + z-index: 9; + @media (max-width: ${maxNarrowScreenWidth}px) { + .${cssPageContainer.className}-optimizeNarrowScreen & { + display: unset; + } + } +`); diff --git a/app/client/ui2018/cssVars.ts b/app/client/ui2018/cssVars.ts index f3303d68..de825a0b 100644 --- a/app/client/ui2018/cssVars.ts +++ b/app/client/ui2018/cssVars.ts @@ -154,6 +154,17 @@ export const cssRootVars = cssBodyVars.className; // class ".test-{name}". Ideally, we'd use noTestId() instead in production. export const testId: TestId = makeTestId('test-'); +// Max width for narrow screen layout (in px). Note: 768px is bootstrap's definition of small screen +export const maxNarrowScreenWidth = 768; + +export const cssHideForNarrowScreen = styled('div', ` + @media (max-width: ${maxNarrowScreenWidth}px) { + & { + display: none !important; + } + } +`); + /** * Attaches the global css properties to the document's root to them available in the page. */