Skip to content

Commit 6d918cb

Browse files
CopilotTechQuery
andcommitted
Add mobile responsive layout to dashboard pages
Co-authored-by: TechQuery <[email protected]>
1 parent 834672d commit 6d918cb

File tree

3 files changed

+239
-49
lines changed

3 files changed

+239
-49
lines changed

components/User/SessionBox.tsx

Lines changed: 132 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,34 @@
11
import { User } from '@idea2app/data-server';
2-
import { Box, List, ListItem, ListItemButton, ListItemText, Modal } from '@mui/material';
2+
import {
3+
Box,
4+
Drawer,
5+
IconButton,
6+
List,
7+
ListItem,
8+
ListItemButton,
9+
ListItemText,
10+
Modal,
11+
} from '@mui/material';
312
import { observable } from 'mobx';
413
import { observer } from 'mobx-react';
514
import Link from 'next/link';
615
import { JWTProps } from 'next-ssr-middleware';
716
import { Component, HTMLAttributes, JSX } from 'react';
817

9-
import { PageHead } from '../PageHead';
1018
import { SessionForm } from './SessionForm';
1119

20+
const MenuIcon = () => (
21+
<svg
22+
xmlns="http://www.w3.org/2000/svg"
23+
viewBox="0 0 24 24"
24+
fill="currentColor"
25+
width="24"
26+
height="24"
27+
>
28+
<path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z" />
29+
</svg>
30+
);
31+
1232
export type MenuItem = Pick<JSX.IntrinsicElements['a'], 'href' | 'title'>;
1333

