update mobile menu layout

This commit is contained in:
Ryan Di
2025-09-19 14:03:52 +10:00
parent 62be53845e
commit e6da5b213d
11 changed files with 103 additions and 103 deletions

View File

@@ -10,7 +10,13 @@ export const hasBackground = (type: ElementOrToolType) =>
type === "freedraw";
export const hasStrokeColor = (type: ElementOrToolType) =>
type !== "image" && type !== "frame" && type !== "magicframe";
type === "rectangle" ||
type === "ellipse" ||
type === "diamond" ||
type === "freedraw" ||
type === "arrow" ||
type === "line" ||
type === "text";
export const hasStrokeWidth = (type: ElementOrToolType) =>
type === "rectangle" ||

View File

@@ -78,6 +78,8 @@ import {
DotsHorizontalIcon,
} from "./icons";
import { Island } from "./Island";
import type {
AppClassProperties,
AppProps,
@@ -86,7 +88,6 @@ import type {
AppState,
} from "../types";
import type { ActionManager } from "../actions/manager";
import { Island } from "./Island";
// Common CSS class combinations
const PROPERTIES_CLASSES = clsx([

View File

@@ -2488,6 +2488,8 @@ class App extends React.Component<AppProps, AppState> {
// but not too narrow (> MQ_MAX_WIDTH_MOBILE)
this.isTabletBreakpoint(editorWidth, editorHeight) && isMobileOrTablet()
? "compact"
: this.isMobileBreakpoint(editorWidth, editorHeight)
? "mobile"
: "full",
});
@@ -6489,6 +6491,10 @@ class App extends React.Component<AppProps, AppState> {
this.setAppState({ snapLines: [] });
}
if (this.state.openPopup) {
this.setState({ openPopup: null });
}
this.updateGestureOnPointerDown(event);
// if dragging element is freedraw and another pointerdown event occurs

View File

@@ -582,13 +582,10 @@ const LayerUI = ({
renderJSONExportDialog={renderJSONExportDialog}
renderImageExportDialog={renderImageExportDialog}
setAppState={setAppState}
onLockToggle={onLockToggle}
onHandToolToggle={onHandToolToggle}
onPenModeToggle={onPenModeToggle}
renderTopRightUI={renderTopRightUI}
renderCustomStats={renderCustomStats}
renderSidebars={renderSidebars}
device={device}
renderWelcomeScreen={renderWelcomeScreen}
UIOptions={UIOptions}
/>

View File

@@ -1,33 +1,23 @@
import React from "react";
import { showSelectedShapeActions } from "@excalidraw/element";
import type { NonDeletedExcalidrawElement } from "@excalidraw/element/types";
import { isHandToolActive } from "../appState";
import { useTunnels } from "../context/tunnels";
import { t } from "../i18n";
import { calculateScrollCenter } from "../scene";
import { SCROLLBAR_WIDTH, SCROLLBAR_MARGIN } from "../scene/scrollbars";
import { SelectedShapeActions, ShapesSwitcher } from "./Actions";
import { MobileShapeActions } from "./Actions";
import { MobileToolBar } from "./MobileToolBar";
import { FixedSideContainer } from "./FixedSideContainer";
import { HandButton } from "./HandButton";
import { HintViewer } from "./HintViewer";
import { Island } from "./Island";
import { LockButton } from "./LockButton";
import { PenModeButton } from "./PenModeButton";
import { Section } from "./Section";
import Stack from "./Stack";
import type { ActionManager } from "../actions/manager";
import type {
AppClassProperties,
AppProps,
AppState,
Device,
ExcalidrawProps,
UIAppState,
} from "../types";
import type { JSX } from "react";
@@ -39,7 +29,6 @@ type MobileMenuProps = {
renderImageExportDialog: () => React.ReactNode;
setAppState: React.Component<any, AppState>["setState"];
elements: readonly NonDeletedExcalidrawElement[];
onLockToggle: () => void;
onHandToolToggle: () => void;
onPenModeToggle: AppClassProperties["togglePenMode"];
@@ -47,9 +36,7 @@ type MobileMenuProps = {
isMobile: boolean,
appState: UIAppState,
) => JSX.Element | null;
renderCustomStats?: ExcalidrawProps["renderCustomStats"];
renderSidebars: () => JSX.Element | null;
device: Device;
renderWelcomeScreen: boolean;
UIOptions: AppProps["UIOptions"];
app: AppClassProperties;
@@ -60,14 +47,10 @@ export const MobileMenu = ({
elements,
actionManager,
setAppState,
onLockToggle,
onHandToolToggle,
onPenModeToggle,
renderTopRightUI,
renderCustomStats,
renderSidebars,
device,
renderWelcomeScreen,
UIOptions,
app,
@@ -79,20 +62,15 @@ export const MobileMenu = ({
} = useTunnels();
const renderToolbar = () => {
return (
<div>
{/* {renderWelcomeScreen && <WelcomeScreenCenterTunnel.Out />} */}
<MobileToolBar
appState={appState}
app={app}
actionManager={actionManager}
onHandToolToggle={onHandToolToggle}
UIOptions={UIOptions}
/>
</div>
<MobileToolBar
appState={appState}
app={app}
onHandToolToggle={onHandToolToggle}
/>
);
};
const renderAppToolbar = () => {
const renderAppTopBar = () => {
if (
appState.viewModeEnabled ||
appState.openDialog?.name === "elementLinkSelector"
@@ -104,70 +82,72 @@ export const MobileMenu = ({
);
}
const topRightUI = renderTopRightUI?.(true, appState);
return (
<div className="App-toolbar-content">
<MainMenuTunnel.Out />
{renderTopRightUI?.(true, appState)}
<div
className="App-toolbar-content"
style={{
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
}}
>
<div
style={{
display: "flex",
flexDirection: "row",
alignItems: "center",
gap: 16,
}}
>
<MainMenuTunnel.Out />
</div>
{topRightUI ? topRightUI : <DefaultSidebarTriggerTunnel.Out />}
</div>
);
};
return (
<>
{/* {renderSidebars()} */}
{renderSidebars()}
<FixedSideContainer side="top" className="App-top-bar">
{renderAppToolbar()}
<HintViewer
appState={appState}
isMobile={true}
device={device}
app={app}
/>
{renderAppTopBar()}
{renderWelcomeScreen && <WelcomeScreenCenterTunnel.Out />}
</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,
marginBottom: SCROLLBAR_WIDTH + SCROLLBAR_MARGIN,
}}
>
<Island padding={0}>
{appState.openMenu === "shape" &&
!appState.viewModeEnabled &&
appState.openDialog?.name !== "elementLinkSelector" &&
showSelectedShapeActions(appState, elements) ? (
<Section className="App-mobile-menu" heading="selectedShapeActions">
<SelectedShapeActions
appState={appState}
elementsMap={app.scene.getNonDeletedElementsMap()}
renderAction={actionManager.renderAction}
app={app}
/>
</Section>
) : null}
<footer className="App-toolbar">
{!appState.viewModeEnabled &&
appState.openDialog?.name !== "elementLinkSelector" &&
renderToolbar()}
{appState.scrolledOutside &&
!appState.openMenu &&
!appState.openSidebar && (
<button
type="button"
className="scroll-back-to-content"
onClick={() => {
setAppState((appState) => ({
...calculateScrollCenter(elements, appState),
}));
}}
>
{t("buttons.scrollBackToContent")}
</button>
)}
</footer>
<MobileShapeActions
appState={appState}
elementsMap={app.scene.getNonDeletedElementsMap()}
renderAction={actionManager.renderAction}
app={app}
setAppState={setAppState}
/>
<Island className="App-toolbar">
{!appState.viewModeEnabled &&
appState.openDialog?.name !== "elementLinkSelector" &&
renderToolbar()}
{appState.scrolledOutside &&
!appState.openMenu &&
!appState.openSidebar && (
<button
type="button"
className="scroll-back-to-content"
onClick={() => {
setAppState((appState) => ({
...calculateScrollCenter(elements, appState),
}));
}}
>
{t("buttons.scrollBackToContent")}
</button>
)}
</Island>
</div>
</>

View File

@@ -3,6 +3,14 @@ import clsx from "clsx";
import { KEYS, capitalizeString } from "@excalidraw/common";
import { trackEvent } from "../analytics";
import { t } from "../i18n";
import { isHandToolActive } from "../appState";
import { useTunnels } from "../context/tunnels";
import { HandButton } from "./HandButton";
import { ToolButton } from "./ToolButton";
import DropdownMenu from "./dropdownMenu/DropdownMenu";
@@ -28,16 +36,11 @@ import {
MagicIcon,
} from "./icons";
import { trackEvent } from "../analytics";
import { t } from "../i18n";
import { isHandToolActive } from "../appState";
import { useTunnels } from "../context/tunnels";
import type { AppClassProperties, UIAppState } from "../types";
import "./ToolIcon.scss";
import "./MobileToolBar.scss";
import type { AppClassProperties, UIAppState } from "../types";
const SHAPE_TOOLS = [
{
type: "rectangle",

View File

@@ -1,11 +1,13 @@
import React, { useEffect, useRef, useState } from "react";
import clsx from "clsx";
import { capitalizeString, CLASSES } from "@excalidraw/common";
import { trackEvent } from "../analytics";
import { ToolButton } from "./ToolButton";
import type { AppClassProperties } from "../types";
import { capitalizeString, CLASSES } from "@excalidraw/common";
import { trackEvent } from "../analytics";
type ToolOption = {
type: string;

View File

@@ -239,16 +239,18 @@ body.excalidraw-cursor-resize * {
.App-bottom-bar {
position: absolute;
top: 0;
// account for margins
width: calc(100% - 28px);
max-width: 400px;
bottom: 0;
left: 0;
right: 0;
left: 50%;
transform: translateX(-50%);
--bar-padding: calc(4 * var(--space-factor));
z-index: 4;
display: flex;
align-items: flex-end;
flex-direction: column;
pointer-events: none;
display: flex;
justify-content: center;
> .Island {
@@ -262,7 +264,6 @@ body.excalidraw-cursor-resize * {
}
.App-toolbar {
width: 100%;
display: flex;
justify-content: center;

View File

@@ -43,6 +43,10 @@
--lg-icon-size: 1rem;
--editor-container-padding: 1rem;
@include isMobile {
--editor-container-padding: 0.75rem;
}
@media screen and (min-device-width: 1921px) {
--lg-button-size: 2.5rem;
--lg-icon-size: 1.25rem;

View File

@@ -6101,7 +6101,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
"offsetTop": 0,
"openDialog": null,
"openMenu": null,
"openPopup": "elementBackground",
"openPopup": null,
"openSidebar": null,
"originSnapOffset": null,
"pasteDialog": {
@@ -11961,7 +11961,7 @@ exports[`history > singleplayer undo/redo > should create entry when selecting f
"offsetTop": 0,
"openDialog": null,
"openMenu": null,
"openPopup": "elementStroke",
"openPopup": null,
"openSidebar": null,
"originSnapOffset": null,
"pasteDialog": {
@@ -15533,7 +15533,7 @@ exports[`history > singleplayer undo/redo > should not override appstate changes
"offsetTop": 0,
"openDialog": null,
"openMenu": null,
"openPopup": "elementBackground",
"openPopup": null,
"openSidebar": null,
"originSnapOffset": null,
"pasteDialog": {

View File

@@ -7384,7 +7384,7 @@ exports[`regression tests > given a selected element A and a not selected elemen
"offsetTop": 0,
"openDialog": null,
"openMenu": null,
"openPopup": "elementBackground",
"openPopup": null,
"openSidebar": null,
"originSnapOffset": null,
"pasteDialog": {