mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
(core) Make side panels responsive and start closed on small screens.
Summary: - Add isNarrowScreenObs() observable. - Remove optimizeNarrowScreen flag (now assumed always true). - Added viewport support and mobile tweaks to Error/Billing/Welcome pages. - Fix responsiveness of panel transitions, and of side panel state. - Close left panel on navigation to another page or workspace. - Start panels collapsed in both doc and docmenu cases. Test Plan: Tested manually, and fixed tests to accept the new behavior. Reviewers: paulfitz Reviewed By: paulfitz Differential Revision: https://phab.getgrist.com/D2726
This commit is contained in:
parent
956e07e877
commit
de1719ee08
@ -15,7 +15,7 @@ import {pagePanels} from 'app/client/ui/PagePanels';
|
|||||||
import {RightPanel} from 'app/client/ui/RightPanel';
|
import {RightPanel} from 'app/client/ui/RightPanel';
|
||||||
import {createTopBarDoc, createTopBarHome} from 'app/client/ui/TopBar';
|
import {createTopBarDoc, createTopBarHome} from 'app/client/ui/TopBar';
|
||||||
import {WelcomePage} from 'app/client/ui/WelcomePage';
|
import {WelcomePage} from 'app/client/ui/WelcomePage';
|
||||||
import {isNarrowScreen, testId} from 'app/client/ui2018/cssVars';
|
import {testId} from 'app/client/ui2018/cssVars';
|
||||||
import {Computed, dom, IDisposable, IDisposableOwner, Observable, replaceContent, subscribe} from 'grainjs';
|
import {Computed, dom, IDisposable, IDisposableOwner, Observable, replaceContent, subscribe} from 'grainjs';
|
||||||
|
|
||||||
// When integrating into the old app, we might in theory switch between new-style and old-style
|
// When integrating into the old app, we might in theory switch between new-style and old-style
|
||||||
@ -100,28 +100,18 @@ function pagePanelsHome(owner: IDisposableOwner, appModel: AppModel) {
|
|||||||
},
|
},
|
||||||
headerMain: createTopBarHome(appModel),
|
headerMain: createTopBarHome(appModel),
|
||||||
contentMain: createDocMenu(pageModel),
|
contentMain: createDocMenu(pageModel),
|
||||||
optimizeNarrowScreen: true,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create session observable. But if device is a narrow screen create a regular observable.
|
|
||||||
function createPanelObs<T>(owner: IDisposableOwner, key: string, _default: T, isValid: (val: any) => val is T) {
|
|
||||||
if (isNarrowScreen()) {
|
|
||||||
return Observable.create(owner, _default);
|
|
||||||
}
|
|
||||||
return createSessionObs<T>(owner, key, _default, isValid);
|
|
||||||
}
|
|
||||||
|
|
||||||
function pagePanelsDoc(owner: IDisposableOwner, appModel: AppModel, appObj: App) {
|
function pagePanelsDoc(owner: IDisposableOwner, appModel: AppModel, appObj: App) {
|
||||||
const pageModel = DocPageModelImpl.create(owner, appObj, appModel);
|
const pageModel = DocPageModelImpl.create(owner, appObj, appModel);
|
||||||
// To simplify manual inspection in the common case, keep the most recently created
|
// To simplify manual inspection in the common case, keep the most recently created
|
||||||
// DocPageModel available as a global variable.
|
// DocPageModel available as a global variable.
|
||||||
(window as any).gristDocPageModel = pageModel;
|
(window as any).gristDocPageModel = pageModel;
|
||||||
const leftPanelOpen = createPanelObs<boolean>(owner, "leftPanelOpen", isNarrowScreen() ? false : true,
|
const leftPanelOpen = createSessionObs<boolean>(owner, "leftPanelOpen", true, isBoolean);
|
||||||
isBoolean);
|
const rightPanelOpen = createSessionObs<boolean>(owner, "rightPanelOpen", false, isBoolean);
|
||||||
const rightPanelOpen = createPanelObs<boolean>(owner, "rightPanelOpen", false, isBoolean);
|
const leftPanelWidth = createSessionObs<number>(owner, "leftPanelWidth", 240, isNumber);
|
||||||
const leftPanelWidth = createPanelObs<number>(owner, "leftPanelWidth", 240, isNumber);
|
const rightPanelWidth = createSessionObs<number>(owner, "rightPanelWidth", 240, isNumber);
|
||||||
const rightPanelWidth = createPanelObs<number>(owner, "rightPanelWidth", 240, isNumber);
|
|
||||||
|
|
||||||
// The RightPanel component gets created only when an instance of GristDoc is set in pageModel.
|
// The RightPanel component gets created only when an instance of GristDoc is set in pageModel.
|
||||||
// use.owner is a feature of grainjs to make the new RightPanel owned by the computed itself:
|
// use.owner is a feature of grainjs to make the new RightPanel owned by the computed itself:
|
||||||
@ -146,7 +136,7 @@ function pagePanelsDoc(owner: IDisposableOwner, appModel: AppModel, appObj: App)
|
|||||||
panelWidth: leftPanelWidth,
|
panelWidth: leftPanelWidth,
|
||||||
panelOpen: leftPanelOpen,
|
panelOpen: leftPanelOpen,
|
||||||
header: appHeader(appModel.currentOrgName || pageModel.currentOrgName, appModel.topAppModel.productFlavor),
|
header: appHeader(appModel.currentOrgName || pageModel.currentOrgName, appModel.topAppModel.productFlavor),
|
||||||
content: pageModel.createLeftPane(isNarrowScreen() ? Observable.create(null, true) : leftPanelOpen),
|
content: pageModel.createLeftPane(leftPanelOpen),
|
||||||
},
|
},
|
||||||
rightPanel: {
|
rightPanel: {
|
||||||
panelWidth: rightPanelWidth,
|
panelWidth: rightPanelWidth,
|
||||||
@ -158,7 +148,6 @@ function pagePanelsDoc(owner: IDisposableOwner, appModel: AppModel, appObj: App)
|
|||||||
contentMain: dom.maybe(pageModel.gristDoc, (gristDoc) => gristDoc.buildDom()),
|
contentMain: dom.maybe(pageModel.gristDoc, (gristDoc) => gristDoc.buildDom()),
|
||||||
onResize,
|
onResize,
|
||||||
testId,
|
testId,
|
||||||
optimizeNarrowScreen: true,
|
|
||||||
contentBottom: dom.create(createBottomBarDoc, pageModel, leftPanelOpen, rightPanelOpen)
|
contentBottom: dom.create(createBottomBarDoc, pageModel, leftPanelOpen, rightPanelOpen)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import {bigBasicButton, bigPrimaryButtonLink} from 'app/client/ui2018/buttons';
|
import {bigBasicButton, bigPrimaryButtonLink} from 'app/client/ui2018/buttons';
|
||||||
import {colors, vars} from 'app/client/ui2018/cssVars';
|
import {colors, mediaSmall, vars} from 'app/client/ui2018/cssVars';
|
||||||
import {icon} from 'app/client/ui2018/icons';
|
import {icon} from 'app/client/ui2018/icons';
|
||||||
import {input, styled} from 'grainjs';
|
import {input, styled} from 'grainjs';
|
||||||
|
|
||||||
@ -98,6 +98,12 @@ export const billingPage = styled('div', `
|
|||||||
display: flex;
|
display: flex;
|
||||||
max-width: 1000px;
|
max-width: 1000px;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
|
|
||||||
|
@media ${mediaSmall} {
|
||||||
|
& {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
export const billingHeader = styled('div', `
|
export const billingHeader = styled('div', `
|
||||||
@ -188,6 +194,11 @@ export const focusText = styled('span', `
|
|||||||
export const cardBlock = styled('div', `
|
export const cardBlock = styled('div', `
|
||||||
flex: 1 1 60%;
|
flex: 1 1 60%;
|
||||||
margin: 60px;
|
margin: 60px;
|
||||||
|
@media ${mediaSmall} {
|
||||||
|
& {
|
||||||
|
margin: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
export const summaryRow = styled('div', `
|
export const summaryRow = styled('div', `
|
||||||
@ -201,7 +212,11 @@ export const summaryHeader = styled(summaryRow, `
|
|||||||
export const summaryBlock = styled('div', `
|
export const summaryBlock = styled('div', `
|
||||||
flex: 1 1 40%;
|
flex: 1 1 40%;
|
||||||
margin: 60px;
|
margin: 60px;
|
||||||
float: left;
|
@media ${mediaSmall} {
|
||||||
|
& {
|
||||||
|
margin: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
export const flexSpace = styled('div', `
|
export const flexSpace = styled('div', `
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
/**
|
/**
|
||||||
* Note that it assumes the presence of cssVars.cssRootVars on <body>.
|
* Note that it assumes the presence of cssVars.cssRootVars on <body>.
|
||||||
*/
|
*/
|
||||||
|
import {urlState} from "app/client/models/gristUrlState";
|
||||||
import {resizeFlexVHandle} from 'app/client/ui/resizeHandle';
|
import {resizeFlexVHandle} from 'app/client/ui/resizeHandle';
|
||||||
import {transition} from 'app/client/ui/transitions';
|
import {transition} from 'app/client/ui/transitions';
|
||||||
import {colors, cssHideForNarrowScreen, isNarrowScreen, mediaNotSmall, mediaSmall} from 'app/client/ui2018/cssVars';
|
import {colors, cssHideForNarrowScreen, mediaNotSmall, mediaSmall} from 'app/client/ui2018/cssVars';
|
||||||
|
import {isNarrowScreenObs} from 'app/client/ui2018/cssVars';
|
||||||
import {icon} from 'app/client/ui2018/icons';
|
import {icon} from 'app/client/ui2018/icons';
|
||||||
import {dom, DomArg, noTestId, Observable, styled, TestId} from "grainjs";
|
import {dom, DomArg, noTestId, Observable, styled, subscribe, TestId} from "grainjs";
|
||||||
|
|
||||||
export interface PageSidePanel {
|
export interface PageSidePanel {
|
||||||
// Note that widths need to start out with a correct default in JS (having them in CSS is not
|
// Note that widths need to start out with a correct default in JS (having them in CSS is not
|
||||||
@ -26,7 +28,6 @@ export interface PageContents {
|
|||||||
|
|
||||||
onResize?: () => void; // Callback for when either pane is opened, closed, or resized.
|
onResize?: () => void; // Callback for when either pane is opened, closed, or resized.
|
||||||
testId?: TestId;
|
testId?: TestId;
|
||||||
optimizeNarrowScreen?: boolean; // If true, show an optimized layout when screen is narrow.
|
|
||||||
contentBottom?: DomArg;
|
contentBottom?: DomArg;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,10 +36,32 @@ export function pagePanels(page: PageContents) {
|
|||||||
const left = page.leftPanel;
|
const left = page.leftPanel;
|
||||||
const right = page.rightPanel;
|
const right = page.rightPanel;
|
||||||
const onResize = page.onResize || (() => null);
|
const onResize = page.onResize || (() => null);
|
||||||
const optimizeNarrowScreen = Boolean(page.optimizeNarrowScreen);
|
|
||||||
|
|
||||||
return [cssPageContainer(
|
let lastLeftOpen = left.panelOpen.get();
|
||||||
cssPageContainer.cls('-optimizeNarrowScreen', optimizeNarrowScreen),
|
let lastRightOpen = right?.panelOpen.get() || false;
|
||||||
|
|
||||||
|
// When switching to mobile mode, close panels; when switching to desktop, restore the
|
||||||
|
// last desktop state.
|
||||||
|
const sub1 = subscribe(isNarrowScreenObs(), (use, narrow) => {
|
||||||
|
if (narrow) {
|
||||||
|
lastLeftOpen = left.panelOpen.get();
|
||||||
|
lastRightOpen = right?.panelOpen.get() || false;
|
||||||
|
}
|
||||||
|
left.panelOpen.set(narrow ? false : lastLeftOpen);
|
||||||
|
right?.panelOpen.set(narrow ? false : lastRightOpen);
|
||||||
|
});
|
||||||
|
|
||||||
|
// When url changes, we must have navigated; close the left panel since if it were open, it was
|
||||||
|
// the likely cause of the navigation (e.g. switch to another page or workspace).
|
||||||
|
const sub2 = subscribe(isNarrowScreenObs(), urlState().state, (use, narrow, state) => {
|
||||||
|
if (narrow) {
|
||||||
|
left.panelOpen.set(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return cssPageContainer(
|
||||||
|
dom.autoDispose(sub1),
|
||||||
|
dom.autoDispose(sub2),
|
||||||
cssLeftPane(
|
cssLeftPane(
|
||||||
testId('left-panel'),
|
testId('left-panel'),
|
||||||
cssTopHeader(left.header),
|
cssTopHeader(left.header),
|
||||||
@ -48,7 +71,7 @@ export function pagePanels(page: PageContents) {
|
|||||||
|
|
||||||
// Opening/closing the left pane, with transitions.
|
// Opening/closing the left pane, with transitions.
|
||||||
cssLeftPane.cls('-open', left.panelOpen),
|
cssLeftPane.cls('-open', left.panelOpen),
|
||||||
optimizeNarrowScreen && isNarrowScreen() ? null : transition(left.panelOpen, {
|
transition(use => (use(isNarrowScreenObs()) ? false : use(left.panelOpen)), {
|
||||||
prepare(elem, open) { elem.style.marginRight = (open ? -1 : 1) * (left.panelWidth.get() - 48) + 'px'; },
|
prepare(elem, open) { elem.style.marginRight = (open ? -1 : 1) * (left.panelWidth.get() - 48) + 'px'; },
|
||||||
run(elem, open) { elem.style.marginRight = ''; },
|
run(elem, open) { elem.style.marginRight = ''; },
|
||||||
finish: onResize,
|
finish: onResize,
|
||||||
@ -61,12 +84,12 @@ export function pagePanels(page: PageContents) {
|
|||||||
{target: 'left', onSave: (val) => { left.panelWidth.set(val); onResize(); }},
|
{target: 'left', onSave: (val) => { left.panelWidth.set(val); onResize(); }},
|
||||||
testId('left-resizer'),
|
testId('left-resizer'),
|
||||||
dom.show(left.panelOpen),
|
dom.show(left.panelOpen),
|
||||||
cssHideForNarrowScreen.cls('', optimizeNarrowScreen)),
|
cssHideForNarrowScreen.cls('')),
|
||||||
|
|
||||||
// Show plain border when the resize handle is hidden.
|
// Show plain border when the resize handle is hidden.
|
||||||
cssResizeDisabledBorder(
|
cssResizeDisabledBorder(
|
||||||
dom.hide(left.panelOpen),
|
dom.hide(left.panelOpen),
|
||||||
cssHideForNarrowScreen.cls('', optimizeNarrowScreen)),
|
cssHideForNarrowScreen.cls('')),
|
||||||
|
|
||||||
cssMainPane(
|
cssMainPane(
|
||||||
cssTopHeader(
|
cssTopHeader(
|
||||||
@ -75,7 +98,7 @@ export function pagePanels(page: PageContents) {
|
|||||||
cssPanelOpener('PanelRight', cssPanelOpener.cls('-open', left.panelOpen),
|
cssPanelOpener('PanelRight', cssPanelOpener.cls('-open', left.panelOpen),
|
||||||
testId('left-opener'),
|
testId('left-opener'),
|
||||||
dom.on('click', () => toggleObs(left.panelOpen)),
|
dom.on('click', () => toggleObs(left.panelOpen)),
|
||||||
cssHideForNarrowScreen.cls('', optimizeNarrowScreen))
|
cssHideForNarrowScreen.cls(''))
|
||||||
),
|
),
|
||||||
|
|
||||||
page.headerMain,
|
page.headerMain,
|
||||||
@ -84,7 +107,7 @@ export function pagePanels(page: PageContents) {
|
|||||||
cssPanelOpener('PanelLeft', cssPanelOpener.cls('-open', right.panelOpen),
|
cssPanelOpener('PanelLeft', cssPanelOpener.cls('-open', right.panelOpen),
|
||||||
testId('right-opener'),
|
testId('right-opener'),
|
||||||
dom.on('click', () => toggleObs(right.panelOpen)),
|
dom.on('click', () => toggleObs(right.panelOpen)),
|
||||||
cssHideForNarrowScreen.cls('', optimizeNarrowScreen))
|
cssHideForNarrowScreen.cls(''))
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
page.contentMain,
|
page.contentMain,
|
||||||
@ -96,7 +119,7 @@ export function pagePanels(page: PageContents) {
|
|||||||
{target: 'right', onSave: (val) => { right.panelWidth.set(val); onResize(); }},
|
{target: 'right', onSave: (val) => { right.panelWidth.set(val); onResize(); }},
|
||||||
testId('right-resizer'),
|
testId('right-resizer'),
|
||||||
dom.show(right.panelOpen),
|
dom.show(right.panelOpen),
|
||||||
cssHideForNarrowScreen.cls('', optimizeNarrowScreen)),
|
cssHideForNarrowScreen.cls('')),
|
||||||
|
|
||||||
cssRightPane(
|
cssRightPane(
|
||||||
testId('right-panel'),
|
testId('right-panel'),
|
||||||
@ -107,7 +130,7 @@ export function pagePanels(page: PageContents) {
|
|||||||
|
|
||||||
// Opening/closing the right pane, with transitions.
|
// Opening/closing the right pane, with transitions.
|
||||||
cssRightPane.cls('-open', right.panelOpen),
|
cssRightPane.cls('-open', right.panelOpen),
|
||||||
optimizeNarrowScreen && isNarrowScreen() ? null : transition(right.panelOpen, {
|
transition(use => (use(isNarrowScreenObs()) ? false : use(right.panelOpen)), {
|
||||||
prepare(elem, open) { elem.style.marginLeft = (open ? -1 : 1) * right.panelWidth.get() + 'px'; },
|
prepare(elem, open) { elem.style.marginLeft = (open ? -1 : 1) * right.panelWidth.get() + 'px'; },
|
||||||
run(elem, open) { elem.style.marginLeft = ''; },
|
run(elem, open) { elem.style.marginLeft = ''; },
|
||||||
finish: onResize,
|
finish: onResize,
|
||||||
@ -122,8 +145,7 @@ export function pagePanels(page: PageContents) {
|
|||||||
}),
|
}),
|
||||||
testId('overlay')
|
testId('overlay')
|
||||||
),
|
),
|
||||||
), (
|
dom.maybe(isNarrowScreenObs(), () =>
|
||||||
!optimizeNarrowScreen ? null :
|
|
||||||
cssBottomFooter(
|
cssBottomFooter(
|
||||||
testId('bottom-footer'),
|
testId('bottom-footer'),
|
||||||
cssPanelOpenerNarrowScreenBtn(
|
cssPanelOpenerNarrowScreenBtn(
|
||||||
@ -152,7 +174,8 @@ export function pagePanels(page: PageContents) {
|
|||||||
)
|
)
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
)];
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleObs(boolObs: Observable<boolean>) {
|
function toggleObs(boolObs: Observable<boolean>) {
|
||||||
@ -178,12 +201,12 @@ const cssPageContainer = styled(cssHBox, `
|
|||||||
background-color: ${colors.lightGrey};
|
background-color: ${colors.lightGrey};
|
||||||
|
|
||||||
@media ${mediaSmall} {
|
@media ${mediaSmall} {
|
||||||
&-optimizeNarrowScreen {
|
& {
|
||||||
bottom: 48px;
|
padding-bottom: 48px;
|
||||||
min-width: 240px;
|
min-width: 240px;
|
||||||
}
|
}
|
||||||
.interface-light & {
|
.interface-light & {
|
||||||
bottom: 0;
|
padding-bottom: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
@ -195,7 +218,7 @@ export const cssLeftPane = styled(cssVBox, `
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
transition: margin-right 0.4s;
|
transition: margin-right 0.4s;
|
||||||
@media ${mediaSmall} {
|
@media ${mediaSmall} {
|
||||||
.${cssPageContainer.className}-optimizeNarrowScreen & {
|
& {
|
||||||
width: 240px;
|
width: 240px;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
@ -207,7 +230,7 @@ export const cssLeftPane = styled(cssVBox, `
|
|||||||
transition: left 0.4s, visibility 0.4s;
|
transition: left 0.4s, visibility 0.4s;
|
||||||
will-change: left;
|
will-change: left;
|
||||||
}
|
}
|
||||||
.${cssPageContainer.className}-optimizeNarrowScreen &-open {
|
&-open {
|
||||||
left: 0;
|
left: 0;
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
@ -242,7 +265,7 @@ const cssRightPane = styled(cssVBox, `
|
|||||||
transition: margin-left 0.4s;
|
transition: margin-left 0.4s;
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
@media ${mediaSmall} {
|
@media ${mediaSmall} {
|
||||||
.${cssPageContainer.className}-optimizeNarrowScreen & {
|
& {
|
||||||
width: 240px;
|
width: 240px;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
@ -254,7 +277,7 @@ const cssRightPane = styled(cssVBox, `
|
|||||||
transition: right 0.4s, visibility 0.4s;
|
transition: right 0.4s, visibility 0.4s;
|
||||||
will-change: right;
|
will-change: right;
|
||||||
}
|
}
|
||||||
.${cssPageContainer.className}-optimizeNarrowScreen &-open {
|
&-open {
|
||||||
right: 0;
|
right: 0;
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
@ -374,7 +397,7 @@ const cssContentOverlay = styled('div', `
|
|||||||
display: none;
|
display: none;
|
||||||
z-index: 9;
|
z-index: 9;
|
||||||
@media ${mediaSmall} {
|
@media ${mediaSmall} {
|
||||||
.${cssPageContainer.className}-optimizeNarrowScreen & {
|
& {
|
||||||
display: unset;
|
display: unset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ import * as BillingPageCss from "app/client/ui/BillingPageCss";
|
|||||||
import * as forms from "app/client/ui/forms";
|
import * as forms from "app/client/ui/forms";
|
||||||
import { pagePanels } from "app/client/ui/PagePanels";
|
import { pagePanels } from "app/client/ui/PagePanels";
|
||||||
import { bigBasicButton, bigPrimaryButton, bigPrimaryButtonLink, cssButton } from "app/client/ui2018/buttons";
|
import { bigBasicButton, bigPrimaryButton, bigPrimaryButtonLink, cssButton } from "app/client/ui2018/buttons";
|
||||||
import { colors, testId, vars } from "app/client/ui2018/cssVars";
|
import { colors, mediaSmall, testId, vars } from "app/client/ui2018/cssVars";
|
||||||
import { getOrgName, Organization } from "app/common/UserAPI";
|
import { getOrgName, Organization } from "app/common/UserAPI";
|
||||||
|
|
||||||
async function _submitForm(form: HTMLFormElement, pending: Observable<boolean>) {
|
async function _submitForm(form: HTMLFormElement, pending: Observable<boolean>) {
|
||||||
@ -199,7 +199,7 @@ const cssScrollContainer = styled('div', `
|
|||||||
`);
|
`);
|
||||||
|
|
||||||
const cssContainer = styled('div', `
|
const cssContainer = styled('div', `
|
||||||
width: 450px;
|
max-width: 450px;
|
||||||
align-self: center;
|
align-self: center;
|
||||||
margin: 60px;
|
margin: 60px;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -208,6 +208,11 @@ const cssContainer = styled('div', `
|
|||||||
content: "";
|
content: "";
|
||||||
height: 8px;
|
height: 8px;
|
||||||
}
|
}
|
||||||
|
@media ${mediaSmall} {
|
||||||
|
& {
|
||||||
|
margin: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const cssFlexSpace = styled('div', `
|
const cssFlexSpace = styled('div', `
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*/
|
*/
|
||||||
import {urlState} from 'app/client/models/gristUrlState';
|
import {urlState} from 'app/client/models/gristUrlState';
|
||||||
import {getTheme, ProductFlavor} from 'app/client/ui/CustomThemes';
|
import {getTheme, ProductFlavor} from 'app/client/ui/CustomThemes';
|
||||||
import {dom, makeTestId, styled, TestId} from 'grainjs';
|
import {dom, makeTestId, Observable, styled, TestId} from 'grainjs';
|
||||||
import values = require('lodash/values');
|
import values = require('lodash/values');
|
||||||
|
|
||||||
const VAR_PREFIX = 'grist';
|
const VAR_PREFIX = 'grist';
|
||||||
@ -164,8 +164,20 @@ export const mediaNotSmall = `(min-width: ${mediumScreenWidth}px)`;
|
|||||||
|
|
||||||
export const mediaDeviceNotSmall = `(min-device-width: ${mediumScreenWidth}px)`;
|
export const mediaDeviceNotSmall = `(min-device-width: ${mediumScreenWidth}px)`;
|
||||||
|
|
||||||
export function isNarrowScreen() {
|
function isNarrowScreen() {
|
||||||
return window.innerWidth <= 768;
|
return window.innerWidth < mediumScreenWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
let _isNarrowScreenObs: Observable<boolean>|undefined;
|
||||||
|
|
||||||
|
// Returns a singleton observable for whether the screen is a small one.
|
||||||
|
export function isNarrowScreenObs(): Observable<boolean> {
|
||||||
|
if (!_isNarrowScreenObs) {
|
||||||
|
const obs = Observable.create<boolean>(null, isNarrowScreen());
|
||||||
|
window.addEventListener('resize', () => obs.set(isNarrowScreen()));
|
||||||
|
_isNarrowScreenObs = obs;
|
||||||
|
}
|
||||||
|
return _isNarrowScreenObs;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const cssHideForNarrowScreen = styled('div', `
|
export const cssHideForNarrowScreen = styled('div', `
|
||||||
|
Loading…
Reference in New Issue
Block a user