feat: add hybrid display for small and big devices.

This commit is contained in:
2026-04-20 16:57:44 +02:00
parent 8079162117
commit 3984b8684f

View File

@@ -1,108 +1,190 @@
import { useMemo, useState } from "react"; import { useMemo, useState } from "react";
import type { MouseEvent } from "react";
import { import {
AppBar,
Box, Box,
Button,
Drawer,
List, List,
ListItem, ListItem,
ListItemButton,
ListItemText, ListItemText,
Menu,
MenuItem,
Toolbar,
Typography Typography
} from "@mui/material"; } from "@mui/material";
import { TOOLS } from "./tools/toolRegistry"; import { TOOLS } from "./tools/toolRegistry";
const App = () => { const App = () => {
const [activeToolId, setActiveToolId] = useState<string>(TOOLS[0]?.id ?? ""); const [activeToolId, setActiveToolId] = useState<string>(TOOLS[0]?.id ?? "");
const [toolsAnchorEl, setToolsAnchorEl] = useState<null | HTMLElement>(null);
const [helpOpen, setHelpOpen] = useState(false);
const activeTool = useMemo( const activeTool = useMemo(
() => TOOLS.find((tool) => tool.id === activeToolId) ?? TOOLS[0], () => TOOLS.find((tool) => tool.id === activeToolId) ?? TOOLS[0],
[activeToolId] [activeToolId]
); );
const toolsMenuOpen = Boolean(toolsAnchorEl);
const openToolsMenu = (event: MouseEvent<HTMLButtonElement>) => {
setToolsAnchorEl(event.currentTarget);
};
const closeToolsMenu = () => {
setToolsAnchorEl(null);
};
const selectTool = (toolId: string) => {
setActiveToolId(toolId);
closeToolsMenu();
};
return ( return (
<Box <Box sx={{ minHeight: "100vh", bgcolor: "background.default" }}>
sx={{ <AppBar
minHeight: "100vh", position="sticky"
display: "grid", color="default"
gridTemplateColumns: { elevation={0}
xs: "1fr", sx={{ borderBottom: 1, borderColor: "divider", display: { xs: "block", md: "none" } }}
md: "240px minmax(0, 1fr)", >
lg: "240px minmax(0, 1fr) 360px" <Toolbar sx={{ gap: 1 }}>
}, <Button
gap: { xs: 2, md: 0 }, variant="text"
p: { xs: 2, md: 0 } color="inherit"
}} onClick={openToolsMenu}
> aria-controls={toolsMenuOpen ? "tools-menu" : undefined}
aria-haspopup="true"
aria-expanded={toolsMenuOpen ? "true" : undefined}
>
Tools
</Button>
<Typography sx={{ flexGrow: 1, fontWeight: 500 }} noWrap>
{activeTool?.name ?? "Tool"}
</Typography>
<Button variant="text" color="inherit" onClick={() => setHelpOpen(true)}>
Help
</Button>
</Toolbar>
</AppBar>
<Menu id="tools-menu" anchorEl={toolsAnchorEl} open={toolsMenuOpen} onClose={closeToolsMenu}>
{TOOLS.map((tool) => (
<MenuItem key={tool.id} selected={tool.id === activeTool?.id} onClick={() => selectTool(tool.id)}>
{tool.name}
</MenuItem>
))}
</Menu>
<Drawer anchor="right" open={helpOpen} onClose={() => setHelpOpen(false)}>
<Box sx={{ width: { xs: "85vw", sm: 360 }, p: 3 }}>
<Typography variant="h6" sx={{ mb: 1 }}>
Help
</Typography>
{activeTool?.helpContent ? (
<Box sx={{ color: "text.secondary", whiteSpace: "pre-wrap" }}>{activeTool.helpContent}</Box>
) : (
<Typography color="text.secondary">No help available for this tool.</Typography>
)}
<List sx={{ mt: 2 }}>
<ListItemButton onClick={() => setHelpOpen(false)}>
<ListItemText primary="Close" />
</ListItemButton>
</List>
</Box>
</Drawer>
<Box sx={{ display: { xs: "block", md: "none" }, p: 2 }}>{activeTool ? activeTool.render() : null}</Box>
<Box <Box
component="aside"
sx={{ sx={{
py: { xs: 0, md: 3 }, display: { xs: "none", md: "grid" },
pl: { xs: 0, md: 3 }, minHeight: "100vh",
pr: { xs: 0, md: 1 } gridTemplateColumns: {
md: "240px minmax(0, 1fr)",
lg: "240px minmax(0, 1fr) 360px"
},
gap: 0,
p: 0
}} }}
> >
<Typography variant="h6" sx={{ mb: 1 }}> <Box
Tools component="aside"
</Typography>
<List
disablePadding
sx={{ sx={{
bgcolor: "#fff", py: 3,
borderRadius: 1 pl: 3,
pr: 1
}} }}
> >
{TOOLS.map((tool) => ( <Typography variant="h6" sx={{ mb: 1 }}>
<ListItem key={tool.id} disablePadding> Tools
<Box </Typography>
role="option" <List
aria-selected={tool.id === activeTool?.id} disablePadding
tabIndex={0} sx={{
onClick={() => setActiveToolId(tool.id)} bgcolor: "#fff",
onKeyDown={(event) => { borderRadius: 1
if (event.key === "Enter" || event.key === " ") { }}
event.preventDefault(); >
setActiveToolId(tool.id); {TOOLS.map((tool) => (
} <ListItem key={tool.id} disablePadding>
}} <Box
sx={{ role="option"
width: "100%", aria-selected={tool.id === activeTool?.id}
px: 1.5, tabIndex={0}
py: 1, onClick={() => setActiveToolId(tool.id)}
cursor: "pointer", onKeyDown={(event) => {
borderLeft: 2, if (event.key === "Enter" || event.key === " ") {
borderColor: tool.id === activeTool?.id ? "primary.main" : "transparent" event.preventDefault();
}} setActiveToolId(tool.id);
>
<ListItemText
primary={tool.name}
slotProps={{
primary: {
sx: {
fontWeight: tool.id === activeTool?.id ? 600 : 400
}
} }
}} }}
/> sx={{
</Box> width: "100%",
</ListItem> px: 1.5,
))} py: 1,
</List> cursor: "pointer",
</Box> borderLeft: 2,
borderColor: tool.id === activeTool?.id ? "primary.main" : "transparent"
}}
>
<ListItemText
primary={tool.name}
slotProps={{
primary: {
sx: {
fontWeight: tool.id === activeTool?.id ? 600 : 400
}
}
}}
/>
</Box>
</ListItem>
))}
</List>
</Box>
<Box sx={{ minWidth: 0, p: { xs: 0, md: 3 } }}>{activeTool ? activeTool.render() : null}</Box> <Box sx={{ minWidth: 0, p: 3 }}>{activeTool ? activeTool.render() : null}</Box>
<Box <Box
component="aside" component="aside"
sx={{ sx={{
gridColumn: { xs: "1", md: "2", lg: "auto" }, gridColumn: { md: "2", lg: "auto" },
p: { xs: 2, md: 3 } p: 3
}} }}
> >
<Typography variant="h6" sx={{ mb: 1 }}> <Typography variant="h6" sx={{ mb: 1 }}>
Help Help
</Typography> </Typography>
{activeTool?.helpContent ? ( {activeTool?.helpContent ? (
<Box sx={{ color: "text.secondary", whiteSpace: "pre-wrap" }}>{activeTool.helpContent}</Box> <Box sx={{ color: "text.secondary", whiteSpace: "pre-wrap" }}>{activeTool.helpContent}</Box>
) : ( ) : (
<Typography color="text.secondary">No help available for this tool.</Typography> <Typography color="text.secondary">No help available for this tool.</Typography>
)} )}
</Box>
</Box> </Box>
</Box> </Box>
); );