Reubencf commited on
Commit
5ab4f0a
·
1 Parent(s): 33d698c

Add mobile-responsive design with landscape optimization

Browse files
app/components/Desktop.tsx CHANGED
@@ -286,7 +286,7 @@ export function Desktop() {
286
  }
287
 
288
  return (
289
- <div className="relative h-screen w-screen overflow-hidden flex" onContextMenu={handleDesktopRightClick}>
290
  <div
291
  className="absolute inset-0 transition-all duration-500 ease-in-out"
292
  style={getBackgroundStyle()}
@@ -315,8 +315,8 @@ export function Desktop() {
315
  <Dock onOpenFileManager={openFileManager} onOpenCalendar={openCalendar} onOpenClock={openClock} onOpenBrowser={openBrowser} onOpenGeminiChat={openGeminiChat} />
316
 
317
  <div className="flex-1 z-10 relative">
318
- {/* Desktop Icons - Positioned in a grid layout */}
319
- <div className="absolute top-16 left-6 grid grid-flow-col grid-rows-[repeat(auto-fill,100px)] gap-x-4 gap-y-2 h-[calc(100vh-140px)] w-full pointer-events-none">
320
  <div className="pointer-events-auto w-24 h-24">
321
  <DraggableDesktopIcon
322
  id="files"
 
286
  }
287
 
288
  return (
289
+ <div className="relative h-screen w-screen overflow-hidden flex touch-auto" onContextMenu={handleDesktopRightClick}>
290
  <div
291
  className="absolute inset-0 transition-all duration-500 ease-in-out"
292
  style={getBackgroundStyle()}
 
315
  <Dock onOpenFileManager={openFileManager} onOpenCalendar={openCalendar} onOpenClock={openClock} onOpenBrowser={openBrowser} onOpenGeminiChat={openGeminiChat} />
316
 
317
  <div className="flex-1 z-10 relative">
318
+ {/* Desktop Icons - Positioned in a grid layout - Hidden on mobile (use dock instead) */}
319
+ <div className="absolute top-16 left-6 grid grid-flow-col grid-rows-[repeat(auto-fill,100px)] gap-x-4 gap-y-2 h-[calc(100vh-140px)] w-full pointer-events-none hidden lg:grid">
320
  <div className="pointer-events-auto w-24 h-24">
321
  <DraggableDesktopIcon
322
  id="files"
app/components/Dock.tsx CHANGED
@@ -42,7 +42,7 @@ const DockItem: React.FC<DockItemProps> = ({ icon, label, onClick, isActive = fa
42
  onMouseEnter={() => setIsHovered(true)}
43
  onMouseLeave={() => setIsHovered(false)}
44
  onClick={onClick}
45
- className={`app-icon w-12 h-12 rounded-xl shadow-lg flex items-center justify-center cursor-pointer border transition-all ${className}`}
46
  title={label}
47
  >
48
  {icon}
@@ -68,7 +68,7 @@ export function Dock({
68
  {
69
  icon: (
70
  <div className="bg-gradient-to-br from-blue-400 to-cyan-200 w-full h-full rounded-xl flex items-center justify-center border border-white/30">
71
- <Folder size={28} weight="regular" className="text-blue-900" />
72
  </div>
73
  ),
74
  label: 'Files',
@@ -79,7 +79,7 @@ export function Dock({
79
  icon: (
80
  <div className="bg-white w-full h-full rounded-xl overflow-hidden relative flex items-center justify-center">
81
  <div className="absolute inset-0 bg-gradient-to-tr from-blue-500 to-cyan-300" />
82
- <Compass size={36} weight="light" className="text-white relative z-10" />
83
  </div>
84
  ),
85
  label: 'Browser',
@@ -89,7 +89,7 @@ export function Dock({
89
  {
90
  icon: (
91
  <div className="bg-white w-full h-full rounded-xl flex items-center justify-center border border-gray-200">
92
- <Sparkle size={28} weight="fill" className="text-blue-500" />
93
  </div>
94
  ),
95
  label: 'Gemini',
@@ -113,7 +113,7 @@ export function Dock({
113
  {
114
  icon: (
115
  <div className="bg-gradient-to-br from-purple-500 to-purple-600 w-full h-full rounded-xl flex items-center justify-center shadow-inner">
116
- <CalendarIcon size={28} weight="regular" className="text-white" />
117
  </div>
118
  ),
119
  label: 'Calendar',
@@ -125,7 +125,7 @@ export function Dock({
125
  return (
126
  <div className="dock-container">
127
  <div
128
- className="dock-glass px-3 pb-2 pt-3 rounded-2xl flex items-end gap-3 shadow-2xl border border-white/20 transition-all duration-300"
129
  onMouseMove={(e) => mouseX.current = e.pageX}
130
  onMouseLeave={() => mouseX.current = null}
131
  >
@@ -134,11 +134,11 @@ export function Dock({
134
  <DockItem {...item} mouseX={mouseX} />
135
  {index === dockItems.length - 1 && (
136
  <>
137
- <div className="w-px h-10 bg-gray-400/30 mx-1" />
138
  <DockItem
139
  icon={
140
  <div className="bg-gradient-to-b from-gray-200 to-gray-300 w-full h-full rounded-xl flex items-center justify-center border border-white/40">
141
- <Trash size={24} weight="regular" className="text-gray-600" />
142
  </div>
143
  }
144
  label="Trash"
 
42
  onMouseEnter={() => setIsHovered(true)}
43
  onMouseLeave={() => setIsHovered(false)}
44
  onClick={onClick}
45
+ className={`app-icon w-10 h-10 md:w-12 md:h-12 rounded-xl shadow-lg flex items-center justify-center cursor-pointer border transition-all ${className}`}
46
  title={label}
47
  >
48
  {icon}
 
68
  {
69
  icon: (
70
  <div className="bg-gradient-to-br from-blue-400 to-cyan-200 w-full h-full rounded-xl flex items-center justify-center border border-white/30">
71
+ <Folder size={24} weight="regular" className="text-blue-900 md:scale-110" />
72
  </div>
73
  ),
74
  label: 'Files',
 
79
  icon: (
80
  <div className="bg-white w-full h-full rounded-xl overflow-hidden relative flex items-center justify-center">
81
  <div className="absolute inset-0 bg-gradient-to-tr from-blue-500 to-cyan-300" />
82
+ <Compass size={28} weight="light" className="text-white relative z-10 md:scale-125" />
83
  </div>
84
  ),
85
  label: 'Browser',
 
89
  {
90
  icon: (
91
  <div className="bg-white w-full h-full rounded-xl flex items-center justify-center border border-gray-200">
92
+ <Sparkle size={24} weight="fill" className="text-blue-500 md:scale-110" />
93
  </div>
94
  ),
95
  label: 'Gemini',
 
113
  {
114
  icon: (
115
  <div className="bg-gradient-to-br from-purple-500 to-purple-600 w-full h-full rounded-xl flex items-center justify-center shadow-inner">
116
+ <CalendarIcon size={24} weight="regular" className="text-white md:scale-110" />
117
  </div>
118
  ),
119
  label: 'Calendar',
 
125
  return (
126
  <div className="dock-container">
127
  <div
128
+ className="dock-glass px-2 pb-1.5 pt-2 md:px-3 md:pb-2 md:pt-3 rounded-t-2xl md:rounded-2xl flex items-end gap-2 md:gap-3 shadow-2xl border border-white/20 transition-all duration-300"
129
  onMouseMove={(e) => mouseX.current = e.pageX}
130
  onMouseLeave={() => mouseX.current = null}
131
  >
 
134
  <DockItem {...item} mouseX={mouseX} />
135
  {index === dockItems.length - 1 && (
136
  <>
137
+ <div className="w-px h-8 md:h-10 bg-gray-400/30 mx-0.5 md:mx-1" />
138
  <DockItem
139
  icon={
140
  <div className="bg-gradient-to-b from-gray-200 to-gray-300 w-full h-full rounded-xl flex items-center justify-center border border-white/40">
141
+ <Trash size={20} weight="regular" className="text-gray-600 md:scale-110" />
142
  </div>
143
  }
144
  label="Trash"
app/components/FlutterRunner.tsx CHANGED
@@ -17,6 +17,22 @@ interface FlutterRunnerProps {
17
  export function FlutterRunner({ file, onClose }: FlutterRunnerProps) {
18
  const [sidebarOpen, setSidebarOpen] = useState(true)
19
  const [copySuccess, setCopySuccess] = useState(false)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
 
21
  const handleCopyCode = async () => {
22
  try {
@@ -63,20 +79,22 @@ export function FlutterRunner({ file, onClose }: FlutterRunnerProps) {
63
  title="Zapp Flutter IDE"
64
  />
65
 
66
- {/* Instruction overlay */}
67
- <div className="absolute top-4 left-4 right-4 bg-blue-500/90 backdrop-blur-sm text-white px-4 py-3 rounded-lg shadow-lg border border-blue-400/50">
68
- <div className="flex items-center gap-2">
69
- <CodeIcon size={20} weight="bold" />
70
- <p className="text-sm font-medium">
71
- Use the sidebar to copy your Flutter code and dependencies, then paste into Zapp's editor.
72
- </p>
 
 
73
  </div>
74
- </div>
75
  </div>
76
 
77
  {/* Collapsible Sidebar */}
78
  {sidebarOpen && (
79
- <div className="w-[350px] border-l border-gray-200 bg-gradient-to-b from-gray-50 to-white flex flex-col shadow-lg">
80
  {/* Sidebar Header */}
81
  <div className="h-12 bg-gradient-to-r from-cyan-500 to-blue-500 flex items-center justify-between px-4 text-white">
82
  <div className="flex items-center gap-2">
 
17
  export function FlutterRunner({ file, onClose }: FlutterRunnerProps) {
18
  const [sidebarOpen, setSidebarOpen] = useState(true)
19
  const [copySuccess, setCopySuccess] = useState(false)
20
+ const [isMobile, setIsMobile] = useState(false)
21
+
22
+ // Detect mobile and auto-close sidebar on mobile landscape
23
+ React.useEffect(() => {
24
+ const checkMobile = () => {
25
+ const mobile = window.innerWidth <= 1024
26
+ setIsMobile(mobile)
27
+ // Auto-close sidebar on mobile landscape for more iframe space
28
+ if (mobile && window.innerHeight < window.innerWidth) {
29
+ setSidebarOpen(false)
30
+ }
31
+ }
32
+ checkMobile()
33
+ window.addEventListener('resize', checkMobile)
34
+ return () => window.removeEventListener('resize', checkMobile)
35
+ }, [])
36
 
37
  const handleCopyCode = async () => {
38
  try {
 
79
  title="Zapp Flutter IDE"
80
  />
81
 
82
+ {/* Instruction overlay - Hide on mobile when sidebar is open */}
83
+ {!(isMobile && sidebarOpen) && (
84
+ <div className="absolute top-2 left-2 right-2 md:top-4 md:left-4 md:right-4 bg-blue-500/90 backdrop-blur-sm text-white px-3 py-2 md:px-4 md:py-3 rounded-lg shadow-lg border border-blue-400/50">
85
+ <div className="flex items-center gap-2">
86
+ <CodeIcon size={isMobile ? 16 : 20} weight="bold" />
87
+ <p className="text-xs md:text-sm font-medium">
88
+ {isMobile ? 'Tap the arrow to view code' : 'Use the sidebar to copy your Flutter code and dependencies, then paste into Zapp\'s editor.'}
89
+ </p>
90
+ </div>
91
  </div>
92
+ )}
93
  </div>
94
 
95
  {/* Collapsible Sidebar */}
96
  {sidebarOpen && (
97
+ <div className={`${isMobile ? 'w-full absolute right-0 top-0 bottom-0 z-10' : 'w-[350px]'} border-l border-gray-200 bg-gradient-to-b from-gray-50 to-white flex flex-col shadow-lg`}>
98
  {/* Sidebar Header */}
99
  <div className="h-12 bg-gradient-to-r from-cyan-500 to-blue-500 flex items-center justify-between px-4 text-white">
100
  <div className="flex items-center gap-2">
app/components/Window.tsx CHANGED
@@ -1,6 +1,6 @@
1
  'use client';
2
 
3
- import React, { ReactNode } from 'react';
4
  import { Rnd } from 'react-rnd';
5
 
6
  interface WindowProps {
@@ -42,9 +42,42 @@ const Window: React.FC<WindowProps> = ({
42
  const [previousSize, setPreviousSize] = React.useState({ width, height, x, y });
43
  const [currentPosition, setCurrentPosition] = React.useState({ x, y });
44
  const [currentSize, setCurrentSize] = React.useState({ width, height });
 
 
 
 
 
 
 
 
 
 
 
45
 
46
  if (!isOpen) return null;
47
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  const handleMaximize = () => {
49
  if (!onMaximize) return;
50
 
 
1
  'use client';
2
 
3
+ import React, { ReactNode, useState, useEffect } from 'react';
4
  import { Rnd } from 'react-rnd';
5
 
6
  interface WindowProps {
 
42
  const [previousSize, setPreviousSize] = React.useState({ width, height, x, y });
43
  const [currentPosition, setCurrentPosition] = React.useState({ x, y });
44
  const [currentSize, setCurrentSize] = React.useState({ width, height });
45
+ const [isMobile, setIsMobile] = React.useState(false);
46
+
47
+ // Detect mobile device
48
+ useEffect(() => {
49
+ const checkMobile = () => {
50
+ setIsMobile(window.innerWidth <= 1024);
51
+ };
52
+ checkMobile();
53
+ window.addEventListener('resize', checkMobile);
54
+ return () => window.removeEventListener('resize', checkMobile);
55
+ }, []);
56
 
57
  if (!isOpen) return null;
58
 
59
+ // On mobile, always render full screen
60
+ if (isMobile) {
61
+ return (
62
+ <div
63
+ className={`fixed inset-0 z-50 flex flex-col overflow-hidden ${windowClass} ${className}`}
64
+ style={{ top: 0, bottom: 0 }}
65
+ >
66
+ <div
67
+ className={`h-10 md:h-8 flex items-center px-3 space-x-2 border-b ${headerClass} ${headerClassName}`}
68
+ >
69
+ <div className="flex space-x-2 group">
70
+ <div className="traffic-light traffic-close" onClick={onClose} />
71
+ <div className="traffic-light traffic-min" onClick={onMinimize} />
72
+ <div className="traffic-light traffic-max" onClick={onMaximize} />
73
+ </div>
74
+ <span className="font-semibold text-gray-700 flex-1 text-center pr-10 text-xs md:text-sm">{title}</span>
75
+ </div>
76
+ <div className="flex-1 overflow-auto">{children}</div>
77
+ </div>
78
+ );
79
+ }
80
+
81
  const handleMaximize = () => {
82
  if (!onMaximize) return;
83
 
app/globals.css CHANGED
@@ -339,4 +339,110 @@
339
 
340
  .dock-item:hover .dock-dot {
341
  @apply opacity-100;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
342
  }
 
339
 
340
  .dock-item:hover .dock-dot {
341
  @apply opacity-100;
342
+ }
343
+
344
+ /* Mobile & Tablet Responsive Styles */
345
+ @media (max-width: 1024px) {
346
+ body {
347
+ touch-action: pan-x pan-y;
348
+ user-select: auto;
349
+ }
350
+
351
+ /* Make windows full screen on mobile */
352
+ .macos-window {
353
+ @apply rounded-none shadow-lg;
354
+ }
355
+
356
+ /* Adjust dock for mobile */
357
+ .dock-container {
358
+ @apply bottom-0 px-2;
359
+ }
360
+
361
+ .dock-glass {
362
+ @apply rounded-t-2xl rounded-b-none;
363
+ padding: 0.5rem 1rem;
364
+ }
365
+
366
+ /* Hide desktop icons on mobile, use dock instead */
367
+ .desktop-icon {
368
+ display: none !important;
369
+ }
370
+
371
+ /* Adjust traffic lights for touch */
372
+ .traffic-light {
373
+ width: 14px;
374
+ height: 14px;
375
+ margin-right: 10px;
376
+ }
377
+ }
378
+
379
+ /* Landscape mobile optimization */
380
+ @media (max-width: 1024px) and (orientation: landscape) {
381
+ /* Top bar more compact */
382
+ .macos-window-header {
383
+ @apply h-8 px-3;
384
+ }
385
+
386
+ .traffic-light {
387
+ width: 10px;
388
+ height: 10px;
389
+ margin-right: 6px;
390
+ }
391
+
392
+ /* Dock even more compact in landscape */
393
+ .dock-container {
394
+ @apply bottom-0;
395
+ }
396
+
397
+ .dock-glass {
398
+ padding: 0.25rem 0.75rem;
399
+ }
400
+
401
+ /* Scrollbars thinner on mobile */
402
+ ::-webkit-scrollbar {
403
+ width: 6px;
404
+ height: 6px;
405
+ }
406
+ }
407
+
408
+ /* Portrait mobile optimization */
409
+ @media (max-width: 768px) and (orientation: portrait) {
410
+ /* Stack dock items more compactly */
411
+ .dock-item {
412
+ @apply gap-0;
413
+ }
414
+
415
+ /* Adjust font sizes for mobile */
416
+ .desktop-icon-label {
417
+ @apply text-[10px];
418
+ }
419
+ }
420
+
421
+ /* Touch-friendly improvements */
422
+ @media (hover: none) and (pointer: coarse) {
423
+ /* Larger touch targets */
424
+ .traffic-light {
425
+ width: 16px;
426
+ height: 16px;
427
+ margin-right: 12px;
428
+ }
429
+
430
+ /* Better tap feedback */
431
+ .app-icon:active {
432
+ transform: scale(0.90);
433
+ }
434
+
435
+ .dock-item:active {
436
+ transform: scale(0.95);
437
+ }
438
+
439
+ /* Remove hover effects on touch devices */
440
+ .app-icon:hover {
441
+ transform: none;
442
+ }
443
+
444
+ .desktop-icon:hover {
445
+ background-color: transparent;
446
+ box-shadow: none;
447
+ }
448
  }