fix: mobile UI and other fixes (#10177)

* remove legacy openMenu=shape state and unused actions

* close menus/popups in applicable cases when opening a different one

* split ui z-indexes to account prefer different overlap

* make top canvas area clickable on mobile

* make mobile main menu closable by clicking outside and reduce width

* offset picker popups from viewport border on mobile

* reduce items gap in mobile main menu

* show top picks for canvas bg colors in all ui modes

* fix menu separator visibility on mobile

* fix command palette items not being filtered
This commit is contained in:
David Luzar
2025-10-14 16:34:49 +02:00
committed by GitHub
parent 8608d7b2e0
commit 5fffc4743f
19 changed files with 58 additions and 85 deletions
+4 -1
View File
@@ -1178,7 +1178,10 @@ export const ShapesSwitcher = ({
// on top of it
(laserToolSelected && !app.props.isCollaborating),
})}
onToggle={() => setIsExtraToolsMenuOpen(!isExtraToolsMenuOpen)}
onToggle={() => {
setIsExtraToolsMenuOpen(!isExtraToolsMenuOpen);
setAppState({ openMenu: null, openPopup: null });
}}
title={t("toolBar.extraTools")}
>
{frameToolSelected
@@ -319,8 +319,9 @@ export const ColorPicker = ({
openRef.current = appState.openPopup;
}, [appState.openPopup]);
const compactMode =
appState.stylesPanelMode === "compact" ||
appState.stylesPanelMode === "mobile";
type !== "canvasBackground" &&
(appState.stylesPanelMode === "compact" ||
appState.stylesPanelMode === "mobile");
return (
<div>
@@ -476,7 +476,6 @@ function CommandPaletteInner({
},
perform: () => {
setAppState((prevState) => ({
openMenu: prevState.openMenu === "shape" ? null : "shape",
openPopup: "elementStroke",
}));
},
@@ -496,7 +495,6 @@ function CommandPaletteInner({
},
perform: () => {
setAppState((prevState) => ({
openMenu: prevState.openMenu === "shape" ? null : "shape",
openPopup: "elementBackground",
}));
},
@@ -838,7 +836,12 @@ function CommandPaletteInner({
let matchingCommands =
commandSearch?.length > 1
? [...allCommands, ...libraryCommands]
? [
...allCommands
.filter(isCommandAvailable)
.sort((a, b) => a.order - b.order),
...libraryCommands,
]
: allCommands
.filter(isCommandAvailable)
.sort((a, b) => a.order - b.order);
@@ -159,7 +159,7 @@ function Picker<T>({
side={isMobile ? "right" : "bottom"}
align="start"
sideOffset={isMobile ? 8 : 12}
style={{ zIndex: "var(--zIndex-popup)" }}
style={{ zIndex: "var(--zIndex-ui-styles-popup)" }}
onKeyDown={handleKeyDown}
>
<div
@@ -133,6 +133,7 @@
}
.layer-ui__library .library-menu-dropdown-container {
z-index: 1;
position: relative;
&--in-heading {
margin-left: auto;
@@ -374,7 +374,10 @@ export const MobileToolBar = ({
extraToolSelected || isOtherShapesMenuOpen,
},
)}
onToggle={() => setIsOtherShapesMenuOpen(!isOtherShapesMenuOpen)}
onToggle={() => {
setIsOtherShapesMenuOpen(!isOtherShapesMenuOpen);
setAppState({ openMenu: null, openPopup: null });
}}
title={t("toolBar.extraTools")}
style={{
width: WIDTH,
@@ -60,7 +60,8 @@ export const PropertiesPopover = React.forwardRef<
alignOffset={-16}
sideOffset={20}
style={{
zIndex: "var(--zIndex-popup)",
zIndex: "var(--zIndex-ui-styles-popup)",
marginLeft: device.editor.isMobile ? "0.5rem" : undefined,
}}
onPointerLeave={onPointerLeave}
onKeyDown={onKeyDown}
@@ -9,7 +9,7 @@
top: 0;
bottom: 0;
right: 0;
z-index: 5;
z-index: var(--zIndex-ui-library);
margin: 0;
padding: 0;
box-sizing: border-box;
@@ -30,7 +30,11 @@ export const SidebarTrigger = ({
.querySelector(".layer-ui__wrapper")
?.classList.remove("animate");
const isOpen = event.target.checked;
setAppState({ openSidebar: isOpen ? { name, tab } : null });
setAppState({
openSidebar: isOpen ? { name, tab } : null,
openMenu: null,
openPopup: null,
});
onToggle?.(isOpen);
}}
checked={appState.openSidebar?.name === name}
@@ -5,6 +5,7 @@
position: absolute;
top: 2.5rem;
margin-top: 0.5rem;
max-width: 16rem;
&--placement-top {
top: auto;
@@ -20,10 +21,8 @@
// When main menu is in the top toolbar, position relative to trigger
&.main-menu-dropdown {
min-width: 232px;
max-width: calc(100vw - var(--editor-container-padding) * 2);
margin-top: 0;
margin-bottom: 0;
z-index: var(--zIndex-layerUI);
@media screen and (orientation: landscape) {
max-width: 232px;
@@ -74,7 +74,12 @@ const MenuContent = ({
{/* the zIndex ensures this menu has higher stacking order,
see https://github.com/excalidraw/excalidraw/pull/1445 */}
{device.editor.isMobile ? (
<Stack.Col className="dropdown-menu-container">{children}</Stack.Col>
<Stack.Col
className="dropdown-menu-container"
style={{ ["--gap" as any]: 1.25 }}
>
{children}
</Stack.Col>
) : (
<Island
className="dropdown-menu-container"
@@ -6,6 +6,7 @@ const MenuSeparator = () => (
height: "1px",
backgroundColor: "var(--default-border-color)",
margin: ".5rem 0",
flex: "0 0 auto",
}}
/>
);
@@ -30,9 +30,6 @@ const MainMenu = Object.assign(
const device = useDevice();
const appState = useUIAppState();
const setAppState = useExcalidrawSetAppState();
const onClickOutside = device.editor.isMobile
? undefined
: () => setAppState({ openMenu: null });
return (
<MainMenuTunnel.In>
@@ -41,6 +38,8 @@ const MainMenu = Object.assign(
onToggle={() => {
setAppState({
openMenu: appState.openMenu === "canvas" ? null : "canvas",
openPopup: null,
openDialog: null,
});
}}
data-testid="main-menu-trigger"
@@ -49,7 +48,7 @@ const MainMenu = Object.assign(
{HamburgerMenuIcon}
</DropdownMenu.Trigger>
<DropdownMenu.Content
onClickOutside={onClickOutside}
onClickOutside={() => setAppState({ openMenu: null })}
onSelect={composeEventHandlers(onSelect, () => {
setAppState({ openMenu: null });
})}