From 7dbf529a451d0447215e53d6a4987c96deb08278 Mon Sep 17 00:00:00 2001
From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
Date: Tue, 30 Sep 2025 11:36:28 +0100
Subject: [PATCH] feat: add mobile slider layout for home page
---
frontend/src/components/tools/ToolPanel.tsx | 10 +-
frontend/src/pages/HomePage.css | 92 ++++++++++++
frontend/src/pages/HomePage.tsx | 153 ++++++++++++++++++--
3 files changed, 237 insertions(+), 18 deletions(-)
create mode 100644 frontend/src/pages/HomePage.css
diff --git a/frontend/src/components/tools/ToolPanel.tsx b/frontend/src/components/tools/ToolPanel.tsx
index 7f19482bd..beefc2c45 100644
--- a/frontend/src/components/tools/ToolPanel.tsx
+++ b/frontend/src/components/tools/ToolPanel.tsx
@@ -8,6 +8,7 @@ import { useSidebarContext } from "../../contexts/SidebarContext";
import rainbowStyles from '../../styles/rainbow.module.css';
import { ScrollArea } from '@mantine/core';
import { ToolId } from '../../types/toolId';
+import { useMediaQuery } from '@mantine/hooks';
// No props needed - component uses context
@@ -15,6 +16,7 @@ export default function ToolPanel() {
const { isRainbowMode } = useRainbowThemeContext();
const { sidebarRefs } = useSidebarContext();
const { toolPanelRef } = sidebarRefs;
+ const isMobile = useMediaQuery('(max-width: 1024px)');
// Use context-based hooks to eliminate prop drilling
@@ -34,17 +36,17 @@ export default function ToolPanel() {
* {
+ flex: 1 1 auto;
+ min-height: 0;
+}
diff --git a/frontend/src/pages/HomePage.tsx b/frontend/src/pages/HomePage.tsx
index 26d190dfa..68b65fb4c 100644
--- a/frontend/src/pages/HomePage.tsx
+++ b/frontend/src/pages/HomePage.tsx
@@ -1,15 +1,23 @@
+import { useCallback, useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { useToolWorkflow } from "../contexts/ToolWorkflowContext";
-import { Group } from "@mantine/core";
+import { ActionIcon, Group } from "@mantine/core";
import { useSidebarContext } from "../contexts/SidebarContext";
import { useDocumentMeta } from "../hooks/useDocumentMeta";
import { getBaseUrl } from "../constants/app";
+import { useMediaQuery } from "@mantine/hooks";
import ToolPanel from "../components/tools/ToolPanel";
import Workbench from "../components/layout/Workbench";
import QuickAccessBar from "../components/shared/QuickAccessBar";
import RightRail from "../components/shared/RightRail";
import FileManager from "../components/FileManager";
+import LocalIcon from "../components/shared/LocalIcon";
+import { useFilesModalContext } from "../contexts/FilesModalContext";
+
+import "./HomePage.css";
+
+type MobileView = "tools" | "workbench";
export default function HomePage() {
@@ -22,6 +30,63 @@ export default function HomePage() {
const { selectedTool, selectedToolKey } = useToolWorkflow();
+ const { openFilesModal } = useFilesModalContext();
+ const isMobile = useMediaQuery("(max-width: 1024px)");
+ const sliderRef = useRef
(null);
+ const [activeMobileView, setActiveMobileView] = useState("tools");
+
+ const handleSelectMobileView = useCallback((view: MobileView) => {
+ setActiveMobileView(view);
+ }, []);
+
+ useEffect(() => {
+ if (isMobile) {
+ const container = sliderRef.current;
+ if (container) {
+ const offset = activeMobileView === "tools" ? 0 : container.clientWidth;
+ container.scrollTo({ left: offset, behavior: "smooth" });
+ }
+ return;
+ }
+
+ setActiveMobileView("tools");
+ const container = sliderRef.current;
+ if (container) {
+ container.scrollTo({ left: 0, behavior: "auto" });
+ }
+ }, [activeMobileView, isMobile]);
+
+ useEffect(() => {
+ if (!isMobile) return;
+
+ const container = sliderRef.current;
+ if (!container) return;
+
+ let animationFrame = 0;
+
+ const handleScroll = () => {
+ if (animationFrame) {
+ cancelAnimationFrame(animationFrame);
+ }
+
+ animationFrame = window.requestAnimationFrame(() => {
+ const { scrollLeft, offsetWidth } = container;
+ const threshold = offsetWidth / 2;
+ const nextView: MobileView = scrollLeft >= threshold ? "workbench" : "tools";
+ setActiveMobileView((current) => (current === nextView ? current : nextView));
+ });
+ };
+
+ container.addEventListener("scroll", handleScroll, { passive: true });
+
+ return () => {
+ container.removeEventListener("scroll", handleScroll);
+ if (animationFrame) {
+ cancelAnimationFrame(animationFrame);
+ }
+ };
+ }, [isMobile]);
+
const baseUrl = getBaseUrl();
// Update document meta when tool changes
@@ -38,19 +103,79 @@ export default function HomePage() {
return (
-
-
-
-
-
-
-
+ {isMobile ? (
+
+
+
+
+ {selectedTool?.name || t('home.mobile.toolSettings', 'Tool settings')}
+
+
+
+
+
+
+
+
+ {t('home.mobile.swipeHint', 'Swipe left or right to switch views')}
+
+
+
+
+
+ ) : (
+
+
+
+
+
+
+
+ )}
);
}