Restyle the mobile UI a bit (#1002)
* Restyle the bottom bar on mobile as an Island * Shorter label for collaboration button, truncate too-long button labels * Refactor safe area things to global vars * Fix scroll bar positioning, don’t block scrollbars with menu island * Update text
This commit is contained in:
parent
f889f6da91
commit
d8bbe536a7
@ -19,7 +19,7 @@
|
|||||||
@media #{$media-query} {
|
@media #{$media-query} {
|
||||||
.Dialog {
|
.Dialog {
|
||||||
--metric: calc(var(--space-factor) * 4);
|
--metric: calc(var(--space-factor) * 4);
|
||||||
--inset-left: #{"max(var(--metric), env(safe-area-inset-left))"};
|
--inset-left: #{"max(var(--metric), var(--sal))"};
|
||||||
--inset-right: #{"max(var(--metric), env(safe-area-inset-right))"};
|
--inset-right: #{"max(var(--metric), env(safe-area-inset-right))"};
|
||||||
}
|
}
|
||||||
.Dialog__title {
|
.Dialog__title {
|
||||||
@ -49,9 +49,9 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding-left: #{"max(calc(var(--padding) * var(--space-factor)), env(safe-area-inset-left))"};
|
padding-left: #{"max(calc(var(--padding) * var(--space-factor)), var(--sal))"};
|
||||||
padding-right: #{"max(calc(var(--padding) * var(--space-factor)), env(safe-area-inset-right))"};
|
padding-right: #{"max(calc(var(--padding) * var(--space-factor)), var(--sar))"};
|
||||||
padding-bottom: #{"max(calc(var(--padding) * var(--space-factor)), env(safe-area-inset-bottom))"};
|
padding-bottom: #{"max(calc(var(--padding) * var(--space-factor)), var(--sab))"};
|
||||||
}
|
}
|
||||||
|
|
||||||
.Dialog .Modal__close {
|
.Dialog .Modal__close {
|
||||||
|
@ -13,6 +13,7 @@ import { calculateScrollCenter, getTargetElement } from "../scene";
|
|||||||
import { SelectedShapeActions, ShapesSwitcher } from "./Actions";
|
import { SelectedShapeActions, ShapesSwitcher } from "./Actions";
|
||||||
import { Section } from "./Section";
|
import { Section } from "./Section";
|
||||||
import { RoomDialog } from "./RoomDialog";
|
import { RoomDialog } from "./RoomDialog";
|
||||||
|
import { SCROLLBAR_WIDTH, SCROLLBAR_MARGIN } from "../scene/scrollbars";
|
||||||
|
|
||||||
type MobileMenuProps = {
|
type MobileMenuProps = {
|
||||||
appState: AppState;
|
appState: AppState;
|
||||||
@ -37,9 +38,40 @@ export function MobileMenu({
|
|||||||
}: MobileMenuProps) {
|
}: MobileMenuProps) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<FixedSideContainer side="top">
|
||||||
|
<Section heading="shapes">
|
||||||
|
{heading => (
|
||||||
|
<Stack.Col gap={4} align="center">
|
||||||
|
<Stack.Row gap={1}>
|
||||||
|
<Island padding={1}>
|
||||||
|
{heading}
|
||||||
|
<Stack.Row gap={1}>
|
||||||
|
<ShapesSwitcher
|
||||||
|
elementType={appState.elementType}
|
||||||
|
setAppState={setAppState}
|
||||||
|
setElements={setElements}
|
||||||
|
elements={elements}
|
||||||
|
/>
|
||||||
|
</Stack.Row>
|
||||||
|
</Island>
|
||||||
|
</Stack.Row>
|
||||||
|
</Stack.Col>
|
||||||
|
)}
|
||||||
|
</Section>
|
||||||
|
<HintViewer appState={appState} elements={elements} />
|
||||||
|
</FixedSideContainer>
|
||||||
|
<div
|
||||||
|
className="App-bottom-bar"
|
||||||
|
style={{
|
||||||
|
marginBottom: SCROLLBAR_WIDTH + SCROLLBAR_MARGIN * 2,
|
||||||
|
marginLeft: SCROLLBAR_WIDTH + SCROLLBAR_MARGIN * 2,
|
||||||
|
marginRight: SCROLLBAR_WIDTH + SCROLLBAR_MARGIN * 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Island padding={3}>
|
||||||
{appState.openMenu === "canvas" ? (
|
{appState.openMenu === "canvas" ? (
|
||||||
<Section className="App-mobile-menu" heading="canvasActions">
|
<Section className="App-mobile-menu" heading="canvasActions">
|
||||||
<div className="App-mobile-menu-scroller panelColumn">
|
<div className="panelColumn">
|
||||||
<Stack.Col gap={4}>
|
<Stack.Col gap={4}>
|
||||||
{actionManager.renderAction("loadScene")}
|
{actionManager.renderAction("loadScene")}
|
||||||
{actionManager.renderAction("saveScene")}
|
{actionManager.renderAction("saveScene")}
|
||||||
@ -67,37 +99,13 @@ export function MobileMenu({
|
|||||||
) : appState.openMenu === "shape" &&
|
) : appState.openMenu === "shape" &&
|
||||||
showSelectedShapeActions(appState, elements) ? (
|
showSelectedShapeActions(appState, elements) ? (
|
||||||
<Section className="App-mobile-menu" heading="selectedShapeActions">
|
<Section className="App-mobile-menu" heading="selectedShapeActions">
|
||||||
<div className="App-mobile-menu-scroller">
|
|
||||||
<SelectedShapeActions
|
<SelectedShapeActions
|
||||||
targetElements={getTargetElement(elements, appState)}
|
targetElements={getTargetElement(elements, appState)}
|
||||||
renderAction={actionManager.renderAction}
|
renderAction={actionManager.renderAction}
|
||||||
elementType={appState.elementType}
|
elementType={appState.elementType}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</Section>
|
</Section>
|
||||||
) : null}
|
) : null}
|
||||||
<FixedSideContainer side="top">
|
|
||||||
<Section heading="shapes">
|
|
||||||
{heading => (
|
|
||||||
<Stack.Col gap={4} align="center">
|
|
||||||
<Stack.Row gap={1}>
|
|
||||||
<Island padding={1}>
|
|
||||||
{heading}
|
|
||||||
<Stack.Row gap={1}>
|
|
||||||
<ShapesSwitcher
|
|
||||||
elementType={appState.elementType}
|
|
||||||
setAppState={setAppState}
|
|
||||||
setElements={setElements}
|
|
||||||
elements={elements}
|
|
||||||
/>
|
|
||||||
</Stack.Row>
|
|
||||||
</Island>
|
|
||||||
</Stack.Row>
|
|
||||||
</Stack.Col>
|
|
||||||
)}
|
|
||||||
</Section>
|
|
||||||
<HintViewer appState={appState} elements={elements} />
|
|
||||||
</FixedSideContainer>
|
|
||||||
<footer className="App-toolbar">
|
<footer className="App-toolbar">
|
||||||
<div className="App-toolbar-content">
|
<div className="App-toolbar-content">
|
||||||
{actionManager.renderAction("toggleCanvasMenu")}
|
{actionManager.renderAction("toggleCanvasMenu")}
|
||||||
@ -118,6 +126,8 @@ export function MobileMenu({
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</footer>
|
</footer>
|
||||||
|
</Island>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -39,6 +39,7 @@
|
|||||||
.ToolIcon__label {
|
.ToolIcon__label {
|
||||||
font-family: var(--ui-font);
|
font-family: var(--ui-font);
|
||||||
margin: 0 0.8em;
|
margin: 0 0.8em;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ToolIcon_size_s .ToolIcon__icon {
|
.ToolIcon_size_s .ToolIcon__icon {
|
||||||
|
@ -66,7 +66,7 @@
|
|||||||
"edit": "Edit",
|
"edit": "Edit",
|
||||||
"undo": "Undo",
|
"undo": "Undo",
|
||||||
"redo": "Redo",
|
"redo": "Redo",
|
||||||
"roomDialog": "Share a live-collaboration session",
|
"roomDialog": "Start live collaboration",
|
||||||
"createNewRoom": "Create new room"
|
"createNewRoom": "Create new room"
|
||||||
},
|
},
|
||||||
"alerts": {
|
"alerts": {
|
||||||
|
@ -2,8 +2,9 @@ import { ExcalidrawElement } from "../element/types";
|
|||||||
import { getCommonBounds } from "../element";
|
import { getCommonBounds } from "../element";
|
||||||
import { FlooredNumber } from "../types";
|
import { FlooredNumber } from "../types";
|
||||||
import { ScrollBars } from "./types";
|
import { ScrollBars } from "./types";
|
||||||
|
import { getGlobalCSSVariable } from "../utils";
|
||||||
|
|
||||||
const SCROLLBAR_MARGIN = 4;
|
export const SCROLLBAR_MARGIN = 4;
|
||||||
export const SCROLLBAR_WIDTH = 6;
|
export const SCROLLBAR_WIDTH = 6;
|
||||||
export const SCROLLBAR_COLOR = "rgba(0,0,0,0.3)";
|
export const SCROLLBAR_COLOR = "rgba(0,0,0,0.3)";
|
||||||
|
|
||||||
@ -36,11 +37,18 @@ export function getScrollBars(
|
|||||||
const viewportWidthDiff = viewportWidth - viewportWidthWithZoom;
|
const viewportWidthDiff = viewportWidth - viewportWidthWithZoom;
|
||||||
const viewportHeightDiff = viewportHeight - viewportHeightWithZoom;
|
const viewportHeightDiff = viewportHeight - viewportHeightWithZoom;
|
||||||
|
|
||||||
|
const safeArea = {
|
||||||
|
top: parseInt(getGlobalCSSVariable("sat")),
|
||||||
|
bottom: parseInt(getGlobalCSSVariable("sab")),
|
||||||
|
left: parseInt(getGlobalCSSVariable("sal")),
|
||||||
|
right: parseInt(getGlobalCSSVariable("sar")),
|
||||||
|
};
|
||||||
|
|
||||||
// The viewport is the rectangle currently visible for the user
|
// The viewport is the rectangle currently visible for the user
|
||||||
const viewportMinX = -scrollX + viewportWidthDiff / 2;
|
const viewportMinX = -scrollX + viewportWidthDiff / 2 + safeArea.left;
|
||||||
const viewportMinY = -scrollY + viewportHeightDiff / 2;
|
const viewportMinY = -scrollY + viewportHeightDiff / 2 + safeArea.top;
|
||||||
const viewportMaxX = viewportMinX + viewportWidthWithZoom;
|
const viewportMaxX = viewportMinX + viewportWidthWithZoom - safeArea.right;
|
||||||
const viewportMaxY = viewportMinY + viewportHeightWithZoom;
|
const viewportMaxY = viewportMinY + viewportHeightWithZoom - safeArea.bottom;
|
||||||
|
|
||||||
// The scene is the bounding box of both the elements and viewport
|
// The scene is the bounding box of both the elements and viewport
|
||||||
const sceneMinX = Math.min(elementsMinX, viewportMinX);
|
const sceneMinX = Math.min(elementsMinX, viewportMinX);
|
||||||
@ -56,30 +64,36 @@ export function getScrollBars(
|
|||||||
? null
|
? null
|
||||||
: {
|
: {
|
||||||
x:
|
x:
|
||||||
|
Math.max(safeArea.left, SCROLLBAR_MARGIN) +
|
||||||
((viewportMinX - sceneMinX) / (sceneMaxX - sceneMinX)) *
|
((viewportMinX - sceneMinX) / (sceneMaxX - sceneMinX)) *
|
||||||
viewportWidth +
|
viewportWidth,
|
||||||
SCROLLBAR_MARGIN,
|
y:
|
||||||
y: viewportHeight - SCROLLBAR_WIDTH - SCROLLBAR_MARGIN,
|
viewportHeight -
|
||||||
|
SCROLLBAR_WIDTH -
|
||||||
|
Math.max(SCROLLBAR_MARGIN, safeArea.bottom),
|
||||||
width:
|
width:
|
||||||
((viewportMaxX - viewportMinX) / (sceneMaxX - sceneMinX)) *
|
((viewportMaxX - viewportMinX) / (sceneMaxX - sceneMinX)) *
|
||||||
viewportWidth -
|
viewportWidth -
|
||||||
SCROLLBAR_MARGIN * 2,
|
Math.max(SCROLLBAR_MARGIN * 2, safeArea.left + safeArea.right),
|
||||||
height: SCROLLBAR_WIDTH,
|
height: SCROLLBAR_WIDTH,
|
||||||
},
|
},
|
||||||
vertical:
|
vertical:
|
||||||
viewportMinY === sceneMinY && viewportMaxY === sceneMaxY
|
viewportMinY === sceneMinY && viewportMaxY === sceneMaxY
|
||||||
? null
|
? null
|
||||||
: {
|
: {
|
||||||
x: viewportWidth - SCROLLBAR_WIDTH - SCROLLBAR_MARGIN,
|
x:
|
||||||
|
viewportWidth -
|
||||||
|
SCROLLBAR_WIDTH -
|
||||||
|
Math.max(safeArea.right, SCROLLBAR_MARGIN),
|
||||||
y:
|
y:
|
||||||
((viewportMinY - sceneMinY) / (sceneMaxY - sceneMinY)) *
|
((viewportMinY - sceneMinY) / (sceneMaxY - sceneMinY)) *
|
||||||
viewportHeight +
|
viewportHeight +
|
||||||
SCROLLBAR_MARGIN,
|
Math.max(safeArea.top, SCROLLBAR_MARGIN),
|
||||||
width: SCROLLBAR_WIDTH,
|
width: SCROLLBAR_WIDTH,
|
||||||
height:
|
height:
|
||||||
((viewportMaxY - viewportMinY) / (sceneMaxY - sceneMinY)) *
|
((viewportMaxY - viewportMinY) / (sceneMaxY - sceneMinY)) *
|
||||||
viewportHeight -
|
viewportHeight -
|
||||||
SCROLLBAR_MARGIN * 2,
|
Math.max(SCROLLBAR_MARGIN * 2, safeArea.top + safeArea.bottom),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,13 @@
|
|||||||
@import "./theme.css";
|
@import "./theme.css";
|
||||||
@import "./_variables";
|
@import "./_variables";
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--sat: env(safe-area-inset-top);
|
||||||
|
--sab: env(safe-area-inset-bottom);
|
||||||
|
--sal: env(safe-area-inset-left);
|
||||||
|
--sar: env(safe-area-inset-right);
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
--ui-font: Arial, Helvetica, sans-serif;
|
--ui-font: Arial, Helvetica, sans-serif;
|
||||||
@ -119,6 +126,7 @@ button,
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
|
outline: transparent;
|
||||||
box-shadow: 0 0 0 2px #a5d8ff;
|
box-shadow: 0 0 0 2px #a5d8ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,24 +154,38 @@ button,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.App-toolbar,
|
.App-bottom-bar {
|
||||||
.App-mobile-menu {
|
|
||||||
--spacing: 0.5rem;
|
|
||||||
--padding: calc(4 * var(--space-factor));
|
|
||||||
padding: var(--padding);
|
|
||||||
padding-left: #{"max(var(--padding), env(safe-area-inset-left))"};
|
|
||||||
padding-right: #{"max(var(--padding), env(safe-area-inset-right))"};
|
|
||||||
background: #fcfcfc;
|
|
||||||
border-top: 1px solid #ccc;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
.App-toolbar {
|
|
||||||
padding-bottom: #{"max(var(--padding), env(safe-area-inset-bottom))"};
|
|
||||||
width: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
overflow: auto;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
--bar-padding: calc(4 * var(--space-factor));
|
||||||
|
padding-top: #{"max(var(--bar-padding), var(--sat))"};
|
||||||
|
padding-left: var(--sal);
|
||||||
|
padding-right: var(--sar);
|
||||||
|
padding-bottom: var(--sab);
|
||||||
|
z-index: 4;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
> .Island {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
min-width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
max-height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
pointer-events: initial;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.App-toolbar {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
.App-toolbar-content {
|
.App-toolbar-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -171,18 +193,11 @@ button,
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
.App-mobile-menu {
|
.App-mobile-menu {
|
||||||
--bottom: calc(3rem - 1px + max(var(--padding), env(safe-area-inset-bottom)));
|
|
||||||
display: grid;
|
|
||||||
position: fixed;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
bottom: var(--bottom);
|
overflow-x: visible;
|
||||||
z-index: 4;
|
overflow-y: auto;
|
||||||
max-height: calc(100% - var(--bottom));
|
box-sizing: border-box;
|
||||||
overflow-y: scroll;
|
margin-bottom: var(--bar-padding);
|
||||||
}
|
|
||||||
.App-mobile-menu .App-mobile-menu-scroller {
|
|
||||||
background: #fcfcfc;
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.App-menu {
|
.App-menu {
|
||||||
@ -358,6 +373,7 @@ button,
|
|||||||
bottom: 30px;
|
bottom: 30px;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
padding: 10px 20px;
|
padding: 10px 20px;
|
||||||
|
z-index: -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media #{$media-query} {
|
@media #{$media-query} {
|
||||||
@ -366,6 +382,6 @@ button,
|
|||||||
}
|
}
|
||||||
.scroll-back-to-content {
|
.scroll-back-to-content {
|
||||||
bottom: 80px;
|
bottom: 80px;
|
||||||
bottom: calc(80px + env(safe-area-inset-bottom));
|
bottom: calc(80px + var(--sab));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -194,3 +194,9 @@ export function sceneCoordsToViewportCoords(
|
|||||||
|
|
||||||
return { x, y };
|
return { x, y };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getGlobalCSSVariable(name: string) {
|
||||||
|
return getComputedStyle(document.documentElement).getPropertyValue(
|
||||||
|
`--${name}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user