Fix mobile / desktop navigation

This commit is contained in:
James Ketr 2025-04-17 21:30:45 -07:00
parent 9722da5038
commit 8209f4f0f9
2 changed files with 159 additions and 66 deletions

View File

@ -1,5 +1,5 @@
div { div {
box-sizing: border-box box-sizing: border-box;
} }
.TabPanel { .TabPanel {
@ -68,22 +68,27 @@ div {
box-sizing: border-box; box-sizing: border-box;
overflow-x: visible; overflow-x: visible;
min-width: 10rem; min-width: 10rem;
width: 100%;
flex-grow: 1; flex-grow: 1;
} }
/* Prevent toolbar from shrinking vertically when media < 600px */ .MenuCard.MuiCard-root {
.MuiToolbar-root { display: flex;
min-height: 56px !important; flex-direction: column;
padding-left: 16px !important; min-width: 10rem;
padding-right: 16px !important; flex-grow: 1;
background-color: #1A2536; /* Midnight Blue */
color: #D3CDBF; /* Warm Gray */
border-radius: 0;
} }
@media (min-width: 768px) { .MenuCard.MuiCard-root button {
.Controls { min-height: 64px;
width: 600px; /* or whatever you prefer for a desktop */ }
max-width: 80vw; /* Optional: Prevent it from taking up too much space */ /* Prevent toolbar from shrinking vertically when media < 600px */
} .MuiToolbar-root {
min-height: 72px !important;
padding-left: 16px !important;
padding-right: 16px !important;
} }
.Conversation { .Conversation {

View File

@ -1,5 +1,7 @@
import React, { useState, useEffect, useRef, useCallback, ReactElement } from 'react'; import React, { useState, useEffect, useRef, useCallback, ReactElement } from 'react';
import useMediaQuery from '@mui/material/useMediaQuery';
import FormGroup from '@mui/material/FormGroup'; import FormGroup from '@mui/material/FormGroup';
import Card from '@mui/material/Card';
import FormControlLabel from '@mui/material/FormControlLabel'; import FormControlLabel from '@mui/material/FormControlLabel';
import { styled } from '@mui/material/styles'; import { styled } from '@mui/material/styles';
import Avatar from '@mui/material/Avatar'; import Avatar from '@mui/material/Avatar';
@ -21,13 +23,13 @@ import AppBar from '@mui/material/AppBar';
import Drawer from '@mui/material/Drawer'; import Drawer from '@mui/material/Drawer';
import Toolbar from '@mui/material/Toolbar'; import Toolbar from '@mui/material/Toolbar';
import SettingsIcon from '@mui/icons-material/Settings'; import SettingsIcon from '@mui/icons-material/Settings';
import CloseIcon from '@mui/icons-material/Close';
import IconButton from '@mui/material/IconButton'; import IconButton from '@mui/material/IconButton';
import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
import CssBaseline from '@mui/material/CssBaseline'; import CssBaseline from '@mui/material/CssBaseline';
import ResetIcon from '@mui/icons-material/History'; import ResetIcon from '@mui/icons-material/History';
import SendIcon from '@mui/icons-material/Send'; import SendIcon from '@mui/icons-material/Send';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import MenuIcon from '@mui/icons-material/Menu';
import PropagateLoader from "react-spinners/PropagateLoader"; import PropagateLoader from "react-spinners/PropagateLoader";
@ -320,8 +322,8 @@ const App = () => {
const [processing, setProcessing] = useState(false); const [processing, setProcessing] = useState(false);
const [sessionId, setSessionId] = useState<string | undefined>(undefined); const [sessionId, setSessionId] = useState<string | undefined>(undefined);
const [connectionBase,] = useState<string>(getConnectionBase(window.location)) const [connectionBase,] = useState<string>(getConnectionBase(window.location))
const [mobileOpen, setMobileOpen] = useState(false); const [menuOpen, setMenuOpen] = useState(false);
const [isClosing, setIsClosing] = useState(false); const [isMenuClosing, setIsMenuClosing] = useState(false);
const [snackOpen, setSnackOpen] = useState(false); const [snackOpen, setSnackOpen] = useState(false);
const [snackMessage, setSnackMessage] = useState(""); const [snackMessage, setSnackMessage] = useState("");
const [snackSeverity, setSnackSeverity] = useState<SeverityType>("success"); const [snackSeverity, setSnackSeverity] = useState<SeverityType>("success");
@ -343,6 +345,8 @@ const App = () => {
const [resume, setResume] = useState<MessageData | undefined>(undefined); const [resume, setResume] = useState<MessageData | undefined>(undefined);
const [facts, setFacts] = useState<MessageData | undefined>(undefined); const [facts, setFacts] = useState<MessageData | undefined>(undefined);
const timerRef = useRef<any>(null); const timerRef = useRef<any>(null);
const isDesktop = useMediaQuery('(min-width:600px)');
const prevIsDesktopRef = useRef<boolean>(isDesktop);
const startCountdown = (seconds: number) => { const startCountdown = (seconds: number) => {
if (timerRef.current) clearInterval(timerRef.current); if (timerRef.current) clearInterval(timerRef.current);
@ -400,6 +404,17 @@ const App = () => {
setSnackOpen(true); setSnackOpen(true);
}, []); }, []);
useEffect(() => {
if (prevIsDesktopRef.current === isDesktop)
return;
if (menuOpen) {
setMenuOpen(false);
}
prevIsDesktopRef.current = isDesktop;
}, [isDesktop, setMenuOpen, menuOpen])
// Get the system information // Get the system information
useEffect(() => { useEffect(() => {
if (systemInfo !== undefined || sessionId === undefined) { if (systemInfo !== undefined || sessionId === undefined) {
@ -810,28 +825,76 @@ const App = () => {
} }
}; };
const handleDrawerClose = () => { const handleMenuClose = () => {
setIsClosing(true); setIsMenuClosing(true);
setMobileOpen(false); setMenuOpen(false);
}; };
const handleDrawerTransitionEnd = () => { const handleMenuTransitionEnd = () => {
setIsClosing(false); setIsMenuClosing(false);
}; };
const handleDrawerToggle = () => { const handleMenuToggle = () => {
if (!isClosing) { if (!isMenuClosing) {
setMobileOpen(!mobileOpen); setMenuOpen(!menuOpen);
} }
}; };
const drawer = ( const settingsPanel = (
<> <>
{sessionId !== undefined && systemInfo !== undefined && {sessionId !== undefined && systemInfo !== undefined &&
<Controls {...{ messageHistoryLength, setMessageHistoryLength, tools, rags, reset, systemPrompt, toggleTool, toggleRag, setSystemPrompt, systemInfo }} />} <Controls {...{ messageHistoryLength, setMessageHistoryLength, tools, rags, reset, systemPrompt, toggleTool, toggleRag, setSystemPrompt, systemInfo }} />}
</> </>
); );
const handleTabChange = (event: React.SyntheticEvent, newValue: number) => {
setTab(newValue);
};
const menuDrawer = (
<Card className="MenuCard">
<Tabs sx={{ display: "flex", flexGrow: 1 }}
orientation="vertical"
value={tab}
indicatorColor="secondary"
textColor="inherit"
variant="scrollable"
allowScrollButtonsMobile
onChange={handleTabChange}
aria-label="Backstory navigation">
<Tab sx={{ fontSize: '1rem' }} label="Backstory"
value={0}
icon={
<Avatar sx={{
width: 24,
height: 24
}}
variant="rounded"
alt="Backstory logo"
src="/logo192.png" />
}
iconPosition="start" />
<Tab
value={1}
sx={{ fontSize: '1rem' }} wrapped
label="Resume Builder"
/>
<Tab
value={2}
sx={{ fontSize: '1rem' }} wrapped
label="Context Visualizer"
/>
<Tab
value={3}
sx={{ fontSize: '1rem' }} label="About" />
<Tab
value={4}
sx={{ fontSize: '1rem' }} icon={<SettingsIcon />} />
</Tabs>
</Card>
);
const submitQuery = (text: string) => { const submitQuery = (text: string) => {
sendQuery(text); sendQuery(text);
} }
@ -1037,10 +1100,6 @@ const App = () => {
setSnackOpen(false); setSnackOpen(false);
}; };
const handleTabChange = (event: React.SyntheticEvent, newValue: number) => {
setTab(newValue);
};
/* toolbar height is 56px + 8px margin-top */ /* toolbar height is 56px + 8px margin-top */
const Offset = styled('div')(({ theme }) => ({ ...theme.mixins.toolbar, minHeight: '64px', height: '64px' })); const Offset = styled('div')(({ theme }) => ({ ...theme.mixins.toolbar, minHeight: '64px', height: '64px' }));
@ -1056,45 +1115,67 @@ const App = () => {
> >
<Toolbar> <Toolbar>
{ {
mobileOpen === false && <Box sx={{ display: "flex", flexGrow: 1, flexDirection: "row" }}>
<Box sx={{ display: "flex", flexGrow: 1 }}> {
<Tabs value={tab} indicatorColor="secondary" !isDesktop &&
textColor="inherit"
variant="scrollable"
allowScrollButtonsMobile
onChange={handleTabChange} aria-label="Backstory navigation">
<Tab label="Backstory" icon={<Avatar sx={{ width: 24, height: 24 }} variant="rounded" alt="Backstory logo" src="/logo192.png" />} iconPosition="start" />
<Tab wrapped label="Resume Builder" />
<Tab wrapped label="Context Visualizer" />
<Tab label="About" />
</Tabs>
<Tooltip title="LLM Settings">
<IconButton <IconButton
sx={{ display: "flex", margin: 'auto 0px' }}
size="large"
edge="start"
color="inherit" color="inherit"
aria-label="open drawer" onClick={handleMenuToggle}
edge="end"
onClick={handleDrawerToggle}
> >
<SettingsIcon /> <Tooltip title="Navigation">
<MenuIcon />
</Tooltip>
</IconButton> </IconButton>
</Tooltip> }
{ menuOpen === false &&
<Tabs sx={{ display: "flex", flexGrow: 1 }}
value={tab}
indicatorColor="secondary"
textColor="inherit"
variant="fullWidth"
allowScrollButtonsMobile
onChange={handleTabChange}
aria-label="Backstory navigation">
<Tab sx={{ fontSize: '1rem' }} label="Backstory"
value={0}
icon={
<Avatar sx={{
width: 24,
height: 24
}}
variant="rounded"
alt="Backstory logo"
src="/logo192.png" />
}
iconPosition="start" />
{ isDesktop &&
<Tab
value={1}
sx={{ fontSize: '1rem' }} wrapped
label="Resume Builder"
/>
}
{isDesktop &&
<Tab
value={2}
sx={{ fontSize: '1rem' }} wrapped
label="Context Visualizer"
/>
}
<Tab
value={3}
sx={{ fontSize: '1rem' }} label="About" />
<Tab
value={4}
sx={{ flexShrink: 1, flexGrow: 0, fontSize: '1rem' }} icon={<SettingsIcon />}/>
</Tabs>
}
</Box> </Box>
} }
{
mobileOpen === true &&
<Tooltip title="Close Settings">
<IconButton
color="inherit"
aria-label="close drawer"
edge="end"
onClick={handleDrawerToggle}
sx={{ mr: 2, right: 0, position: "absolute" }}
>
<CloseIcon />
</IconButton>
</Tooltip>
}
</Toolbar> </Toolbar>
</AppBar> </AppBar>
@ -1106,13 +1187,12 @@ const App = () => {
component="nav" component="nav"
aria-label="mailbox folders" aria-label="mailbox folders"
> >
{/* The implementation can be swapped with js to avoid SEO duplication of links. */}
<Drawer <Drawer
container={window.document.body} container={window.document.body}
variant="temporary" variant="temporary"
open={mobileOpen} open={menuOpen}
onTransitionEnd={handleDrawerTransitionEnd} onTransitionEnd={handleMenuTransitionEnd}
onClose={handleDrawerClose} onClose={handleMenuClose}
sx={{ sx={{
display: 'block', display: 'block',
'& .MuiDrawer-paper': { boxSizing: 'border-box' }, '& .MuiDrawer-paper': { boxSizing: 'border-box' },
@ -1124,7 +1204,7 @@ const App = () => {
}} }}
> >
<Toolbar /> <Toolbar />
{drawer} {menuDrawer}
</Drawer> </Drawer>
</Box> </Box>
@ -1203,6 +1283,14 @@ const App = () => {
</Box> </Box>
</CustomTabPanel> </CustomTabPanel>
<CustomTabPanel tab={tab} index={4}>
<Box className="ChatBox">
<Box className="Conversation">
{ settingsPanel }
</Box>
</Box>
</CustomTabPanel>
</Box> </Box>
<Snackbar open={snackOpen} autoHideDuration={(snackSeverity === "success" || snackSeverity === "info") ? 1500 : 6000} onClose={handleSnackClose}> <Snackbar open={snackOpen} autoHideDuration={(snackSeverity === "success" || snackSeverity === "info") ? 1500 : 6000} onClose={handleSnackClose}>