mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-10-15 20:21:07 +02:00
update mobile menu layout
This commit is contained in:
@@ -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" ||
|
||||
|
@@ -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([
|
||||
|
@@ -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
|
||||
|
@@ -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}
|
||||
/>
|
||||
|
@@ -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>
|
||||
</>
|
||||
|
@@ -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",
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
|
||||
|
@@ -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;
|
||||
|
@@ -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": {
|
||||
|
@@ -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": {
|
||||
|
Reference in New Issue
Block a user