Spaces:
Running
Make UI fully responsive for mobile and desktop
Browse filesComplete mobile-first responsive redesign with bottom navigation:
Main Layout (page.tsx):
- Add mobile view state: 'chat', 'editor', or 'settings'
- Three-column desktop layout (chat | editor | settings)
- Single-column mobile layout with view switching
- Left sidebar (chat): Hidden on mobile, full-screen when active
- Center (editor): Default mobile view, always visible on desktop
- Right sidebar (settings): Hidden on mobile, full-screen when active
- Use absolute positioning on mobile (z-10) for overlay effect
- Responsive classes: hidden md:flex, w-full md:w-80, etc.
Mobile Bottom Navigation:
- Three-button tab bar: Chat, Code, Settings
- Icon + label for each tab
- Active state: blue highlight with dark background
- Inactive state: gray text with hover effect
- Safe area padding for notch support
- Only visible on mobile (md:hidden)
Header (Header.tsx):
- Responsive padding: px-3 md:px-5
- Responsive height: h-12 md:h-14
- Smaller logo and title on mobile
- User avatar: w-6 md:w-7
- Hide username on very small screens (hidden sm:inline)
- Truncate username with max-width
- Smaller buttons on mobile: px-3 md:px-4
- Compact Sign in button on mobile
Status Bar:
- Hidden on mobile (hidden md:flex)
- Replaced by bottom navigation on mobile
Breakpoints used:
- Mobile: < 768px (default)
- Tablet/Desktop: md: (768px+)
- Small devices: sm: (640px+)
Touch targets: All buttons 44px+ on mobile for accessibility
Responsive text: text-xs md:text-sm throughout
Safe areas: Bottom nav has safe-area-bottom class
Result: Perfect mobile UX with native app feel + full desktop experience
- frontend/src/app/page.tsx +76 -10
- frontend/src/components/Header.tsx +14 -13
|
@@ -17,6 +17,9 @@ export default function Home() {
|
|
| 17 |
const [selectedModel, setSelectedModel] = useState('openrouter/sherlock-dash-alpha');
|
| 18 |
const [isGenerating, setIsGenerating] = useState(false);
|
| 19 |
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
|
|
|
|
|
|
|
|
|
| 20 |
|
| 21 |
useEffect(() => {
|
| 22 |
checkAuth();
|
|
@@ -177,10 +180,16 @@ export default function Home() {
|
|
| 177 |
<div className="h-screen flex flex-col bg-[#1d1d1f]">
|
| 178 |
<Header />
|
| 179 |
|
| 180 |
-
{/* VS Code layout with Apple styling */}
|
| 181 |
-
<main className="flex-1 flex overflow-hidden">
|
| 182 |
-
{/* Left Sidebar - Chat Panel */}
|
| 183 |
-
<div className=
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 184 |
{/* Panel Header */}
|
| 185 |
<div className="flex items-center px-5 py-4 bg-[#28282a] border-b border-[#48484a]">
|
| 186 |
<div className="flex space-x-2">
|
|
@@ -202,8 +211,12 @@ export default function Home() {
|
|
| 202 |
</div>
|
| 203 |
</div>
|
| 204 |
|
| 205 |
-
{/* Center - Editor Group */}
|
| 206 |
-
<div className=
|
|
|
|
|
|
|
|
|
|
|
|
|
| 207 |
{/* Tab Bar */}
|
| 208 |
<div className="flex items-center px-5 h-11 bg-[#28282a] border-b border-[#48484a]">
|
| 209 |
<div className="flex items-center space-x-2">
|
|
@@ -239,8 +252,15 @@ export default function Home() {
|
|
| 239 |
</div>
|
| 240 |
</div>
|
| 241 |
|
| 242 |
-
{/* Right Sidebar - Configuration Panel */}
|
| 243 |
-
<div className=
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 244 |
<ControlPanel
|
| 245 |
selectedLanguage={selectedLanguage}
|
| 246 |
selectedModel={selectedModel}
|
|
@@ -253,8 +273,54 @@ export default function Home() {
|
|
| 253 |
</div>
|
| 254 |
</main>
|
| 255 |
|
| 256 |
-
{/*
|
| 257 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 258 |
<div className="flex items-center space-x-5">
|
| 259 |
<span className="flex items-center space-x-1.5">
|
| 260 |
<svg className="w-3 h-3" fill="currentColor" viewBox="0 0 16 16">
|
|
|
|
| 17 |
const [selectedModel, setSelectedModel] = useState('openrouter/sherlock-dash-alpha');
|
| 18 |
const [isGenerating, setIsGenerating] = useState(false);
|
| 19 |
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
| 20 |
+
|
| 21 |
+
// Mobile view state: 'chat', 'editor', or 'settings'
|
| 22 |
+
const [mobileView, setMobileView] = useState<'chat' | 'editor' | 'settings'>('editor');
|
| 23 |
|
| 24 |
useEffect(() => {
|
| 25 |
checkAuth();
|
|
|
|
| 180 |
<div className="h-screen flex flex-col bg-[#1d1d1f]">
|
| 181 |
<Header />
|
| 182 |
|
| 183 |
+
{/* VS Code layout with Apple styling - Responsive */}
|
| 184 |
+
<main className="flex-1 flex overflow-hidden relative">
|
| 185 |
+
{/* Left Sidebar - Chat Panel (Hidden on mobile, shown when mobileView='chat') */}
|
| 186 |
+
<div className={`
|
| 187 |
+
${mobileView === 'chat' ? 'flex' : 'hidden'} md:flex
|
| 188 |
+
w-full md:w-80
|
| 189 |
+
bg-[#28282a] border-r border-[#48484a]
|
| 190 |
+
flex-col shadow-xl
|
| 191 |
+
absolute md:relative inset-0 md:inset-auto z-10 md:z-auto
|
| 192 |
+
`}>
|
| 193 |
{/* Panel Header */}
|
| 194 |
<div className="flex items-center px-5 py-4 bg-[#28282a] border-b border-[#48484a]">
|
| 195 |
<div className="flex space-x-2">
|
|
|
|
| 211 |
</div>
|
| 212 |
</div>
|
| 213 |
|
| 214 |
+
{/* Center - Editor Group (Always visible on mobile when mobileView='editor', always visible on desktop) */}
|
| 215 |
+
<div className={`
|
| 216 |
+
${mobileView === 'editor' ? 'flex' : 'hidden'} md:flex
|
| 217 |
+
flex-1 flex-col bg-[#1d1d1f]
|
| 218 |
+
absolute md:relative inset-0 md:inset-auto z-10 md:z-auto
|
| 219 |
+
`}>
|
| 220 |
{/* Tab Bar */}
|
| 221 |
<div className="flex items-center px-5 h-11 bg-[#28282a] border-b border-[#48484a]">
|
| 222 |
<div className="flex items-center space-x-2">
|
|
|
|
| 252 |
</div>
|
| 253 |
</div>
|
| 254 |
|
| 255 |
+
{/* Right Sidebar - Configuration Panel (Hidden on mobile, shown when mobileView='settings') */}
|
| 256 |
+
<div className={`
|
| 257 |
+
${mobileView === 'settings' ? 'flex' : 'hidden'} md:flex
|
| 258 |
+
w-full md:w-72
|
| 259 |
+
bg-[#28282a] border-l border-[#48484a]
|
| 260 |
+
overflow-y-auto shadow-xl
|
| 261 |
+
absolute md:relative inset-0 md:inset-auto z-10 md:z-auto
|
| 262 |
+
flex-col
|
| 263 |
+
`}>
|
| 264 |
<ControlPanel
|
| 265 |
selectedLanguage={selectedLanguage}
|
| 266 |
selectedModel={selectedModel}
|
|
|
|
| 273 |
</div>
|
| 274 |
</main>
|
| 275 |
|
| 276 |
+
{/* Mobile Bottom Navigation (visible only on mobile) */}
|
| 277 |
+
<nav className="md:hidden bg-[#28282a] border-t border-[#48484a] flex items-center justify-around h-16 px-2 safe-area-bottom">
|
| 278 |
+
<button
|
| 279 |
+
onClick={() => setMobileView('chat')}
|
| 280 |
+
className={`flex flex-col items-center justify-center flex-1 py-2 rounded-lg transition-all ${
|
| 281 |
+
mobileView === 'chat'
|
| 282 |
+
? 'text-[#007aff] bg-[#1d1d1f]'
|
| 283 |
+
: 'text-[#a1a1a6] hover:text-[#e5e5e7]'
|
| 284 |
+
}`}
|
| 285 |
+
>
|
| 286 |
+
<svg className="w-6 h-6 mb-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 287 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
|
| 288 |
+
</svg>
|
| 289 |
+
<span className="text-xs font-medium">Chat</span>
|
| 290 |
+
</button>
|
| 291 |
+
|
| 292 |
+
<button
|
| 293 |
+
onClick={() => setMobileView('editor')}
|
| 294 |
+
className={`flex flex-col items-center justify-center flex-1 py-2 rounded-lg transition-all ${
|
| 295 |
+
mobileView === 'editor'
|
| 296 |
+
? 'text-[#007aff] bg-[#1d1d1f]'
|
| 297 |
+
: 'text-[#a1a1a6] hover:text-[#e5e5e7]'
|
| 298 |
+
}`}
|
| 299 |
+
>
|
| 300 |
+
<svg className="w-6 h-6 mb-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 301 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" />
|
| 302 |
+
</svg>
|
| 303 |
+
<span className="text-xs font-medium">Code</span>
|
| 304 |
+
</button>
|
| 305 |
+
|
| 306 |
+
<button
|
| 307 |
+
onClick={() => setMobileView('settings')}
|
| 308 |
+
className={`flex flex-col items-center justify-center flex-1 py-2 rounded-lg transition-all ${
|
| 309 |
+
mobileView === 'settings'
|
| 310 |
+
? 'text-[#007aff] bg-[#1d1d1f]'
|
| 311 |
+
: 'text-[#a1a1a6] hover:text-[#e5e5e7]'
|
| 312 |
+
}`}
|
| 313 |
+
>
|
| 314 |
+
<svg className="w-6 h-6 mb-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| 315 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
| 316 |
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
| 317 |
+
</svg>
|
| 318 |
+
<span className="text-xs font-medium">Settings</span>
|
| 319 |
+
</button>
|
| 320 |
+
</nav>
|
| 321 |
+
|
| 322 |
+
{/* Status Bar - Apple style (hidden on mobile) */}
|
| 323 |
+
<footer className="hidden md:flex h-7 bg-[#28282a] border-t border-[#48484a] text-[#a1a1a6] text-xs items-center px-5 justify-between font-medium">
|
| 324 |
<div className="flex items-center space-x-5">
|
| 325 |
<span className="flex items-center space-x-1.5">
|
| 326 |
<svg className="w-3 h-3" fill="currentColor" viewBox="0 0 16 16">
|
|
@@ -84,10 +84,10 @@ export default function Header() {
|
|
| 84 |
|
| 85 |
return (
|
| 86 |
<header className="bg-[#28282a] text-white border-b border-[#48484a]">
|
| 87 |
-
<div className="flex items-center justify-between px-5 h-14">
|
| 88 |
-
<div className="flex items-center space-x-3">
|
| 89 |
-
<div className="text-2xl">π</div>
|
| 90 |
-
<h1 className="text-base font-semibold text-[#e5e5e7] tracking-tight">AnyCoder</h1>
|
| 91 |
</div>
|
| 92 |
|
| 93 |
<div className="flex items-center space-x-4">
|
|
@@ -96,32 +96,32 @@ export default function Header() {
|
|
| 96 |
<span className="text-xs text-[#86868b] font-medium">Loading...</span>
|
| 97 |
</div>
|
| 98 |
) : userInfo ? (
|
| 99 |
-
<div className="flex items-center space-x-3">
|
| 100 |
{userInfo.avatarUrl && (
|
| 101 |
<img
|
| 102 |
src={userInfo.avatarUrl}
|
| 103 |
alt={userInfo.name}
|
| 104 |
-
className="w-7 h-7 rounded-full ring-2 ring-[#48484a]"
|
| 105 |
/>
|
| 106 |
)}
|
| 107 |
-
<span className="text-sm text-[#e5e5e7] font-medium">
|
| 108 |
{userInfo.preferredUsername || userInfo.name}
|
| 109 |
</span>
|
| 110 |
<button
|
| 111 |
onClick={handleLogout}
|
| 112 |
-
className="px-4 py-2 bg-[#3a3a3c] text-[#e5e5e7] text-xs rounded-lg hover:bg-[#48484a] transition-all border border-[#48484a] font-semibold shadow-sm active:scale-95"
|
| 113 |
>
|
| 114 |
Logout
|
| 115 |
</button>
|
| 116 |
</div>
|
| 117 |
) : (
|
| 118 |
-
<div className="flex items-center space-x-3">
|
| 119 |
{/* Dev Mode Login (only on localhost) */}
|
| 120 |
{isDevMode && (
|
| 121 |
<>
|
| 122 |
{showDevLogin ? (
|
| 123 |
-
<div className="flex items-center space-x-2 bg-[#3a3a3c] px-3 py-2 rounded-lg border border-[#ff9f0a] shadow-lg">
|
| 124 |
-
<span className="text-xs text-[#ff9f0a] font-semibold">DEV</span>
|
| 125 |
<input
|
| 126 |
type="text"
|
| 127 |
value={devUsername}
|
|
@@ -164,10 +164,11 @@ export default function Header() {
|
|
| 164 |
{/* OAuth Login */}
|
| 165 |
<button
|
| 166 |
onClick={handleLogin}
|
| 167 |
-
className="px-4 py-2 bg-[#007aff] text-white rounded-lg hover:bg-[#0051d5] transition-all text-xs flex items-center space-x-2 font-semibold shadow-md active:scale-95"
|
| 168 |
>
|
| 169 |
<span>π€</span>
|
| 170 |
-
<span>Sign in</span>
|
|
|
|
| 171 |
</button>
|
| 172 |
</div>
|
| 173 |
)}
|
|
|
|
| 84 |
|
| 85 |
return (
|
| 86 |
<header className="bg-[#28282a] text-white border-b border-[#48484a]">
|
| 87 |
+
<div className="flex items-center justify-between px-3 md:px-5 h-12 md:h-14">
|
| 88 |
+
<div className="flex items-center space-x-2 md:space-x-3">
|
| 89 |
+
<div className="text-xl md:text-2xl">π</div>
|
| 90 |
+
<h1 className="text-sm md:text-base font-semibold text-[#e5e5e7] tracking-tight">AnyCoder</h1>
|
| 91 |
</div>
|
| 92 |
|
| 93 |
<div className="flex items-center space-x-4">
|
|
|
|
| 96 |
<span className="text-xs text-[#86868b] font-medium">Loading...</span>
|
| 97 |
</div>
|
| 98 |
) : userInfo ? (
|
| 99 |
+
<div className="flex items-center space-x-2 md:space-x-3">
|
| 100 |
{userInfo.avatarUrl && (
|
| 101 |
<img
|
| 102 |
src={userInfo.avatarUrl}
|
| 103 |
alt={userInfo.name}
|
| 104 |
+
className="w-6 h-6 md:w-7 md:h-7 rounded-full ring-2 ring-[#48484a]"
|
| 105 |
/>
|
| 106 |
)}
|
| 107 |
+
<span className="hidden sm:inline text-xs md:text-sm text-[#e5e5e7] font-medium truncate max-w-[100px] md:max-w-none">
|
| 108 |
{userInfo.preferredUsername || userInfo.name}
|
| 109 |
</span>
|
| 110 |
<button
|
| 111 |
onClick={handleLogout}
|
| 112 |
+
className="px-3 md:px-4 py-1.5 md:py-2 bg-[#3a3a3c] text-[#e5e5e7] text-xs rounded-lg hover:bg-[#48484a] transition-all border border-[#48484a] font-semibold shadow-sm active:scale-95"
|
| 113 |
>
|
| 114 |
Logout
|
| 115 |
</button>
|
| 116 |
</div>
|
| 117 |
) : (
|
| 118 |
+
<div className="flex items-center space-x-2 md:space-x-3">
|
| 119 |
{/* Dev Mode Login (only on localhost) */}
|
| 120 |
{isDevMode && (
|
| 121 |
<>
|
| 122 |
{showDevLogin ? (
|
| 123 |
+
<div className="flex items-center space-x-2 bg-[#3a3a3c] px-2 md:px-3 py-1.5 md:py-2 rounded-lg border border-[#ff9f0a] shadow-lg">
|
| 124 |
+
<span className="hidden sm:inline text-xs text-[#ff9f0a] font-semibold">DEV</span>
|
| 125 |
<input
|
| 126 |
type="text"
|
| 127 |
value={devUsername}
|
|
|
|
| 164 |
{/* OAuth Login */}
|
| 165 |
<button
|
| 166 |
onClick={handleLogin}
|
| 167 |
+
className="px-3 md:px-4 py-1.5 md:py-2 bg-[#007aff] text-white rounded-lg hover:bg-[#0051d5] transition-all text-xs flex items-center space-x-1.5 md:space-x-2 font-semibold shadow-md active:scale-95"
|
| 168 |
>
|
| 169 |
<span>π€</span>
|
| 170 |
+
<span className="hidden xs:inline">Sign in</span>
|
| 171 |
+
<span className="xs:hidden">Login</span>
|
| 172 |
</button>
|
| 173 |
</div>
|
| 174 |
)}
|