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')} + +
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+ +
+ ) : ( + + + + + + + + )}
); }