Skip to content

Commit aa94722

Browse files
committed
feat: resizable sidebar and mobile optimization
1 parent 4728536 commit aa94722

File tree

2 files changed

+100
-24
lines changed

2 files changed

+100
-24
lines changed

src/app/page.tsx

Lines changed: 98 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,60 @@ export default function Home() {
1616
const [mounted, setMounted] = useState(false);
1717
const [viewMode, setViewMode] = useState<"split" | "editor" | "preview">("split");
1818
const [isSidebarOpen, setIsSidebarOpen] = useState(true);
19+
20+
// Sidebar resizing & Mobile state
21+
const [sidebarWidth, setSidebarWidth] = useState(256);
22+
const [isResizing, setIsResizing] = useState(false);
23+
const [isMobile, setIsMobile] = useState(false);
1924

2025
useEffect(() => {
2126
setMounted(true);
27+
const checkMobile = () => {
28+
const mobile = window.innerWidth < 768;
29+
setIsMobile(mobile);
30+
if (mobile) {
31+
setIsSidebarOpen(false);
32+
if (viewMode === "split") setViewMode("editor");
33+
} else {
34+
setIsSidebarOpen(true);
35+
}
36+
};
37+
38+
checkMobile();
39+
window.addEventListener("resize", checkMobile);
40+
return () => window.removeEventListener("resize", checkMobile);
41+
}, []);
42+
43+
const startResizing = React.useCallback((e: React.MouseEvent) => {
44+
e.preventDefault();
45+
setIsResizing(true);
46+
}, []);
47+
48+
const stopResizing = React.useCallback(() => {
49+
setIsResizing(false);
2250
}, []);
2351

52+
const resize = React.useCallback(
53+
(mouseMoveEvent: MouseEvent) => {
54+
if (isResizing) {
55+
const newWidth = mouseMoveEvent.clientX;
56+
if (newWidth > 150 && newWidth < 600) {
57+
setSidebarWidth(newWidth);
58+
}
59+
}
60+
},
61+
[isResizing]
62+
);
63+
64+
useEffect(() => {
65+
window.addEventListener("mousemove", resize);
66+
window.addEventListener("mouseup", stopResizing);
67+
return () => {
68+
window.removeEventListener("mousemove", resize);
69+
window.removeEventListener("mouseup", stopResizing);
70+
};
71+
}, [resize, stopResizing]);
72+
2473
const activeFile = activeFileId ? files[activeFileId] : null;
2574

2675
const handleContentChange = (newContent: string) => {
@@ -35,34 +84,59 @@ export default function Home() {
3584
<div className="flex flex-col h-screen w-screen bg-background text-foreground overflow-hidden">
3685
<Navbar fileSystem={fileSystem} />
3786

38-
<div className="flex-1 flex overflow-hidden">
87+
<div className="flex-1 flex overflow-hidden relative">
88+
{/* Mobile Backdrop */}
89+
{isMobile && isSidebarOpen && (
90+
<div
91+
className="absolute inset-0 bg-black/50 z-40"
92+
onClick={() => setIsSidebarOpen(false)}
93+
/>
94+
)}
95+
3996
{/* Sidebar */}
40-
<div className={cn(
41-
"h-full transition-all duration-300 ease-in-out border-r border-border bg-card",
42-
isSidebarOpen ? "w-64" : "w-0 overflow-hidden border-r-0"
43-
)}>
44-
<Sidebar fileSystem={fileSystem} />
97+
<div
98+
className={cn(
99+
"h-full bg-card border-r border-border flex-shrink-0 relative z-50",
100+
isMobile ? "absolute left-0 top-0 bottom-0 shadow-2xl transition-transform duration-300" : "transition-[width] ease-linear duration-0",
101+
!isSidebarOpen && isMobile && "-translate-x-full",
102+
!isSidebarOpen && !isMobile && "w-0 border-none overflow-hidden"
103+
)}
104+
style={{ width: isMobile ? "80%" : (isSidebarOpen ? sidebarWidth : 0) }}
105+
>
106+
<Sidebar fileSystem={fileSystem} className="border-none" />
107+
108+
{/* Resizer Handle (Desktop only) */}
109+
{!isMobile && isSidebarOpen && (
110+
<div
111+
className="absolute right-0 top-0 w-1 h-full cursor-col-resize hover:bg-primary/50 transition-colors z-50 translate-x-1/2"
112+
onMouseDown={startResizing}
113+
/>
114+
)}
45115
</div>
46116

47117
{/* Main Area */}
48-
<main className="flex-1 flex flex-col overflow-hidden min-w-0">
118+
<main className="flex-1 flex flex-col overflow-hidden min-w-0 bg-background relative z-0">
49119

50120
{/* Toolbar */}
51-
<div className="h-10 border-b border-border flex items-center justify-between px-4 bg-muted/20">
52-
<div className="flex items-center gap-2">
121+
<div className="h-10 border-b border-border flex items-center justify-between px-4 bg-muted/20 shrink-0">
122+
<div className="flex items-center gap-2 overflow-hidden">
53123
<button
54124
onClick={() => setIsSidebarOpen(!isSidebarOpen)}
55-
className="p-1 hover:bg-accent rounded text-muted-foreground"
125+
className="p-1 hover:bg-accent rounded text-muted-foreground shrink-0"
56126
title="Toggle Sidebar"
57127
>
58-
<svg width="16" height="16" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1.5 3C1.22386 3 1 3.22386 1 3.5V11.5C1 11.7761 1.22386 12 1.5 12H13.5C13.7761 12 14 11.7761 14 11.5V3.5C14 3.22386 13.7761 3 13.5 3H1.5ZM1 3.5C1 2.67157 1.67157 2 2.5 2H12.5C13.3284 2 14 2.67157 14 3.5V11.5C14 12.3284 13.3284 13 12.5 13H2.5C1.67157 13 1 12.3284 1 11.5V3.5Z" fill="currentColor" fillRule="evenodd" clipRule="evenodd"></path><path d="M5 3V12H4V3H5Z" fill="currentColor" fillRule="evenodd" clipRule="evenodd"></path></svg>
128+
{isSidebarOpen ? (
129+
<svg width="16" height="16" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1.5 3C1.22386 3 1 3.22386 1 3.5V11.5C1 11.7761 1.22386 12 1.5 12H13.5C13.7761 12 14 11.7761 14 11.5V3.5C14 3.22386 13.7761 3 13.5 3H1.5ZM1 3.5C1 2.67157 1.67157 2 2.5 2H12.5C13.3284 2 14 2.67157 14 3.5V11.5C14 12.3284 13.3284 13 12.5 13H2.5C1.67157 13 1 12.3284 1 11.5V3.5Z" fill="currentColor" fillRule="evenodd" clipRule="evenodd"></path><path d="M5 3V12H4V3H5Z" fill="currentColor" fillRule="evenodd" clipRule="evenodd"></path></svg>
130+
) : (
131+
<svg width="16" height="16" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1.5 3C1.22386 3 1 3.22386 1 3.5V11.5C1 11.7761 1.22386 12 1.5 12H13.5C13.7761 12 14 11.7761 14 11.5V3.5C14 3.22386 13.7761 3 13.5 3H1.5ZM1 3.5C1 2.67157 1.67157 2 2.5 2H12.5C13.3284 2 14 2.67157 14 3.5V11.5C14 12.3284 13.3284 13 12.5 13H2.5C1.67157 13 1 12.3284 1 11.5V3.5Z" fill="currentColor" fillRule="evenodd" clipRule="evenodd"></path></svg>
132+
)}
59133
</button>
60134
<span className="text-sm text-muted-foreground truncate">
61135
{activeFile ? activeFile.name : "No file selected"}
62136
</span>
63137
</div>
64138

65-
<div className="flex items-center gap-1 border border-input rounded-md overflow-hidden bg-background">
139+
<div className="flex items-center gap-1 border border-input rounded-md overflow-hidden bg-background shrink-0">
66140
<button
67141
onClick={() => setViewMode("editor")}
68142
className={cn(
@@ -73,16 +147,18 @@ export default function Home() {
73147
>
74148
<Code2 size={16} />
75149
</button>
76-
<button
77-
onClick={() => setViewMode("split")}
78-
className={cn(
79-
"p-1.5 hover:bg-accent transition-colors",
80-
viewMode === "split" && "bg-accent text-accent-foreground"
81-
)}
82-
title="Split View"
83-
>
84-
<Columns size={16} />
85-
</button>
150+
{!isMobile && (
151+
<button
152+
onClick={() => setViewMode("split")}
153+
className={cn(
154+
"p-1.5 hover:bg-accent transition-colors",
155+
viewMode === "split" && "bg-accent text-accent-foreground"
156+
)}
157+
title="Split View"
158+
>
159+
<Columns size={16} />
160+
</button>
161+
)}
86162
<button
87163
onClick={() => setViewMode("preview")}
88164
className={cn(

src/components/navbar.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,12 @@ export function Navbar({ fileSystem }: NavbarProps) {
5555
<span className="font-bold text-lg tracking-tight hidden sm:inline-block">MathStudio</span>
5656
</div>
5757

58-
<div className="flex-1 max-w-md mx-4 relative" ref={searchRef}>
58+
<div className="flex-1 max-w-md mx-2 sm:mx-4 relative" ref={searchRef}>
5959
<div className="relative">
6060
<Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
6161
<input
6262
type="text"
63-
placeholder="Search files..."
63+
placeholder="Search..."
6464
className="w-full bg-muted/50 border border-input rounded-md pl-8 pr-4 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-ring"
6565
value={query}
6666
onChange={(e) => setQuery(e.target.value)}

0 commit comments

Comments
 (0)