From 5fffc4743fecb89eeb549c8cf4fb4b9ffd05c703 Mon Sep 17 00:00:00 2001 From: David Luzar <5153846+dwelle@users.noreply.github.com> Date: Tue, 14 Oct 2025 16:34:49 +0200 Subject: [PATCH] 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 --- packages/excalidraw/actions/actionMenu.tsx | 58 +------------------ packages/excalidraw/actions/index.ts | 6 +- packages/excalidraw/actions/types.ts | 2 - packages/excalidraw/components/Actions.tsx | 5 +- .../components/ColorPicker/ColorPicker.tsx | 5 +- .../CommandPalette/CommandPalette.tsx | 9 ++- packages/excalidraw/components/IconPicker.tsx | 2 +- .../excalidraw/components/LibraryMenu.scss | 1 + .../excalidraw/components/MobileToolBar.tsx | 5 +- .../components/PropertiesPopover.tsx | 3 +- .../components/Sidebar/Sidebar.scss | 2 +- .../components/Sidebar/SidebarTrigger.tsx | 6 +- .../components/dropdownMenu/DropdownMenu.scss | 3 +- .../dropdownMenu/DropdownMenuContent.tsx | 7 ++- .../dropdownMenu/DropdownMenuSeparator.tsx | 1 + .../components/main-menu/MainMenu.tsx | 7 +-- packages/excalidraw/css/styles.scss | 15 ++++- .../__snapshots__/excalidraw.test.tsx.snap | 4 +- packages/excalidraw/types.ts | 2 +- 19 files changed, 58 insertions(+), 85 deletions(-) diff --git a/packages/excalidraw/actions/actionMenu.tsx b/packages/excalidraw/actions/actionMenu.tsx index 2c6a774456..4cb95c2f0f 100644 --- a/packages/excalidraw/actions/actionMenu.tsx +++ b/packages/excalidraw/actions/actionMenu.tsx @@ -1,65 +1,11 @@ import { KEYS } from "@excalidraw/common"; -import { getNonDeletedElements } from "@excalidraw/element"; - -import { showSelectedShapeActions } from "@excalidraw/element"; - import { CaptureUpdateAction } from "@excalidraw/element"; -import { ToolButton } from "../components/ToolButton"; -import { HamburgerMenuIcon, HelpIconThin, palette } from "../components/icons"; -import { t } from "../i18n"; +import { HelpIconThin } from "../components/icons"; import { register } from "./register"; -export const actionToggleCanvasMenu = register({ - name: "toggleCanvasMenu", - label: "buttons.menu", - trackEvent: { category: "menu" }, - perform: (_, appState) => ({ - appState: { - ...appState, - openMenu: appState.openMenu === "canvas" ? null : "canvas", - }, - captureUpdate: CaptureUpdateAction.EVENTUALLY, - }), - PanelComponent: ({ appState, updateData }) => ( - - ), -}); - -export const actionToggleEditMenu = register({ - name: "toggleEditMenu", - label: "buttons.edit", - trackEvent: { category: "menu" }, - perform: (_elements, appState) => ({ - appState: { - ...appState, - openMenu: appState.openMenu === "shape" ? null : "shape", - }, - captureUpdate: CaptureUpdateAction.EVENTUALLY, - }), - PanelComponent: ({ elements, appState, updateData }) => ( - - ), -}); - export const actionShortcuts = register({ name: "toggleShortcuts", label: "welcomeScreen.defaults.helpHint", @@ -79,6 +25,8 @@ export const actionShortcuts = register({ : { name: "help", }, + openMenu: null, + openPopup: null, }, captureUpdate: CaptureUpdateAction.EVENTUALLY, }; diff --git a/packages/excalidraw/actions/index.ts b/packages/excalidraw/actions/index.ts index 2719a5d0a2..6b888e92d3 100644 --- a/packages/excalidraw/actions/index.ts +++ b/packages/excalidraw/actions/index.ts @@ -44,11 +44,7 @@ export { } from "./actionExport"; export { actionCopyStyles, actionPasteStyles } from "./actionStyles"; -export { - actionToggleCanvasMenu, - actionToggleEditMenu, - actionShortcuts, -} from "./actionMenu"; +export { actionShortcuts } from "./actionMenu"; export { actionGroup, actionUngroup } from "./actionGroup"; diff --git a/packages/excalidraw/actions/types.ts b/packages/excalidraw/actions/types.ts index 302a76fb4e..d533294d39 100644 --- a/packages/excalidraw/actions/types.ts +++ b/packages/excalidraw/actions/types.ts @@ -72,8 +72,6 @@ export type ActionName = | "changeArrowProperties" | "changeOpacity" | "changeFontSize" - | "toggleCanvasMenu" - | "toggleEditMenu" | "undo" | "redo" | "finalize" diff --git a/packages/excalidraw/components/Actions.tsx b/packages/excalidraw/components/Actions.tsx index ec95d40c3e..48ec4dc9a2 100644 --- a/packages/excalidraw/components/Actions.tsx +++ b/packages/excalidraw/components/Actions.tsx @@ -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 diff --git a/packages/excalidraw/components/ColorPicker/ColorPicker.tsx b/packages/excalidraw/components/ColorPicker/ColorPicker.tsx index 759ab9cad2..238960fa0b 100644 --- a/packages/excalidraw/components/ColorPicker/ColorPicker.tsx +++ b/packages/excalidraw/components/ColorPicker/ColorPicker.tsx @@ -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 (
diff --git a/packages/excalidraw/components/CommandPalette/CommandPalette.tsx b/packages/excalidraw/components/CommandPalette/CommandPalette.tsx index 03f9c93cb8..e9f4c72d4c 100644 --- a/packages/excalidraw/components/CommandPalette/CommandPalette.tsx +++ b/packages/excalidraw/components/CommandPalette/CommandPalette.tsx @@ -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); diff --git a/packages/excalidraw/components/IconPicker.tsx b/packages/excalidraw/components/IconPicker.tsx index 2d7f8a0ba5..031d181eb0 100644 --- a/packages/excalidraw/components/IconPicker.tsx +++ b/packages/excalidraw/components/IconPicker.tsx @@ -159,7 +159,7 @@ function Picker({ side={isMobile ? "right" : "bottom"} align="start" sideOffset={isMobile ? 8 : 12} - style={{ zIndex: "var(--zIndex-popup)" }} + style={{ zIndex: "var(--zIndex-ui-styles-popup)" }} onKeyDown={handleKeyDown} >
setIsOtherShapesMenuOpen(!isOtherShapesMenuOpen)} + onToggle={() => { + setIsOtherShapesMenuOpen(!isOtherShapesMenuOpen); + setAppState({ openMenu: null, openPopup: null }); + }} title={t("toolBar.extraTools")} style={{ width: WIDTH, diff --git a/packages/excalidraw/components/PropertiesPopover.tsx b/packages/excalidraw/components/PropertiesPopover.tsx index d4437b3858..3c03c35b99 100644 --- a/packages/excalidraw/components/PropertiesPopover.tsx +++ b/packages/excalidraw/components/PropertiesPopover.tsx @@ -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} diff --git a/packages/excalidraw/components/Sidebar/Sidebar.scss b/packages/excalidraw/components/Sidebar/Sidebar.scss index c7776d1c69..2fba020ca9 100644 --- a/packages/excalidraw/components/Sidebar/Sidebar.scss +++ b/packages/excalidraw/components/Sidebar/Sidebar.scss @@ -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; diff --git a/packages/excalidraw/components/Sidebar/SidebarTrigger.tsx b/packages/excalidraw/components/Sidebar/SidebarTrigger.tsx index 6e8bf374ce..706a6abe52 100644 --- a/packages/excalidraw/components/Sidebar/SidebarTrigger.tsx +++ b/packages/excalidraw/components/Sidebar/SidebarTrigger.tsx @@ -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} diff --git a/packages/excalidraw/components/dropdownMenu/DropdownMenu.scss b/packages/excalidraw/components/dropdownMenu/DropdownMenu.scss index a0a230941d..f6c7d7dc24 100644 --- a/packages/excalidraw/components/dropdownMenu/DropdownMenu.scss +++ b/packages/excalidraw/components/dropdownMenu/DropdownMenu.scss @@ -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; diff --git a/packages/excalidraw/components/dropdownMenu/DropdownMenuContent.tsx b/packages/excalidraw/components/dropdownMenu/DropdownMenuContent.tsx index 291f857e80..5bbb41763b 100644 --- a/packages/excalidraw/components/dropdownMenu/DropdownMenuContent.tsx +++ b/packages/excalidraw/components/dropdownMenu/DropdownMenuContent.tsx @@ -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 ? ( - {children} + + {children} + ) : ( ( height: "1px", backgroundColor: "var(--default-border-color)", margin: ".5rem 0", + flex: "0 0 auto", }} /> ); diff --git a/packages/excalidraw/components/main-menu/MainMenu.tsx b/packages/excalidraw/components/main-menu/MainMenu.tsx index 8ce2a5d69b..0098ebe526 100644 --- a/packages/excalidraw/components/main-menu/MainMenu.tsx +++ b/packages/excalidraw/components/main-menu/MainMenu.tsx @@ -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 ( @@ -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} setAppState({ openMenu: null })} onSelect={composeEventHandlers(onSelect, () => { setAppState({ openMenu: null }); })} diff --git a/packages/excalidraw/css/styles.scss b/packages/excalidraw/css/styles.scss index 679a5c4cd1..72890f206d 100644 --- a/packages/excalidraw/css/styles.scss +++ b/packages/excalidraw/css/styles.scss @@ -12,6 +12,11 @@ --zIndex-eyeDropperPreview: 6; --zIndex-hyperlinkContainer: 7; + --zIndex-ui-styles-popup: 40; + --zIndex-ui-bottom: 60; + --zIndex-ui-library: 80; + --zIndex-ui-top: 100; + --zIndex-modal: 1000; --zIndex-popup: 1001; --zIndex-toast: 999999; @@ -237,7 +242,7 @@ body.excalidraw-cursor-resize * { } .App-top-bar { - z-index: var(--zIndex-layerUI); + z-index: var(--zIndex-ui-top); display: flex; flex-direction: column; } @@ -255,7 +260,7 @@ body.excalidraw-cursor-resize * { left: 50%; transform: translateX(-50%); --bar-padding: calc(4 * var(--space-factor)); - z-index: var(--zIndex-layerUI); + z-index: var(--zIndex-ui-bottom); display: flex; flex-direction: column; @@ -296,6 +301,12 @@ body.excalidraw-cursor-resize * { .App-toolbar-content { display: flex; flex-direction: column; + + pointer-events: none; + + & > * { + pointer-events: var(--ui-pointerEvents); + } } .App-mobile-menu { diff --git a/packages/excalidraw/tests/__snapshots__/excalidraw.test.tsx.snap b/packages/excalidraw/tests/__snapshots__/excalidraw.test.tsx.snap index f1a65130ea..d1a1ef77e0 100644 --- a/packages/excalidraw/tests/__snapshots__/excalidraw.test.tsx.snap +++ b/packages/excalidraw/tests/__snapshots__/excalidraw.test.tsx.snap @@ -414,7 +414,7 @@ exports[` > Test UIOptions prop > Test canvasActions > should rende