feat: new mobile layout (#9996)

* compact bottom toolbar

* put menu trigger to top left

* add popup to switch between grouped tool types

* add a dedicated mobile toolbar

* update position for mobile

* fix active tool type

* add mobile mode as well

* mobile actions

* remove refactored popups

* excali logo mobile

* include mobile

* update mobile menu layout

* move selection and deletion back to right

* do not fill eraser

* fix styling

* fix active styling

* bigger buttons, smaller gaps

* fix other tools not opened

* fix: Style panel persistence and restore

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>

* move hidden action btns to extra popover

* fix dropdown overlapping with welcome screen

* replace custom popup with popover

* improve button styles

* swapping redo and delete

* always show undo & redo and improve styling

* change background

* toolbar styles

* no any

* persist perferred selection tool and align tablet as well

* add a renderTopLeftUI to props

* tweak border and bg

* show combined properties only when using suitable tools

* fix preferred tool

* new stroke icon

* hide color picker hot keys

* init preferred tool based on device

* fix main menu sizing

* fix welcome screen offset

* put text before image

* disable call highlight on buttons

* fix renderTopLeftUI

---------

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
Co-authored-by: Mark Tolmacs <mark@lazycat.hu>
Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
This commit is contained in:
Ryan Di
2025-10-10 08:48:31 +11:00
committed by GitHub
parent 98e0cd9078
commit 416e8b3e42
47 changed files with 2407 additions and 678 deletions

View File

@@ -348,7 +348,10 @@ export const actionChangeStrokeColor = register({
elements={elements}
appState={appState}
updateData={updateData}
compactMode={appState.stylesPanelMode === "compact"}
compactMode={
appState.stylesPanelMode === "compact" ||
appState.stylesPanelMode === "mobile"
}
/>
</>
),
@@ -428,7 +431,10 @@ export const actionChangeBackgroundColor = register({
elements={elements}
appState={appState}
updateData={updateData}
compactMode={appState.stylesPanelMode === "compact"}
compactMode={
appState.stylesPanelMode === "compact" ||
appState.stylesPanelMode === "mobile"
}
/>
</>
),
@@ -531,9 +537,7 @@ export const actionChangeStrokeWidth = register({
},
PanelComponent: ({ elements, appState, updateData, app, data }) => (
<fieldset>
{appState.stylesPanelMode === "full" && (
<legend>{t("labels.strokeWidth")}</legend>
)}
<legend>{t("labels.strokeWidth")}</legend>
<div className="buttonList">
<RadioSelection
group="stroke-width"
@@ -590,9 +594,7 @@ export const actionChangeSloppiness = register({
},
PanelComponent: ({ elements, appState, updateData, app, data }) => (
<fieldset>
{appState.stylesPanelMode === "full" && (
<legend>{t("labels.sloppiness")}</legend>
)}
<legend>{t("labels.sloppiness")}</legend>
<div className="buttonList">
<RadioSelection
group="sloppiness"
@@ -645,9 +647,7 @@ export const actionChangeStrokeStyle = register({
},
PanelComponent: ({ elements, appState, updateData, app, data }) => (
<fieldset>
{appState.stylesPanelMode === "full" && (
<legend>{t("labels.strokeStyle")}</legend>
)}
<legend>{t("labels.strokeStyle")}</legend>
<div className="buttonList">
<RadioSelection
group="strokeStyle"
@@ -776,7 +776,8 @@ export const actionChangeFontSize = register({
onChange={(value) => {
withCaretPositionPreservation(
() => updateData(value),
appState.stylesPanelMode === "compact",
appState.stylesPanelMode === "compact" ||
appState.stylesPanelMode === "mobile",
!!appState.editingTextElement,
data?.onPreventClose,
);
@@ -1040,7 +1041,7 @@ export const actionChangeFontFamily = register({
return result;
},
PanelComponent: ({ elements, appState, app, updateData, data }) => {
PanelComponent: ({ elements, appState, app, updateData }) => {
const cachedElementsRef = useRef<ElementsMap>(new Map());
const prevSelectedFontFamilyRef = useRef<number | null>(null);
// relying on state batching as multiple `FontPicker` handlers could be called in rapid succession and we want to combine them
@@ -1117,7 +1118,7 @@ export const actionChangeFontFamily = register({
}, []);
return (
<fieldset>
<>
{appState.stylesPanelMode === "full" && (
<legend>{t("labels.fontFamily")}</legend>
)}
@@ -1125,7 +1126,7 @@ export const actionChangeFontFamily = register({
isOpened={appState.openPopup === "fontFamily"}
selectedFontFamily={selectedFontFamily}
hoveredFontFamily={appState.currentHoveredFontFamily}
compactMode={appState.stylesPanelMode === "compact"}
compactMode={appState.stylesPanelMode !== "full"}
onSelect={(fontFamily) => {
withCaretPositionPreservation(
() => {
@@ -1137,7 +1138,8 @@ export const actionChangeFontFamily = register({
// defensive clear so immediate close won't abuse the cached elements
cachedElementsRef.current.clear();
},
appState.stylesPanelMode === "compact",
appState.stylesPanelMode === "compact" ||
appState.stylesPanelMode === "mobile",
!!appState.editingTextElement,
);
}}
@@ -1213,7 +1215,8 @@ export const actionChangeFontFamily = register({
// Refocus text editor when font picker closes if we were editing text
if (
appState.stylesPanelMode === "compact" &&
(appState.stylesPanelMode === "compact" ||
appState.stylesPanelMode === "mobile") &&
appState.editingTextElement
) {
restoreCaretPosition(null); // Just refocus without saved position
@@ -1221,7 +1224,7 @@ export const actionChangeFontFamily = register({
}
}}
/>
</fieldset>
</>
);
},
});
@@ -1314,7 +1317,8 @@ export const actionChangeTextAlign = register({
onChange={(value) => {
withCaretPositionPreservation(
() => updateData(value),
appState.stylesPanelMode === "compact",
appState.stylesPanelMode === "compact" ||
appState.stylesPanelMode === "mobile",
!!appState.editingTextElement,
data?.onPreventClose,
);
@@ -1413,7 +1417,8 @@ export const actionChangeVerticalAlign = register({
onChange={(value) => {
withCaretPositionPreservation(
() => updateData(value),
appState.stylesPanelMode === "compact",
appState.stylesPanelMode === "compact" ||
appState.stylesPanelMode === "mobile",
!!appState.editingTextElement,
data?.onPreventClose,
);
@@ -1678,8 +1683,8 @@ export const actionChangeArrowProperties = register({
PanelComponent: ({ elements, appState, updateData, app, renderAction }) => {
return (
<div className="selected-shape-actions">
{renderAction("changeArrowType")}
{renderAction("changeArrowhead")}
{renderAction("changeArrowType")}
</div>
);
},