1434
export interface SessionBoxProps extends HTMLAttributes<HTMLDivElement>, JWTProps<User> {
@@ -21,52 +41,135 @@ export class SessionBox extends Component<SessionBoxProps> {
2141
@observable
2242
accessor modalShown = false;
2343

44+
@observable
45+
accessor mobileMenuOpen = false;
46+
2447
componentDidMount() {
2548
this.modalShown = !this.props.jwtPayload;
2649
}
2750

51+
toggleMobileMenu = () => {
52+
this.mobileMenuOpen = !this.mobileMenuOpen;
53+
};
54+
55+
closeMobileMenu = () => {
56+
this.mobileMenuOpen = false;
57+
};
58+
59+
renderMenuItems() {
60+
const { path, menu = [] } = this.props;
61+
62+
return (
63+
<List component="nav" sx={{ px: 2 }}>
64+
{menu.map(({ href, title }) => (
65+
<ListItem key={href} disablePadding>
66+
<ListItemButton
67+
component={Link}
68+
href={href || '#'}
69+
selected={path?.split('?')[0].startsWith(href || '')}
70+
sx={{ borderRadius: 1 }}
71+
onClick={this.closeMobileMenu}
72+
>
73+
<ListItemText primary={title} />
74+
</ListItemButton>
75+
</ListItem>
76+
))}
77+
</List>
78+
);
79+
}
80+
2881
render() {
29-
const { className = '', title, children, path, menu = [], jwtPayload, ...props } = this.props;
82+
const { className = '', children, jwtPayload, ...props } = this.props;
3083

3184
return (
32-
<div className={`flex ${className}`} {...props}>
33-
<div>
34-
<List
35-
component="nav"
36-
className="sticky-top flex-col px-3"
37-
style={{ top: '5rem', minWidth: '200px' }}
85+
<Box
86+
sx={{
87+
display: 'flex',
88+
flexDirection: { xs: 'column', md: 'row' },
89+
}}
90+
className={className}
91+
{...props}
92+
>
93+
{/* Mobile Menu Button */}
94+
<Box
95+
sx={{
96+
display: { xs: 'flex', md: 'none' },
97+
position: 'sticky',
98+
top: 0,
99+
zIndex: 1100,
100+
bgcolor: 'background.paper',
101+
borderBottom: 1,
102+
borderColor: 'divider',
103+
p: 1,
104+
}}
105+
>
106+
<IconButton
107+
edge="start"
108+
color="inherit"
109+
aria-label="menu"
110+
onClick={this.toggleMobileMenu}
38111
>
39-
{menu.map(({ href, title }) => (
40-
<ListItem key={href} disablePadding>
41-
<ListItemButton
42-
component={Link}
43-
href={href || '#'}
44-
selected={path?.split('?')[0].startsWith(href || '')}
45-
className="rounded"
46-
>
47-
<ListItemText primary={title} />
48-
</ListItemButton>
49-
</ListItem>
50-
))}
51-
</List>
52-
</div>
53-
<main className="flex-1 pb-3">
112+
<MenuIcon />
113+
</IconButton>
114+
</Box>
115+
116+
{/* Mobile Drawer */}
117+
<Drawer
118+
anchor="left"
119+
open={this.mobileMenuOpen}
120+
sx={{ display: { xs: 'block', md: 'none' } }}
121+
onClose={this.closeMobileMenu}
122+
>
123+
<Box sx={{ width: 250 }}>{this.renderMenuItems()}</Box>
124+
</Drawer>
125+
126+
{/* Desktop Sidebar */}
127+
<Box
128+
sx={{
129+
display: { xs: 'none', md: 'block' },
130+
minWidth: 200,
131+
}}
132+
>
133+
<Box
134+
sx={{
135+
position: 'sticky',
136+
top: '5rem',
137+
}}
138+
>
139+
{this.renderMenuItems()}
140+
</Box>
141+
</Box>
142+
143+
{/* Main Content */}
144+
<Box
145+
component="main"
146+
sx={{
147+
flex: 1,
148+
pb: 3,
149+
px: { xs: 2, sm: 3 },
150+
}}
151+
>
54152
{children}
55153

56154
<Modal open={this.modalShown}>
57155
<Box
58-
className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 rounded p-4 shadow-lg"
59156
sx={{
60-
width: '400px',
61-
maxWidth: '90vw',
157+
position: 'absolute',
158+
top: '50%',
159+
left: '50%',
160+
transform: 'translate(-50%, -50%)',
161+
width: { xs: '90vw', sm: 400 },
62162
bgcolor: 'background.paper',
163+
borderRadius: 1,
164+
boxShadow: 24,
165+
p: 4,
63166
}}
64167
>
65168
<SessionForm onSignIn={() => (this.modalShown = false)} />
66169
</Box>
67170
</Modal>
68-
</main>
69-
</div>
171+
</Box>
172+
</Box>
70173
);
71174
}
72175
}

pages/dashboard/index.tsx

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,28 @@ const DashboardPage: FC<DashboardPageProps> = observer(({ route, jwtPayload }) =
3737

3838
return (
3939
<SessionBox title={t('backend_management')} path={route.resolvedUrl} {...{ menu, jwtPayload }}>
40-
<Container maxWidth="lg" className="py-8">
41-
<Typography variant="h3" component="h1" gutterBottom>
40+
<Container maxWidth="lg" sx={{ py: { xs: 3, md: 8 } }}>
41+
<Typography
42+
variant="h3"
43+
component="h1"
44+
gutterBottom
45+
sx={{
46+
fontSize: { xs: '1.75rem', sm: '2.5rem', md: '3rem' },
47+
}}
48+
>
4249
{t('welcome_use')}
4350
</Typography>
4451

4552
<Box
4653
component="form"
47-
sx={{ display: 'flex', gap: 2, alignItems: 'center', mt: 2, mb: 4 }}
54+
sx={{
55+
display: 'flex',
56+
flexDirection: { xs: 'column', sm: 'row' },
57+
gap: 2,
58+
alignItems: { xs: 'stretch', sm: 'center' },
59+
mt: 2,
60+
mb: 4,
61+
}}
4862
onSubmit={handleCreateProject}
4963
>
5064
<TextField
@@ -56,16 +70,27 @@ const DashboardPage: FC<DashboardPageProps> = observer(({ route, jwtPayload }) =
5670
defaultValue={route.query.name}
5771
/>
5872
<Button
59-
className="text-nowrap"
6073
type="submit"
6174
variant="contained"
6275
disabled={projectStore.uploading > 0}
76+
sx={{
77+
whiteSpace: 'nowrap',
78+
minWidth: { xs: '100%', sm: 'auto' },
79+
}}
6380
>
6481
{t('create_new_project')}
6582
</Button>
6683
</Box>
6784

68-
<Typography variant="h5" component="h2" sx={{ mt: 4, mb: 3 }}>
85+
<Typography
86+
variant="h5"
87+
component="h2"
88+
sx={{
89+
mt: 4,
90+
mb: 3,
91+
fontSize: { xs: '1.25rem', sm: '1.5rem' },
92+
}}
93+
>
6994
{t('recent_projects')}
7095
</Typography>
7196

@@ -76,10 +101,10 @@ const DashboardPage: FC<DashboardPageProps> = observer(({ route, jwtPayload }) =
76101
jwtPayload?.roles.includes(2 as UserRole.Client) ? { createdBy: jwtPayload.id } : {}
77102
}
78103
renderList={allItems => (
79-
<Grid container spacing={3}>
104+
<Grid container spacing={{ xs: 2, sm: 3 }}>
80105
{allItems[0] ? (
81106
allItems.map(project => (
82-
<Grid key={project.id} size={{ xs: 12, md: 4 }}>
107+
<Grid key={project.id} size={{ xs: 12, sm: 6, md: 4 }}>
83108
<ProjectCard {...project} />
84109
</Grid>
85110
))

0 commit comments

Comments
 (0)