{field.label}
diff --git a/frontend/editor/src/core/components/tools/shared/OperationButton.tsx b/frontend/editor/src/core/components/tools/shared/OperationButton.tsx
index 3993953ea..afe56a5d1 100644
--- a/frontend/editor/src/core/components/tools/shared/OperationButton.tsx
+++ b/frontend/editor/src/core/components/tools/shared/OperationButton.tsx
@@ -91,7 +91,18 @@ const OperationButton = ({
color={color}
data-testid={dataTestId}
data-tour={dataTour}
- style={{ minHeight: "2.5rem", position: "relative" }}
+ style={{ minHeight: "2.5rem", height: "auto", position: "relative" }}
+ styles={{
+ label: {
+ whiteSpace: "normal",
+ textAlign: "center",
+ lineHeight: 1.2,
+ },
+ inner: {
+ paddingTop: "0.25rem",
+ paddingBottom: "0.25rem",
+ },
+ }}
>
{isLoading
? loadingText || t("loading", "Loading...")
diff --git a/frontend/editor/src/core/components/viewer/PdfViewerToolbar.tsx b/frontend/editor/src/core/components/viewer/PdfViewerToolbar.tsx
index ea71bb826..b3bab5195 100644
--- a/frontend/editor/src/core/components/viewer/PdfViewerToolbar.tsx
+++ b/frontend/editor/src/core/components/viewer/PdfViewerToolbar.tsx
@@ -4,11 +4,13 @@ import {
Button,
Paper,
Group,
+ Menu,
NumberInput,
Slider,
} from "@mantine/core";
import { useTranslation } from "react-i18next";
import { useViewer } from "@app/contexts/ViewerContext";
+import { useIsPhone } from "@app/hooks/useIsMobile";
import { Tooltip } from "@app/components/shared/Tooltip";
import FirstPageIcon from "@mui/icons-material/FirstPage";
import ArrowBackIosIcon from "@mui/icons-material/ArrowBackIos";
@@ -21,6 +23,7 @@ import WbSunnyIcon from "@mui/icons-material/WbSunny";
import WbTwilightIcon from "@mui/icons-material/WbTwilight";
import ZoomInIcon from "@mui/icons-material/ZoomIn";
import ZoomOutIcon from "@mui/icons-material/ZoomOut";
+import MoreVertIcon from "@mui/icons-material/MoreVert";
interface PdfViewerToolbarProps {
// Page navigation props (placeholders for now)
@@ -35,6 +38,9 @@ export function PdfViewerToolbar({
onPageChange,
}: PdfViewerToolbarProps) {
const { t } = useTranslation();
+ const isPhone = useIsPhone();
+ const buttonMinWidth = isPhone ? "3rem" : "2.5rem";
+ const buttonSize = isPhone ? "lg" : "md";
const {
getScrollState,
getZoomState,
@@ -141,41 +147,45 @@ export function PdfViewerToolbar({
style={{
display: "flex",
alignItems: "center",
+ flexWrap: "wrap",
+ rowGap: 8,
gap: 10,
+ justifyContent: "center",
borderTopLeftRadius: 16,
borderTopRightRadius: 16,
borderBottomLeftRadius: 0,
borderBottomRightRadius: 0,
boxShadow: "0 -2px 8px rgba(0,0,0,0.04)",
pointerEvents: "auto",
- minWidth: "30rem",
}}
>
{/* First Page Button */}
-
-
-
+ {!isPhone && (
+
+
+
+ )}
{/* Previous Page Button */}
@@ -212,137 +222,242 @@ export function PdfViewerToolbar({
{/* Last Page Button */}
-
-
-
+ {!isPhone && (
+
+
+
+ )}
{/* Dual Page Toggle */}
-
-
- {isDualPageActive ? (
-
- ) : (
-
- )}
-
-
+
+ {isDualPageActive ? (
+
+ ) : (
+
+ )}
+
+
+ )}
{/* PDF Render Mode Toggle */}
-
-
- {pdfRenderMode === "normal" && }
- {pdfRenderMode === "dark" && }
- {pdfRenderMode === "sepia" && }
-
-
+
+ {pdfRenderMode === "normal" && }
+ {pdfRenderMode === "dark" && }
+ {pdfRenderMode === "sepia" && }
+
+
+ )}
- {/* Zoom Controls */}
-
-
-
-
- zoomActions.setZoomLevel?.(val / 100)}
- size="xs"
- styles={{
- root: { width: "6rem" },
- thumb: { width: 14, height: 14 },
- track: { height: 3 },
- }}
- label={null}
- />
-
+
+
+ zoomActions.setZoomLevel?.(val / 100)}
+ size="xs"
+ styles={{
+ root: { minWidth: "6rem", width: "6rem", flexShrink: 0 },
+ thumb: { width: 14, height: 14 },
+ track: { height: 3 },
+ }}
+ label={null}
+ />
+
+
+
+
+ {displayZoomPercent}%
+
+
+ )}
+
+ {isPhone && (
+
-
-
-
- {displayZoomPercent}%
-
-
+
+
+
+
+
+
+
+ {t("viewer.pageNavigation", "Page navigation")}
+
+ }
+ disabled={scrollState.currentPage === 1}
+ onClick={handleFirstPage}
+ >
+ {t("viewer.firstPage", "First page")}
+
+ }
+ disabled={scrollState.currentPage === scrollState.totalPages}
+ onClick={handleLastPage}
+ >
+ {t("viewer.lastPage", "Last page")}
+
+
+
+ {t("viewer.zoom", "Zoom")}
+ }
+ onClick={handleZoomOut}
+ >
+ {t("viewer.zoomOut", "Zoom out")}
+
+ }
+ onClick={handleZoomIn}
+ >
+ {t("viewer.zoomIn", "Zoom in")} ({displayZoomPercent}%)
+
+
+
+ {t("viewer.view", "View")}
+
+ ) : (
+
+ )
+ }
+ disabled={scrollState.totalPages <= 1}
+ onClick={handleDualPageToggle}
+ >
+ {isDualPageActive
+ ? t("viewer.singlePageView", "Single Page View")
+ : t("viewer.dualPageView", "Dual Page View")}
+
+
+ ) : pdfRenderMode === "dark" ? (
+
+ ) : (
+
+ )
+ }
+ onClick={cyclePdfRenderMode}
+ >
+ {pdfRenderMode === "normal"
+ ? t("viewer.enableDarkFilter", "Enable Dark Filter")
+ : pdfRenderMode === "dark"
+ ? t("viewer.enableSepiaFilter", "Enable Sepia Filter")
+ : t("viewer.disableColorFilter", "Disable Color Filter")}
+
+
+
+ )}
);
}
diff --git a/frontend/editor/src/core/hooks/useIsMobile.ts b/frontend/editor/src/core/hooks/useIsMobile.ts
index 23d461041..a75ef7924 100644
--- a/frontend/editor/src/core/hooks/useIsMobile.ts
+++ b/frontend/editor/src/core/hooks/useIsMobile.ts
@@ -15,3 +15,11 @@ export const useIsMobile = (): boolean => {
export const useIsPhone = (): boolean => {
return useMediaQuery("(max-width: 768px)") ?? false;
};
+
+/**
+ * Custom hook to detect a coarse pointer (touch device)
+ * Use to gate touch-only UI hints when combined with useIsMobile
+ */
+export const useIsTouch = (): boolean => {
+ return useMediaQuery("(pointer: coarse)") ?? false;
+};
diff --git a/frontend/editor/src/core/pages/HomePage.tsx b/frontend/editor/src/core/pages/HomePage.tsx
index a589ed43b..f1e5158da 100644
--- a/frontend/editor/src/core/pages/HomePage.tsx
+++ b/frontend/editor/src/core/pages/HomePage.tsx
@@ -5,7 +5,7 @@ import { Group } from "@mantine/core";
import { useSidebarContext } from "@app/contexts/SidebarContext";
import { useDocumentMeta } from "@app/hooks/useDocumentMeta";
import { useBaseUrl } from "@app/hooks/useBaseUrl";
-import { useIsMobile } from "@app/hooks/useIsMobile";
+import { useIsMobile, useIsTouch } from "@app/hooks/useIsMobile";
import { useAppConfig } from "@app/contexts/AppConfigContext";
import { LogoIcon } from "@app/components/shared/LogoIcon";
import { Wordmark } from "@app/components/shared/Wordmark";
@@ -84,6 +84,7 @@ export default function HomePage() {
const navigate = useNavigate();
const { config } = useAppConfig();
const isMobile = useIsMobile();
+ const isTouch = useIsTouch();
const sliderRef = useRef(null);
const [activeMobileView, setActiveMobileView] = useState("tools");
const isProgrammaticScroll = useRef(false);
@@ -352,12 +353,14 @@ export default function HomePage() {
{t("home.mobile.workspace", "Workspace")}