Skip to content

Commit 4d7ba60

Browse files
authored
🤖 Add mobile-responsive sidebar with overlay and hamburger menu (#356)
1 parent d101c54 commit 4d7ba60

File tree

4 files changed

+162
-6
lines changed

4 files changed

+162
-6
lines changed

src/App.tsx

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,29 @@ const globalStyles = css`
5959
-moz-osx-font-smoothing: grayscale;
6060
}
6161
62+
/* Mobile: Improve touch interactions */
63+
@media (max-width: 768px) {
64+
html {
65+
/* Prevent text size adjustment on orientation change */
66+
-webkit-text-size-adjust: 100%;
67+
/* Improve tap responsiveness */
68+
touch-action: manipulation;
69+
}
70+
71+
body {
72+
/* Slightly larger font for better readability on mobile */
73+
font-size: 15px;
74+
}
75+
76+
/* Make buttons and interactive elements easier to tap */
77+
button,
78+
a,
79+
[role="button"] {
80+
min-height: 44px;
81+
min-width: 44px;
82+
}
83+
}
84+
6285
code {
6386
font-family: var(--font-monospace);
6487
}
@@ -135,19 +158,35 @@ const AppContainer = styled.div`
135158
height: 100vh;
136159
overflow: hidden;
137160
background: #1e1e1e;
161+
162+
/* Mobile: Ensure content takes full width */
163+
@media (max-width: 768px) {
164+
flex-direction: column;
165+
}
138166
`;
139167

140168
const MainContent = styled.div`
141169
flex: 1;
142170
display: flex;
143171
flex-direction: column;
144172
overflow: hidden;
173+
min-width: 0; /* Allow content to shrink below its minimum content size */
174+
175+
/* Mobile: Take full width */
176+
@media (max-width: 768px) {
177+
width: 100%;
178+
}
145179
`;
146180

147181
const ContentArea = styled.div`
148182
flex: 1;
149183
display: flex;
150184
overflow: hidden;
185+
186+
/* Mobile: Stack content vertically if needed */
187+
@media (max-width: 768px) {
188+
flex-direction: column;
189+
}
151190
`;
152191

153192
const WelcomeView = styled.div`
@@ -186,7 +225,10 @@ function AppInner() {
186225
);
187226
const [workspaceModalLoadError, setWorkspaceModalLoadError] = useState<string | null>(null);
188227
const workspaceModalProjectRef = useRef<string | null>(null);
189-
const [sidebarCollapsed, setSidebarCollapsed] = usePersistedState("sidebarCollapsed", false);
228+
229+
// Auto-collapse sidebar on mobile by default
230+
const isMobile = typeof window !== "undefined" && window.innerWidth <= 768;
231+
const [sidebarCollapsed, setSidebarCollapsed] = usePersistedState("sidebarCollapsed", isMobile);
190232

191233
const handleToggleSidebar = useCallback(() => {
192234
setSidebarCollapsed((prev) => !prev);

src/components/AIView.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,24 @@ const ViewContainer = styled.div`
4141
overflow-x: auto;
4242
overflow-y: hidden;
4343
container-type: inline-size;
44+
45+
/* Mobile: Stack vertically */
46+
@media (max-width: 768px) {
47+
flex-direction: column;
48+
}
4449
`;
4550

4651
const ChatArea = styled.div`
4752
flex: 1;
4853
min-width: 400px; /* Reduced from 750px to allow narrower layout when Review panel is wide */
4954
display: flex;
5055
flex-direction: column;
56+
57+
/* Mobile: Remove min-width and take full width */
58+
@media (max-width: 768px) {
59+
min-width: 0;
60+
width: 100%;
61+
}
5162
`;
5263

5364
const ViewHeader = styled.div`
@@ -57,6 +68,13 @@ const ViewHeader = styled.div`
5768
display: flex;
5869
justify-content: space-between;
5970
align-items: center;
71+
72+
/* Mobile: Add padding for hamburger button and adjust spacing */
73+
@media (max-width: 768px) {
74+
padding: 8px 15px 8px 60px; /* Extra left padding for hamburger button */
75+
flex-wrap: wrap;
76+
gap: 8px;
77+
}
6078
`;
6179

6280
const WorkspaceTitle = styled.div`

src/components/LeftSidebar.tsx

Lines changed: 91 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,71 @@ const LeftSidebarContainer = styled.div<{ collapsed?: boolean }>`
1717
flex-shrink: 0;
1818
transition: width 0.2s ease;
1919
overflow: hidden;
20+
position: relative;
21+
z-index: 100;
22+
23+
/* Mobile: Sidebar becomes overlay */
24+
@media (max-width: 768px) {
25+
position: fixed;
26+
left: 0;
27+
top: 0;
28+
width: 280px;
29+
z-index: 1000;
30+
transform: ${(props) => (props.collapsed ? "translateX(-100%)" : "translateX(0)")};
31+
transition: transform 0.3s ease;
32+
box-shadow: ${(props) => (props.collapsed ? "none" : "2px 0 8px rgba(0, 0, 0, 0.5)")};
33+
}
34+
`;
35+
36+
const Overlay = styled.div<{ visible: boolean }>`
37+
display: none;
38+
39+
/* Mobile: Show overlay backdrop when sidebar is open */
40+
@media (max-width: 768px) {
41+
display: ${(props) => (props.visible ? "block" : "none")};
42+
position: fixed;
43+
top: 0;
44+
left: 0;
45+
right: 0;
46+
bottom: 0;
47+
background: rgba(0, 0, 0, 0.5);
48+
z-index: 999;
49+
backdrop-filter: blur(2px);
50+
}
51+
`;
52+
53+
const HamburgerButton = styled.button`
54+
display: none;
55+
56+
/* Mobile: Show hamburger menu */
57+
@media (max-width: 768px) {
58+
display: flex;
59+
position: fixed;
60+
top: 12px;
61+
left: 12px;
62+
z-index: 998;
63+
width: 40px;
64+
height: 40px;
65+
background: #252526;
66+
border: 1px solid #3c3c3c;
67+
border-radius: 6px;
68+
cursor: pointer;
69+
align-items: center;
70+
justify-content: center;
71+
color: #cccccc;
72+
font-size: 20px;
73+
transition: all 0.2s;
74+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
75+
76+
&:hover {
77+
background: #2a2a2b;
78+
border-color: #4c4c4c;
79+
}
80+
81+
&:active {
82+
transform: scale(0.95);
83+
}
84+
}
2085
`;
2186

2287
interface LeftSidebarProps {
@@ -46,12 +111,33 @@ interface LeftSidebarProps {
46111
}
47112

48113
export function LeftSidebar(props: LeftSidebarProps) {
49-
const { collapsed, ...projectSidebarProps } = props;
114+
const { collapsed, onToggleCollapsed, ...projectSidebarProps } = props;
50115

51116
return (
52-
<LeftSidebarContainer collapsed={collapsed}>
53-
{!collapsed && <TitleBar />}
54-
<ProjectSidebar {...projectSidebarProps} collapsed={collapsed} />
55-
</LeftSidebarContainer>
117+
<>
118+
{/* Hamburger menu button - only visible on mobile */}
119+
{collapsed && (
120+
<HamburgerButton
121+
onClick={onToggleCollapsed}
122+
title="Open sidebar"
123+
aria-label="Open sidebar menu"
124+
>
125+
126+
</HamburgerButton>
127+
)}
128+
129+
{/* Overlay backdrop - only visible on mobile when sidebar is open */}
130+
<Overlay visible={!collapsed} onClick={onToggleCollapsed} />
131+
132+
{/* Sidebar */}
133+
<LeftSidebarContainer collapsed={collapsed}>
134+
{!collapsed && <TitleBar />}
135+
<ProjectSidebar
136+
{...projectSidebarProps}
137+
collapsed={collapsed}
138+
onToggleCollapsed={onToggleCollapsed}
139+
/>
140+
</LeftSidebarContainer>
141+
</>
56142
);
57143
}

src/components/RightSidebar.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,16 @@ const SidebarContainer = styled.div<SidebarContainerStyleProps>`
5454
z-index: 10;
5555
box-shadow: -2px 0 4px rgba(0, 0, 0, 0.2);
5656
`}
57+
58+
/* Mobile: Full width when expanded, hide when collapsed */
59+
@media (max-width: 768px) {
60+
width: ${(props) => (props.collapsed ? "0" : "100%")};
61+
border-left: none;
62+
border-top: 1px solid #3e3e42;
63+
max-height: ${(props) => (props.collapsed ? "0" : "50vh")};
64+
position: ${(props) => (props.collapsed ? "absolute" : "relative")};
65+
bottom: ${(props) => (props.collapsed ? "0" : "auto")};
66+
}
5767
`;
5868

5969
const FullView = styled.div<{ visible: boolean }>`

0 commit comments

Comments
 (0)