diff --git a/packages/excalidraw/components/MobileToolBar.scss b/packages/excalidraw/components/MobileToolBar.scss index a52872cf7..0fefa43ab 100644 --- a/packages/excalidraw/components/MobileToolBar.scss +++ b/packages/excalidraw/components/MobileToolBar.scss @@ -8,22 +8,18 @@ align-items: center; padding: 4px; gap: 4px; - max-width: 400px; border-radius: var(--space-factor); overflow-x: auto; scrollbar-width: none; -ms-overflow-style: none; justify-content: space-between; - @media screen and (min-width: 350px) { - gap: 8px; - padding: 4px 6px; + @media screen and (min-width: 340px) { + gap: 6px; } - // add a media query to increase the gaps on larger mobile devices - @media screen and (min-width: 400px) { - gap: 12px; - padding: 4px 8px; + @media screen and (min-width: 380px) { + gap: 8px; } } @@ -32,8 +28,8 @@ } .mobile-toolbar .ToolIcon { - min-width: 32px; - min-height: 32px; + min-width: 2rem; + min-height: 2rem; border-radius: 4px; display: flex; align-items: center; @@ -41,19 +37,19 @@ flex-shrink: 0; .ToolIcon__icon { - width: 32px; - height: 32px; + width: 2rem; + height: 2rem; } svg { - width: 16px; - height: 16px; + width: 1rem; + height: 1rem; } } - /* Reuse existing dropdown styles */ .mobile-toolbar .App-toolbar__extra-tools-dropdown { min-width: 160px; + z-index: var(--zIndex-layerUI); } .mobile-toolbar-separator { diff --git a/packages/excalidraw/components/MobileToolBar.tsx b/packages/excalidraw/components/MobileToolBar.tsx index bb93c2bed..1bb488b8f 100644 --- a/packages/excalidraw/components/MobileToolBar.tsx +++ b/packages/excalidraw/components/MobileToolBar.tsx @@ -1,15 +1,12 @@ -import React, { useState, useEffect } from "react"; +import { useState, useEffect } from "react"; import clsx from "clsx"; import { KEYS, capitalizeString } from "@excalidraw/common"; import { HandButton } from "./HandButton"; import { ToolButton } from "./ToolButton"; -import { ShapesSwitcher } from "./Actions"; import DropdownMenu from "./dropdownMenu/DropdownMenu"; -import { ShapeTypePopup } from "./ShapeTypePopup"; -import { SelectionTypePopup } from "./SelectionTypePopup"; -import { LinearElementTypePopup } from "./LinearElementTypePopup"; +import { ToolWithPopup } from "./ToolWithPopup"; import { SelectionIcon, @@ -36,26 +33,61 @@ import { t } from "../i18n"; import { isHandToolActive } from "../appState"; import { useTunnels } from "../context/tunnels"; -import type { ActionManager } from "../actions/manager"; -import type { AppClassProperties, AppProps, UIAppState } from "../types"; +import type { AppClassProperties, UIAppState } from "../types"; import "./ToolIcon.scss"; import "./MobileToolBar.scss"; +const SHAPE_TOOLS = [ + { + type: "rectangle", + icon: RectangleIcon, + title: capitalizeString(t("toolBar.rectangle")), + }, + { + type: "diamond", + icon: DiamondIcon, + title: capitalizeString(t("toolBar.diamond")), + }, + { + type: "ellipse", + icon: EllipseIcon, + title: capitalizeString(t("toolBar.ellipse")), + }, +] as const; + +const SELECTION_TOOLS = [ + { + type: "selection", + icon: SelectionIcon, + title: capitalizeString(t("toolBar.selection")), + }, + { + type: "lasso", + icon: LassoIcon, + title: capitalizeString(t("toolBar.lasso")), + }, +] as const; + +const LINEAR_ELEMENT_TOOLS = [ + { + type: "arrow", + icon: ArrowIcon, + title: capitalizeString(t("toolBar.arrow")), + }, + { type: "line", icon: LineIcon, title: capitalizeString(t("toolBar.line")) }, +] as const; + type MobileToolBarProps = { appState: UIAppState; app: AppClassProperties; - actionManager: ActionManager; onHandToolToggle: () => void; - UIOptions: AppProps["UIOptions"]; }; export const MobileToolBar = ({ appState, app, - actionManager, onHandToolToggle, - UIOptions, }: MobileToolBarProps) => { const activeTool = appState.activeTool; const [isOtherShapesMenuOpen, setIsOtherShapesMenuOpen] = useState(false); @@ -65,17 +97,6 @@ export const MobileToolBar = ({ const [lastActiveLinearElement, setLastActiveLinearElement] = useState< "arrow" | "line" >("arrow"); - const [isShapeTypePopupOpen, setIsShapeTypePopupOpen] = useState(false); - const [rectangleTriggerRef, setRectangleTriggerRef] = - useState(null); - const [isLinearElementTypePopupOpen, setIsLinearElementTypePopupOpen] = - useState(false); - const [linearElementTriggerRef, setLinearElementTriggerRef] = - useState(null); - const [isSelectionTypePopupOpen, setIsSelectionTypePopupOpen] = - useState(false); - const [selectionTriggerRef, setSelectionTriggerRef] = - useState(null); // keep lastActiveGenericShape in sync with active tool if user switches via other UI useEffect(() => { @@ -97,8 +118,6 @@ export const MobileToolBar = ({ const frameToolSelected = activeTool.type === "frame"; const laserToolSelected = activeTool.type === "laser"; - const lassoToolSelected = - activeTool.type === "lasso" && app.defaultSelectionTool !== "lasso"; const embeddableToolSelected = activeTool.type === "embeddable"; const { TTDDialogTriggerTunnel } = useTunnels(); @@ -119,6 +138,39 @@ export const MobileToolBar = ({ } }; + const showTextToolOutside = appState.width >= 400; + const showFrameToolOutside = appState.width >= 440; + + const extraTools = [ + "text", + "frame", + "embeddable", + "laser", + "magicframe", + ].filter((tool) => { + if (showTextToolOutside && tool === "text") { + return false; + } + if (showFrameToolOutside && tool === "frame") { + return false; + } + return true; + }); + const extraToolSelected = extraTools.includes(appState.activeTool.type); + const extraIcon = extraToolSelected + ? appState.activeTool.type === "frame" + ? frameToolIcon + : appState.activeTool.type === "embeddable" + ? EmbedIcon + : appState.activeTool.type === "laser" + ? laserPointerToolIcon + : appState.activeTool.type === "text" + ? TextIcon + : appState.activeTool.type === "magicframe" + ? MagicIcon + : extraToolsIcon + : extraToolsIcon; + return (
{/* Hand Tool */} @@ -130,41 +182,26 @@ export const MobileToolBar = ({ /> {/* Selection Tool */} -
-
- { - setIsSelectionTypePopupOpen((val) => !val); - app.setActiveTool({ type: app.defaultSelectionTool }); - }} - /> -
- setIsSelectionTypePopupOpen(false)} - onChange={(type) => { - app.setActiveTool({ type }); - app.defaultSelectionTool = type; - }} - currentType={activeTool.type === "lasso" ? "lasso" : "selection"} - /> -
+ { + app.setActiveTool({ type: type as any }); + app.defaultSelectionTool = type as any; + }} + getDisplayedOption={() => + SELECTION_TOOLS.find( + (tool) => tool.type === app.defaultSelectionTool, + ) || SELECTION_TOOLS[0] + } + isActive={(type: string) => type === "lasso" || type === "selection"} + /> {/* Free Draw */} {/* Rectangle */} -
setRectangleTriggerRef(el as HTMLElement | null)} - > + { + setLastActiveGenericShape(type as any); + app.setActiveTool({ type: type as any }); + }} + getDisplayedOption={() => + SHAPE_TOOLS.find((tool) => tool.type === lastActiveGenericShape) || + SHAPE_TOOLS[0] + } + isActive={(type: string) => + ["rectangle", "diamond", "ellipse"].includes(type) + } + /> + + {/* Arrow/Line */} + { + setLastActiveLinearElement(type as any); + app.setActiveTool({ type: type as any }); + }} + getDisplayedOption={() => + LINEAR_ELEMENT_TOOLS.find( + (tool) => tool.type === lastActiveLinearElement, + ) || LINEAR_ELEMENT_TOOLS[0] + } + isActive={(type: string) => ["arrow", "line"].includes(type)} + /> + + {/* Image */} + handleToolChange("image")} + /> + + {/* Text Tool */} + {showTextToolOutside && ( { - setIsShapeTypePopupOpen((val) => !val); - app.setActiveTool({ type: lastActiveGenericShape }); - }} + title={`${capitalizeString(t("toolBar.text"))}`} + aria-label={capitalizeString(t("toolBar.text"))} + data-testid="toolbar-text" + onChange={() => handleToolChange("text")} /> + )} - { - setIsShapeTypePopupOpen(false); - }} - onChange={(type) => { - setLastActiveGenericShape(type); - app.setActiveTool({ type }); - }} - currentType={activeTool.type} - /> -
- - {/* Arrow/Line */} -
setLinearElementTriggerRef(el as HTMLElement | null)} - > + {/* Frame Tool */} + {showFrameToolOutside && ( { - setIsLinearElementTypePopupOpen((val) => !val); - app.setActiveTool({ type: lastActiveLinearElement }); - }} + title={`${capitalizeString(t("toolBar.frame"))}`} + aria-label={capitalizeString(t("toolBar.frame"))} + data-testid="toolbar-frame" + onChange={() => handleToolChange("frame")} /> - - { - setIsLinearElementTypePopupOpen(false); - }} - onChange={(type) => { - setLastActiveLinearElement(type); - app.setActiveTool({ type }); - }} - currentType={activeTool.type === "line" ? "line" : "arrow"} - /> -
+ )} {/* Other Shapes */} setIsOtherShapesMenuOpen(!isOtherShapesMenuOpen)} title={t("toolBar.extraTools")} > - {frameToolSelected - ? frameToolIcon - : embeddableToolSelected - ? EmbedIcon - : activeTool.type === "text" - ? TextIcon - : activeTool.type === "image" - ? ImageIcon - : laserToolSelected && !app.props.isCollaborating - ? laserPointerToolIcon - : lassoToolSelected - ? LassoIcon - : extraToolsIcon} + {extraIcon} setIsOtherShapesMenuOpen(false)} onSelect={() => setIsOtherShapesMenuOpen(false)} className="App-toolbar__extra-tools-dropdown" > - app.setActiveTool({ type: "text" })} - icon={TextIcon} - shortcut={KEYS.T.toLocaleUpperCase()} - data-testid="toolbar-text" - selected={activeTool.type === "text"} - > - {t("toolBar.text")} - - app.setActiveTool({ type: "image" })} - icon={ImageIcon} - data-testid="toolbar-image" - selected={activeTool.type === "image"} - > - {t("toolBar.image")} - - app.setActiveTool({ type: "frame" })} - icon={frameToolIcon} - shortcut={KEYS.F.toLocaleUpperCase()} - data-testid="toolbar-frame" - selected={frameToolSelected} - > - {t("toolBar.frame")} - + {!showTextToolOutside && ( + app.setActiveTool({ type: "text" })} + icon={TextIcon} + shortcut={KEYS.T.toLocaleUpperCase()} + data-testid="toolbar-text" + selected={activeTool.type === "text"} + > + {t("toolBar.text")} + + )} + {!showFrameToolOutside && ( + app.setActiveTool({ type: "frame" })} + icon={frameToolIcon} + shortcut={KEYS.F.toLocaleUpperCase()} + data-testid="toolbar-frame" + selected={frameToolSelected} + > + {t("toolBar.frame")} + + )} app.setActiveTool({ type: "embeddable" })} icon={EmbedIcon} @@ -381,16 +389,6 @@ export const MobileToolBar = ({ > {t("toolBar.laser")} - {app.defaultSelectionTool !== "lasso" && ( - app.setActiveTool({ type: "lasso" })} - icon={LassoIcon} - data-testid="toolbar-lasso" - selected={lassoToolSelected} - > - {t("toolBar.lasso")} - - )}
Generate
@@ -416,14 +414,6 @@ export const MobileToolBar = ({ )}
- - {/* Separator */} -
- - {/* Undo Button */} -
- {actionManager.renderAction("undo")} -
); };