fix: improve mobile navigation and interaction

- Fix mobile view toggle buttons not working when clicked (use offsetWidth instead of clientWidth)
- Add flag to prevent scroll handler interference during programmatic scrolls
- Move Files button from header to bottom navigation bar
- Add bottom navigation bar with All Tools, Automate, and Files buttons
- Add RightRail to mobile workspace view
- Auto-switch to Tools view when clicking All Tools or Automate in mobile
- Style bottom bar buttons as full-width clickable areas with labels

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Anthony Stirling 2025-10-01 12:22:00 +01:00
parent d854497266
commit 5d77373ef8
2 changed files with 93 additions and 12 deletions

View File

@ -116,3 +116,43 @@
flex: 1 1 auto;
min-height: 0;
}
.mobile-bottom-bar {
display: flex;
align-items: center;
justify-content: space-around;
padding: 0.5rem;
border-top: 1px solid var(--border-subtle);
background: var(--bg-toolbar);
gap: 0.5rem;
}
.mobile-bottom-button {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 0.25rem;
padding: 0.5rem;
border: none;
background: transparent;
color: var(--text-primary);
cursor: pointer;
border-radius: 0.5rem;
transition: background 0.2s ease;
}
.mobile-bottom-button:hover {
background: var(--bg-hover, rgba(0, 0, 0, 0.05));
}
.mobile-bottom-button:active {
background: var(--bg-active, rgba(0, 0, 0, 0.1));
}
.mobile-bottom-button-label {
font-size: 0.75rem;
font-weight: 500;
color: var(--text-muted);
}

View File

@ -1,11 +1,12 @@
import { useCallback, useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { useToolWorkflow } from "../contexts/ToolWorkflowContext";
import { ActionIcon, Group, useMantineColorScheme } from "@mantine/core";
import { Group, useMantineColorScheme } from "@mantine/core";
import { useSidebarContext } from "../contexts/SidebarContext";
import { useDocumentMeta } from "../hooks/useDocumentMeta";
import { BASE_PATH, getBaseUrl } from "../constants/app";
import { useMediaQuery } from "@mantine/hooks";
import AppsIcon from '@mui/icons-material/AppsRounded';
import ToolPanel from "../components/tools/ToolPanel";
import Workbench from "../components/layout/Workbench";
@ -28,13 +29,14 @@ export default function HomePage() {
const { quickAccessRef } = sidebarRefs;
const { selectedTool, selectedToolKey } = useToolWorkflow();
const { selectedTool, selectedToolKey, handleToolSelect, handleBackToTools } = useToolWorkflow();
const { openFilesModal } = useFilesModalContext();
const { colorScheme } = useMantineColorScheme();
const isMobile = useMediaQuery("(max-width: 1024px)");
const sliderRef = useRef<HTMLDivElement | null>(null);
const [activeMobileView, setActiveMobileView] = useState<MobileView>("tools");
const isProgrammaticScroll = useRef(false);
const brandName = t("home.mobile.brandName", "Stirling");
const brandAltText = t("home.mobile.brandAlt", "Stirling PDF logo");
@ -50,8 +52,14 @@ export default function HomePage() {
if (isMobile) {
const container = sliderRef.current;
if (container) {
const offset = activeMobileView === "tools" ? 0 : container.clientWidth;
isProgrammaticScroll.current = true;
const offset = activeMobileView === "tools" ? 0 : container.offsetWidth;
container.scrollTo({ left: offset, behavior: "smooth" });
// Re-enable scroll listener after animation completes
setTimeout(() => {
isProgrammaticScroll.current = false;
}, 500);
}
return;
}
@ -72,6 +80,10 @@ export default function HomePage() {
let animationFrame = 0;
const handleScroll = () => {
if (isProgrammaticScroll.current) {
return;
}
if (animationFrame) {
cancelAnimationFrame(animationFrame);
}
@ -118,14 +130,6 @@ export default function HomePage() {
<img src={brandMarkSrc} alt={brandAltText} className="mobile-brand-mark" />
<span className="mobile-brand-name">{brandName}</span>
</div>
<ActionIcon
variant="subtle"
size="md"
aria-label={t('home.mobile.openFiles', 'Open files')}
onClick={() => openFilesModal()}
>
<LocalIcon icon="folder-rounded" width="1.25rem" height="1.25rem" />
</ActionIcon>
</div>
<div className="mobile-toggle-buttons" role="tablist" aria-label={t('home.mobile.viewSwitcher', 'Switch workspace view')}>
<button
@ -159,12 +163,49 @@ export default function HomePage() {
</div>
<div className="mobile-slide" aria-label={t('home.mobile.workbenchSlide', 'Workspace panel')}>
<div className="mobile-slide-content">
<div className="flex-1 min-h-0 flex flex-col">
<div className="flex-1 min-h-0 flex">
<Workbench />
<RightRail />
</div>
</div>
</div>
</div>
<div className="mobile-bottom-bar">
<button
className="mobile-bottom-button"
aria-label={t('quickAccess.allTools', 'All Tools')}
onClick={() => {
handleBackToTools();
if (isMobile) {
setActiveMobileView('tools');
}
}}
>
<AppsIcon sx={{ fontSize: '1.5rem' }} />
<span className="mobile-bottom-button-label">{t('quickAccess.allTools', 'All Tools')}</span>
</button>
<button
className="mobile-bottom-button"
aria-label={t('quickAccess.automate', 'Automate')}
onClick={() => {
handleToolSelect('automate');
if (isMobile) {
setActiveMobileView('tools');
}
}}
>
<LocalIcon icon="automation-outline" width="1.5rem" height="1.5rem" />
<span className="mobile-bottom-button-label">{t('quickAccess.automate', 'Automate')}</span>
</button>
<button
className="mobile-bottom-button"
aria-label={t('home.mobile.openFiles', 'Open files')}
onClick={() => openFilesModal()}
>
<LocalIcon icon="folder-rounded" width="1.5rem" height="1.5rem" />
<span className="mobile-bottom-button-label">{t('quickAccess.files', 'Files')}</span>
</button>
</div>
<FileManager selectedTool={selectedTool as any /* FIX ME */} />
</div>
) : (