feat: add hybrid display for small and big devices.
This commit is contained in:
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user