mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-11-14 17:54:47 +01:00
fix: mobile view ui issues (#10284)
* hide zen mode when formFactor = phone * tool bar fixes: icon and width * view mode * fix lint * add exit-view-mode button --------- Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
This commit is contained in:
@@ -852,7 +852,7 @@ const ExcalidrawWrapper = () => {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className="top-right-ui">
|
<div className="excalidraw-ui-top-right">
|
||||||
{collabError.message && <CollabError collabError={collabError} />}
|
{collabError.message && <CollabError collabError={collabError} />}
|
||||||
<LiveCollaborationTrigger
|
<LiveCollaborationTrigger
|
||||||
isCollaborating={isCollaborating}
|
isCollaborating={isCollaborating}
|
||||||
|
|||||||
@@ -5,12 +5,6 @@
|
|||||||
--color-primary-contrast-offset: #726dff; // to offset Chubb illusion
|
--color-primary-contrast-offset: #726dff; // to offset Chubb illusion
|
||||||
}
|
}
|
||||||
|
|
||||||
.top-right-ui {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-center {
|
.footer-center {
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
margin-top: auto;
|
margin-top: auto;
|
||||||
|
|||||||
@@ -25,8 +25,11 @@ export const actionToggleZenMode = register({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
checked: (appState) => appState.zenModeEnabled,
|
checked: (appState) => appState.zenModeEnabled,
|
||||||
predicate: (elements, appState, appProps) => {
|
predicate: (elements, appState, appProps, app) => {
|
||||||
return typeof appProps.zenModeEnabled === "undefined";
|
return (
|
||||||
|
app.editorInterface.formFactor !== "phone" &&
|
||||||
|
typeof appProps.zenModeEnabled === "undefined"
|
||||||
|
);
|
||||||
},
|
},
|
||||||
keyTest: (event) =>
|
keyTest: (event) =>
|
||||||
!event[KEYS.CTRL_OR_CMD] && event.altKey && event.code === CODES.Z,
|
!event[KEYS.CTRL_OR_CMD] && event.altKey && event.code === CODES.Z,
|
||||||
|
|||||||
@@ -49,6 +49,8 @@ import { getFormValue } from "../actions/actionProperties";
|
|||||||
|
|
||||||
import { useTextEditorFocus } from "../hooks/useTextEditorFocus";
|
import { useTextEditorFocus } from "../hooks/useTextEditorFocus";
|
||||||
|
|
||||||
|
import { actionToggleViewMode } from "../actions/actionToggleViewMode";
|
||||||
|
|
||||||
import { getToolbarTools } from "./shapes";
|
import { getToolbarTools } from "./shapes";
|
||||||
|
|
||||||
import "./Actions.scss";
|
import "./Actions.scss";
|
||||||
@@ -79,6 +81,7 @@ import {
|
|||||||
adjustmentsIcon,
|
adjustmentsIcon,
|
||||||
DotsHorizontalIcon,
|
DotsHorizontalIcon,
|
||||||
SelectionIcon,
|
SelectionIcon,
|
||||||
|
pencilIcon,
|
||||||
} from "./icons";
|
} from "./icons";
|
||||||
|
|
||||||
import { Island } from "./Island";
|
import { Island } from "./Island";
|
||||||
@@ -1304,7 +1307,7 @@ export const UndoRedoActions = ({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const ExitZenModeAction = ({
|
export const ExitZenModeButton = ({
|
||||||
actionManager,
|
actionManager,
|
||||||
showExitZenModeBtn,
|
showExitZenModeBtn,
|
||||||
}: {
|
}: {
|
||||||
@@ -1321,3 +1324,17 @@ export const ExitZenModeAction = ({
|
|||||||
{t("buttons.exitZenMode")}
|
{t("buttons.exitZenMode")}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const ExitViewModeButton = ({
|
||||||
|
actionManager,
|
||||||
|
}: {
|
||||||
|
actionManager: ActionManager;
|
||||||
|
}) => (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="disable-view-mode"
|
||||||
|
onClick={() => actionManager.executeAction(actionToggleViewMode)}
|
||||||
|
>
|
||||||
|
{pencilIcon}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
|||||||
@@ -120,4 +120,53 @@
|
|||||||
margin-bottom: auto;
|
margin-bottom: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.disable-view-mode {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
align-items: center;
|
||||||
|
border: 1px solid var(--color-primary);
|
||||||
|
padding: 0.5rem;
|
||||||
|
border-radius: var(--border-radius-lg);
|
||||||
|
background-color: var(--island-bg-color);
|
||||||
|
text-decoration: none !important;
|
||||||
|
|
||||||
|
font-family: var(--ui-font);
|
||||||
|
font-size: 0.8333rem;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
width: var(--mobile-action-button-size, var(--default-button-size));
|
||||||
|
height: var(--mobile-action-button-size, var(--default-button-size));
|
||||||
|
|
||||||
|
border: none;
|
||||||
|
box-shadow: 0 0 0 1px var(--color-surface-lowest);
|
||||||
|
background-color: var(--color-surface-low);
|
||||||
|
color: var(--button-color, var(--color-on-surface)) !important;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
box-shadow: 0 0 0 1px var(--color-brand-active);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--color-primary);
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
&:active {
|
||||||
|
background-color: var(--color-primary-darker);
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme--dark {
|
||||||
|
.plus-banner {
|
||||||
|
&:hover {
|
||||||
|
color: black !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { t } from "../i18n";
|
|||||||
import { calculateScrollCenter } from "../scene";
|
import { calculateScrollCenter } from "../scene";
|
||||||
import { SCROLLBAR_WIDTH, SCROLLBAR_MARGIN } from "../scene/scrollbars";
|
import { SCROLLBAR_WIDTH, SCROLLBAR_MARGIN } from "../scene/scrollbars";
|
||||||
|
|
||||||
import { MobileShapeActions } from "./Actions";
|
import { ExitViewModeButton, MobileShapeActions } from "./Actions";
|
||||||
import { MobileToolBar } from "./MobileToolBar";
|
import { MobileToolBar } from "./MobileToolBar";
|
||||||
import { FixedSideContainer } from "./FixedSideContainer";
|
import { FixedSideContainer } from "./FixedSideContainer";
|
||||||
|
|
||||||
@@ -65,8 +65,18 @@ export const MobileMenu = ({
|
|||||||
DefaultSidebarTriggerTunnel,
|
DefaultSidebarTriggerTunnel,
|
||||||
} = useTunnels();
|
} = useTunnels();
|
||||||
const renderAppTopBar = () => {
|
const renderAppTopBar = () => {
|
||||||
const topRightUI = renderTopRightUI?.(true, appState) ?? (
|
if (appState.openDialog?.name === "elementLinkSelector") {
|
||||||
<DefaultSidebarTriggerTunnel.Out />
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const topRightUI = (
|
||||||
|
<div className="excalidraw-ui-top-right">
|
||||||
|
{renderTopRightUI?.(true, appState) ??
|
||||||
|
(!appState.viewModeEnabled && <DefaultSidebarTriggerTunnel.Out />)}
|
||||||
|
{appState.viewModeEnabled && (
|
||||||
|
<ExitViewModeButton actionManager={actionManager} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
const topLeftUI = (
|
const topLeftUI = (
|
||||||
@@ -76,13 +86,6 @@ export const MobileMenu = ({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (
|
|
||||||
appState.viewModeEnabled ||
|
|
||||||
appState.openDialog?.name === "elementLinkSelector"
|
|
||||||
) {
|
|
||||||
return <div className="App-toolbar-content">{topLeftUI}</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="App-toolbar-content"
|
className="App-toolbar-content"
|
||||||
@@ -117,6 +120,7 @@ export const MobileMenu = ({
|
|||||||
{renderWelcomeScreen && <WelcomeScreenCenterTunnel.Out />}
|
{renderWelcomeScreen && <WelcomeScreenCenterTunnel.Out />}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{!appState.viewModeEnabled && (
|
||||||
<div
|
<div
|
||||||
className="App-bottom-bar"
|
className="App-bottom-bar"
|
||||||
style={{
|
style={{
|
||||||
@@ -152,6 +156,7 @@ export const MobileMenu = ({
|
|||||||
)}
|
)}
|
||||||
</Island>
|
</Island>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<FixedSideContainer side="top" className="App-top-bar">
|
<FixedSideContainer side="top" className="App-top-bar">
|
||||||
{renderAppTopBar()}
|
{renderAppTopBar()}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect, useRef } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
|
||||||
import { KEYS, capitalizeString } from "@excalidraw/common";
|
import { KEYS, capitalizeString } from "@excalidraw/common";
|
||||||
@@ -101,8 +101,6 @@ export const MobileToolBar = ({
|
|||||||
"arrow" | "line"
|
"arrow" | "line"
|
||||||
>("arrow");
|
>("arrow");
|
||||||
|
|
||||||
const toolbarRef = useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
// keep lastActiveGenericShape in sync with active tool if user switches via other UI
|
// keep lastActiveGenericShape in sync with active tool if user switches via other UI
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
@@ -143,8 +141,8 @@ export const MobileToolBar = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const toolbarWidth =
|
const [toolbarWidth, setToolbarWidth] = useState(0);
|
||||||
toolbarRef.current?.getBoundingClientRect()?.width ?? 0 - 8;
|
|
||||||
const WIDTH = 36;
|
const WIDTH = 36;
|
||||||
const GAP = 4;
|
const GAP = 4;
|
||||||
|
|
||||||
@@ -164,6 +162,9 @@ export const MobileToolBar = ({
|
|||||||
"laser",
|
"laser",
|
||||||
"magicframe",
|
"magicframe",
|
||||||
].filter((tool) => {
|
].filter((tool) => {
|
||||||
|
if (showTextToolOutside && tool === "text") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (showImageToolOutside && tool === "image") {
|
if (showImageToolOutside && tool === "image") {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -174,21 +175,30 @@ export const MobileToolBar = ({
|
|||||||
});
|
});
|
||||||
const extraToolSelected = extraTools.includes(activeTool.type);
|
const extraToolSelected = extraTools.includes(activeTool.type);
|
||||||
const extraIcon = extraToolSelected
|
const extraIcon = extraToolSelected
|
||||||
? activeTool.type === "frame"
|
? activeTool.type === "text"
|
||||||
|
? TextIcon
|
||||||
|
: activeTool.type === "image"
|
||||||
|
? ImageIcon
|
||||||
|
: activeTool.type === "frame"
|
||||||
? frameToolIcon
|
? frameToolIcon
|
||||||
: activeTool.type === "embeddable"
|
: activeTool.type === "embeddable"
|
||||||
? EmbedIcon
|
? EmbedIcon
|
||||||
: activeTool.type === "laser"
|
: activeTool.type === "laser"
|
||||||
? laserPointerToolIcon
|
? laserPointerToolIcon
|
||||||
: activeTool.type === "text"
|
|
||||||
? TextIcon
|
|
||||||
: activeTool.type === "magicframe"
|
: activeTool.type === "magicframe"
|
||||||
? MagicIcon
|
? MagicIcon
|
||||||
: extraToolsIcon
|
: extraToolsIcon
|
||||||
: extraToolsIcon;
|
: extraToolsIcon;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mobile-toolbar" ref={toolbarRef}>
|
<div
|
||||||
|
className="mobile-toolbar"
|
||||||
|
ref={(div) => {
|
||||||
|
if (div) {
|
||||||
|
setToolbarWidth(div.getBoundingClientRect().width);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
{/* Hand Tool */}
|
{/* Hand Tool */}
|
||||||
<HandButton
|
<HandButton
|
||||||
checked={isHandToolActive(app.state)}
|
checked={isHandToolActive(app.state)}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import clsx from "clsx";
|
|||||||
|
|
||||||
import { actionShortcuts } from "../../actions";
|
import { actionShortcuts } from "../../actions";
|
||||||
import { useTunnels } from "../../context/tunnels";
|
import { useTunnels } from "../../context/tunnels";
|
||||||
import { ExitZenModeAction, UndoRedoActions, ZoomActions } from "../Actions";
|
import { ExitZenModeButton, UndoRedoActions, ZoomActions } from "../Actions";
|
||||||
import { HelpButton } from "../HelpButton";
|
import { HelpButton } from "../HelpButton";
|
||||||
import { Section } from "../Section";
|
import { Section } from "../Section";
|
||||||
import Stack from "../Stack";
|
import Stack from "../Stack";
|
||||||
@@ -66,7 +66,7 @@ const Footer = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ExitZenModeAction
|
<ExitZenModeButton
|
||||||
actionManager={actionManager}
|
actionManager={actionManager}
|
||||||
showExitZenModeBtn={showExitZenModeBtn}
|
showExitZenModeBtn={showExitZenModeBtn}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -2326,3 +2326,12 @@ export const strokeIcon = createIcon(
|
|||||||
</g>,
|
</g>,
|
||||||
tablerIconProps,
|
tablerIconProps,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const pencilIcon = createIcon(
|
||||||
|
<g strokeWidth={1.25}>
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<path d="M4 20h4l10.5 -10.5a2.828 2.828 0 1 0 -4 -4l-10.5 10.5v4" />
|
||||||
|
<path d="M13.5 6.5l4 4" />
|
||||||
|
</g>,
|
||||||
|
tablerIconProps,
|
||||||
|
);
|
||||||
|
|||||||
@@ -293,7 +293,8 @@ body.excalidraw-cursor-resize * {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.excalidraw-ui-top-left {
|
.excalidraw-ui-top-left,
|
||||||
|
.excalidraw-ui-top-right {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
|
|||||||
Reference in New Issue
Block a user