Skip to content

Commit bb16850

Browse files
CopilotTechQuery
andauthored
[optimize] Mobile responsive design for Dashboard routes (#92)
Co-authored-by: South Drifter <[email protected]>
1 parent 2624b5c commit bb16850

File tree

5 files changed

+150
-77
lines changed

5 files changed

+150
-77
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { Drawer, useMediaQuery, useTheme } from '@mui/material';
2+
import { FC, PropsWithChildren } from 'react';
3+
4+
const DESKTOP_DRAWER_STYLES = {
5+
position: 'sticky',
6+
top: '5rem',
7+
height: 'calc(100vh - 5rem)',
8+
border: 'none',
9+
boxShadow: 'none',
10+
} as const;
11+
12+
export interface ResponsiveDrawerProps extends PropsWithChildren {
13+
open: boolean;
14+
onClose: () => void;
15+
}
16+
17+
export const ResponsiveDrawer: FC<ResponsiveDrawerProps> = ({ open, onClose, children }) => {
18+
const theme = useTheme();
19+
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
20+
21+
return (
22+
<Drawer
23+
anchor="left"
24+
open={isMobile ? open : true}
25+
variant={isMobile ? 'temporary' : 'permanent'}
26+
onClose={onClose}
27+
sx={{
28+
display: { xs: isMobile ? 'block' : 'none', md: isMobile ? 'none' : 'block' },
29+
'& .MuiDrawer-paper': {
30+
width: 250,
31+
...(isMobile ? {} : DESKTOP_DRAWER_STYLES),
32+
},
33+
}}
34+
>
35+
{children}
36+
</Drawer>
37+
);
38+
};

components/User/SessionBox.tsx

Lines changed: 61 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,21 @@
11
import { User } from '@idea2app/data-server';
2-
import { Box, List, ListItem, ListItemButton, ListItemText, Modal } from '@mui/material';
2+
import {
3+
Box,
4+
IconButton,
5+
List,
6+
ListItem,
7+
ListItemButton,
8+
ListItemText,
9+
Modal,
10+
} from '@mui/material';
311
import { observable } from 'mobx';
412
import { observer } from 'mobx-react';
513
import Link from 'next/link';
614
import { JWTProps } from 'next-ssr-middleware';
715
import { Component, HTMLAttributes, JSX } from 'react';
816

9-
import { PageHead } from '../PageHead';
17+
import { SymbolIcon } from '../Icon';
18+
import { ResponsiveDrawer } from './ResponsiveDrawer';
1019
import { SessionForm } from './SessionForm';
1120

1221
export type MenuItem = Pick<JSX.IntrinsicElements['a'], 'href' | 'title'>;
@@ -21,46 +30,69 @@ export class SessionBox extends Component<SessionBoxProps> {
2130
@observable
2231
accessor modalShown = false;
2332

33+
@observable
34+
accessor drawerOpen = false;
35+
2436
componentDidMount() {
2537
this.modalShown = !this.props.jwtPayload;
2638
}
2739

40+
toggleDrawer = () => (this.drawerOpen = !this.drawerOpen);
41+
42+
closeDrawer = () => (this.drawerOpen = false);
43+
44+
renderMenuItems() {
45+
const { path, menu = [] } = this.props;
46+
47+
return (
48+
<List component="nav" className="px-2">
49+
{menu.map(({ href, title }) => (
50+
<ListItem key={href} disablePadding>
51+
<ListItemButton
52+
component={Link}
53+
href={href || '#'}
54+
selected={path?.split('?')[0].startsWith(href || '')}
55+
className="rounded"
56+
onClick={this.closeDrawer}
57+
>
58+
<ListItemText primary={title} />
59+
</ListItemButton>
60+
</ListItem>
61+
))}
62+
</List>
63+
);
64+
}
65+
2866
render() {
29-
const { className = '', title, children, path, menu = [], jwtPayload, ...props } = this.props;
67+
const { className = '', children, jwtPayload, ...props } = this.props;
3068

3169
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' }}
70+
<div className={`flex flex-col md:flex-row ${className}`} {...props}>
71+
{/* Mobile Menu Button */}
72+
<div className="sticky top-0 z-[1100] flex border-b p-1 md:hidden bg-background-paper border-divider">
73+
<IconButton
74+
edge="start"
75+
color="inherit"
76+
aria-label="menu"
77+
onClick={this.toggleDrawer}
3878
>
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>
79+
<SymbolIcon name="menu" />
80+
</IconButton>
5281
</div>
53-
<main className="flex-1 pb-3">
82+
83+
{/* Unified Responsive Drawer */}
84+
<ResponsiveDrawer open={this.drawerOpen} onClose={this.closeDrawer}>
85+
<div className="w-[250px]">{this.renderMenuItems()}</div>
86+
</ResponsiveDrawer>
87+
88+
{/* Main Content */}
89+
<main className="flex-1 px-2 pb-3 sm:px-3">
5490
{children}
5591

5692
<Modal open={this.modalShown}>
5793
<Box
58-
className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 rounded p-4 shadow-lg"
59-
sx={{
60-
width: '400px',
61-
maxWidth: '90vw',
62-
bgcolor: 'background.paper',
63-
}}
94+
className="absolute left-1/2 top-1/2 w-[90vw] -translate-x-1/2 -translate-y-1/2 rounded p-4 shadow-2xl sm:w-[400px] bg-background-paper"
95+
sx={{ boxShadow: 24 }}
6496
>
6597
<SessionForm onSignIn={() => (this.modalShown = false)} />
6698
</Box>

next-env.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/// <reference types="next" />
22
/// <reference types="next/image-types/global" />
3-
import './.next/dev/types/routes.d.ts';
3+
import "./.next/dev/types/routes.d.ts";
44

55
// NOTE: This file should not be edited
66
// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.

pages/dashboard/index.tsx

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,18 @@ 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" className="py-3 md:py-8">
41+
<Typography
42+
variant="h3"
43+
component="h1"
44+
gutterBottom
45+
className="text-[1.75rem] sm:text-[2.5rem] md:text-[3rem]"
46+
>
4247
{t('welcome_use')}
4348
</Typography>
4449

45-
<Box
46-
component="form"
47-
sx={{ display: 'flex', gap: 2, alignItems: 'center', mt: 2, mb: 4 }}
50+
<form
51+
className="mb-4 mt-2 flex flex-col items-stretch gap-2 sm:flex-row sm:items-center"
4852
onSubmit={handleCreateProject}
4953
>
5054
<TextField
@@ -56,16 +60,20 @@ const DashboardPage: FC<DashboardPageProps> = observer(({ route, jwtPayload }) =
5660
defaultValue={route.query.name}
5761
/>
5862
<Button
59-
className="text-nowrap"
60-
type="submit"
63+
className="min-w-full whitespace-nowrap sm:min-w-0"
6164
variant="contained"
65+
type="submit"
6266
disabled={projectStore.uploading > 0}
6367
>
6468
{t('create_new_project')}
6569
</Button>
66-
</Box>
70+
</form>
6771

68-
<Typography variant="h5" component="h2" sx={{ mt: 4, mb: 3 }}>
72+
<Typography
73+
variant="h5"
74+
component="h2"
75+
className="mb-3 mt-4 text-[1.25rem] sm:text-[1.5rem]"
76+
>
6977
{t('recent_projects')}
7078
</Typography>
7179

@@ -76,16 +84,16 @@ const DashboardPage: FC<DashboardPageProps> = observer(({ route, jwtPayload }) =
7684
jwtPayload?.roles.includes(2 as UserRole.Client) ? { createdBy: jwtPayload.id } : {}
7785
}
7886
renderList={allItems => (
79-
<Grid container spacing={3}>
87+
<Grid container spacing={{ xs: 2, sm: 3 }}>
8088
{allItems[0] ? (
8189
allItems.map(project => (
82-
<Grid key={project.id} size={{ xs: 12, md: 4 }}>
90+
<Grid key={project.id} size={{ xs: 12, sm: 6, md: 4 }}>
8391
<ProjectCard {...project} />
8492
</Grid>
8593
))
8694
) : (
8795
<Grid size={{ xs: 12 }}>
88-
<Typography color="textSecondary" sx={{ textAlign: 'center', mt: 4 }}>
96+
<Typography color="textSecondary" className="mt-4 text-center">
8997
{t('no_project_data')}
9098
</Typography>
9199
</Grid>

pages/dashboard/project/[id].tsx

Lines changed: 30 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -80,44 +80,35 @@ export default class ProjectEvaluationPage extends ObservedComponent<
8080
const name = isBot ? `${t('ai_assistant')} 🤖` : createdBy?.name || 'User';
8181

8282
return (
83-
<Box
83+
<div
8484
key={id}
8585
id={index + 1 === length ? 'last-message' : undefined}
86-
sx={{
87-
display: 'flex',
88-
justifyContent: isBot ? 'flex-start' : 'flex-end',
89-
mb: 2,
90-
width: '100%',
91-
}}
86+
className={`mb-2 flex w-full ${isBot ? 'justify-start' : 'justify-end'}`}
9287
>
93-
<Box
94-
sx={{
95-
display: 'flex',
96-
flexDirection: isBot ? 'row' : 'row-reverse',
97-
alignItems: 'flex-start',
98-
maxWidth: '80%',
99-
gap: 1,
100-
}}
88+
<div
89+
className={`flex items-start gap-1 max-w-[95%] sm:max-w-[80%] ${isBot ? 'flex-row' : 'flex-row-reverse'}`}
10190
>
102-
<Avatar src={avatarSrc} alt={name} sx={{ width: 32, height: 32 }} />
91+
<Avatar src={avatarSrc} alt={name} className="h-7 w-7 sm:h-8 sm:w-8" />
10392
<Paper
10493
elevation={1}
94+
className="rounded-[16px_16px_4px_16px] p-1.5 sm:p-2 bg-primary-light text-primary-contrast"
10595
sx={{
106-
p: 2,
10796
backgroundColor: 'primary.light',
10897
color: 'primary.contrastText',
109-
borderRadius: '16px 16px 4px 16px',
11098
}}
11199
>
112-
<Typography variant="caption" display="block" sx={{ mb: 0.5, opacity: 0.8 }}>
100+
<Typography
101+
variant="caption"
102+
display="block"
103+
className="mb-0.5 text-[0.7rem] opacity-80 sm:text-[0.75rem]"
104+
>
113105
{name}
114106
</Typography>
115107

116108
{content && (
117109
<Typography
118-
className="prose"
110+
className="prose mb-1 text-[0.875rem] sm:text-base"
119111
variant="body2"
120-
sx={{ mb: 1 }}
121112
dangerouslySetInnerHTML={{ __html: marked(content) }}
122113
/>
123114
)}
@@ -130,13 +121,13 @@ export default class ProjectEvaluationPage extends ObservedComponent<
130121
/>
131122
)}
132123
{createdAt && (
133-
<Typography variant="caption" sx={{ opacity: 0.6, fontSize: '0.75rem' }}>
124+
<Typography variant="caption" className="text-[0.65rem] opacity-60 sm:text-[0.75rem]">
134125
{new Date(createdAt).toLocaleTimeString()}
135126
</Typography>
136127
)}
137128
</Paper>
138-
</Box>
139-
</Box>
129+
</div>
130+
</div>
140131
);
141132
};
142133

@@ -154,31 +145,36 @@ export default class ProjectEvaluationPage extends ObservedComponent<
154145

155146
<Container
156147
maxWidth="md"
157-
sx={{ height: '85vh', display: 'flex', flexDirection: 'column', gap: '1rem' }}
148+
className="flex h-[calc(100vh-120px)] flex-col gap-2 px-0 sm:gap-4 sm:px-2 md:h-[85vh]"
158149
>
159-
<h1 className="mt-20 text-3xl font-bold">{title}</h1>
150+
<Typography
151+
component="h1"
152+
className="mb-1 mt-2 px-2 text-2xl font-bold sm:mb-2 sm:mt-4 sm:px-0 sm:text-3xl md:mt-20 md:text-5xl"
153+
>
154+
{title}
155+
</Typography>
160156
{/* Chat Messages Area */}
161-
<Box sx={{ flex: 1, overflow: 'auto', mb: 2 }}>
157+
<div className="mb-2 flex-1 overflow-auto">
162158
<ScrollList
163159
translator={i18n}
164160
store={messageStore}
165161
filter={{ project: projectId }}
166162
renderList={allItems => (
167-
<Box sx={{ height: '100%', overflowY: 'auto', p: 1 }}>
163+
<div className="h-full overflow-y-auto p-1 sm:p-2">
168164
{allItems.map(this.renderChatMessage)}
169-
</Box>
165+
</div>
170166
)}
171167
/>
172-
</Box>
168+
</div>
173169

174170
{/* Message Input Form */}
175171
<Paper
176172
component="form"
177173
elevation={1}
178-
sx={{ p: 2, mt: 'auto' }}
174+
className="mx-1 mb-1 mt-auto p-1.5 sm:mx-0 sm:mb-0 sm:p-2"
179175
onSubmit={this.handleMessageSubmit}
180176
>
181-
<Box sx={{ display: 'flex', gap: 1, alignItems: 'flex-end' }}>
177+
<div className="flex flex-col items-end gap-1 sm:flex-row">
182178
<TextField
183179
name="content"
184180
placeholder={t('type_your_message')}
@@ -191,14 +187,13 @@ export default class ProjectEvaluationPage extends ObservedComponent<
191187
/>
192188
<Button
193189
type="submit"
194-
className="text-nowrap"
195190
variant="contained"
196-
sx={{ minWidth: 'auto', px: 2 }}
191+
className="min-w-full whitespace-nowrap px-2 sm:min-w-0"
197192
disabled={messageStore.uploading > 0}
198193
>
199194
{t('send')}
200195
</Button>
201-
</Box>
196+
</div>
202197
</Paper>
203198
</Container>
204199
</SessionBox>

0 commit comments

Comments
 (0)