Skip to content

Commit 33cec91

Browse files
committed
Made a ResizableSidebar so we are not duplicating code for both sidebars
1 parent 32bf1f7 commit 33cec91

File tree

9 files changed

+206
-304
lines changed

9 files changed

+206
-304
lines changed

apps/array/src/renderer/components/HeaderRow.tsx

Lines changed: 0 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,22 @@ import { ChangesTabBadge } from "@features/task-detail/components/ChangesTabBadg
66
import { Box, Flex } from "@radix-ui/themes";
77
import { useHeaderStore } from "@stores/headerStore";
88
import { useNavigationStore } from "@stores/navigationStore";
9-
import { useEffect } from "react";
109

1110
const HEADER_HEIGHT = 40;
1211
const COLLAPSED_WIDTH = 110;
13-
const MIN_WIDTH = 140;
1412

1513
export function HeaderRow() {
1614
const content = useHeaderStore((state) => state.content);
1715
const view = useNavigationStore((state) => state.view);
1816
const sidebarOpen = useSidebarStore((state) => state.open);
1917
const sidebarWidth = useSidebarStore((state) => state.width);
2018
const isResizing = useSidebarStore((state) => state.isResizing);
21-
const setWidth = useSidebarStore((state) => state.setWidth);
2219
const setIsResizing = useSidebarStore((state) => state.setIsResizing);
2320
const rightSidebarOpen = useRightSidebarStore((state) => state.open);
2421
const rightSidebarWidth = useRightSidebarStore((state) => state.width);
2522
const rightSidebarIsResizing = useRightSidebarStore(
2623
(state) => state.isResizing,
2724
);
28-
const setRightSidebarWidth = useRightSidebarStore((state) => state.setWidth);
2925
const setRightSidebarIsResizing = useRightSidebarStore(
3026
(state) => state.setIsResizing,
3127
);
@@ -46,63 +42,6 @@ export function HeaderRow() {
4642
document.body.style.userSelect = "none";
4743
};
4844

49-
// Left sidebar resize handler
50-
useEffect(() => {
51-
const handleMouseMove = (e: MouseEvent) => {
52-
if (!isResizing) return;
53-
54-
const maxWidth = window.innerWidth * 0.5;
55-
const newWidth = Math.max(MIN_WIDTH, Math.min(maxWidth, e.clientX));
56-
setWidth(newWidth);
57-
};
58-
59-
const handleMouseUp = () => {
60-
if (isResizing) {
61-
setIsResizing(false);
62-
document.body.style.cursor = "";
63-
document.body.style.userSelect = "";
64-
}
65-
};
66-
67-
document.addEventListener("mousemove", handleMouseMove);
68-
document.addEventListener("mouseup", handleMouseUp);
69-
70-
return () => {
71-
document.removeEventListener("mousemove", handleMouseMove);
72-
document.removeEventListener("mouseup", handleMouseUp);
73-
};
74-
}, [setWidth, isResizing, setIsResizing]);
75-
76-
// Right sidebar resize handler
77-
useEffect(() => {
78-
const handleMouseMove = (e: MouseEvent) => {
79-
if (!rightSidebarIsResizing) return;
80-
81-
const maxWidth = window.innerWidth * 0.5;
82-
const newWidth = Math.max(
83-
MIN_WIDTH,
84-
Math.min(maxWidth, window.innerWidth - e.clientX),
85-
);
86-
setRightSidebarWidth(newWidth);
87-
};
88-
89-
const handleMouseUp = () => {
90-
if (rightSidebarIsResizing) {
91-
setRightSidebarIsResizing(false);
92-
document.body.style.cursor = "";
93-
document.body.style.userSelect = "";
94-
}
95-
};
96-
97-
document.addEventListener("mousemove", handleMouseMove);
98-
document.addEventListener("mouseup", handleMouseUp);
99-
100-
return () => {
101-
document.removeEventListener("mousemove", handleMouseMove);
102-
document.removeEventListener("mouseup", handleMouseUp);
103-
};
104-
}, [setRightSidebarWidth, rightSidebarIsResizing, setRightSidebarIsResizing]);
105-
10645
return (
10746
<Flex
10847
align="center"
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import { Box, Flex } from "@radix-ui/themes";
2+
import React from "react";
3+
4+
const MIN_WIDTH = 140;
5+
6+
interface ResizableSidebarProps {
7+
children: React.ReactNode;
8+
open: boolean;
9+
width: number;
10+
setWidth: (width: number) => void;
11+
isResizing: boolean;
12+
setIsResizing: (isResizing: boolean) => void;
13+
side: "left" | "right";
14+
}
15+
16+
export const ResizableSidebar: React.FC<ResizableSidebarProps> = ({
17+
children,
18+
open,
19+
width,
20+
setWidth,
21+
isResizing,
22+
setIsResizing,
23+
side,
24+
}) => {
25+
const handleMouseDown = (e: React.MouseEvent) => {
26+
e.preventDefault();
27+
setIsResizing(true);
28+
document.body.style.cursor = "col-resize";
29+
document.body.style.userSelect = "none";
30+
};
31+
32+
React.useEffect(() => {
33+
const handleMouseMove = (e: MouseEvent) => {
34+
if (!isResizing) return;
35+
36+
const maxWidth = window.innerWidth * 0.5;
37+
const newWidth =
38+
side === "left"
39+
? Math.max(MIN_WIDTH, Math.min(maxWidth, e.clientX))
40+
: Math.max(
41+
MIN_WIDTH,
42+
Math.min(maxWidth, window.innerWidth - e.clientX),
43+
);
44+
setWidth(newWidth);
45+
};
46+
47+
const handleMouseUp = () => {
48+
if (isResizing) {
49+
setIsResizing(false);
50+
document.body.style.cursor = "";
51+
document.body.style.userSelect = "";
52+
}
53+
};
54+
55+
document.addEventListener("mousemove", handleMouseMove);
56+
document.addEventListener("mouseup", handleMouseUp);
57+
58+
return () => {
59+
document.removeEventListener("mousemove", handleMouseMove);
60+
document.removeEventListener("mouseup", handleMouseUp);
61+
};
62+
}, [setWidth, isResizing, setIsResizing, side]);
63+
64+
const isLeft = side === "left";
65+
66+
return (
67+
<Box
68+
style={{
69+
width: open ? `${width}px` : "0",
70+
minWidth: open ? `${width}px` : "0",
71+
maxWidth: open ? `${width}px` : "0",
72+
height: "100%",
73+
overflow: "hidden",
74+
transition: isResizing ? "none" : "width 0.2s ease-in-out",
75+
borderLeft: !isLeft && open ? "1px solid var(--gray-6)" : "none",
76+
borderRight: isLeft && open ? "1px solid var(--gray-6)" : "none",
77+
position: "relative",
78+
flexShrink: 0,
79+
}}
80+
>
81+
<Flex
82+
direction="column"
83+
style={{
84+
width: `${width}px`,
85+
height: "100%",
86+
}}
87+
>
88+
{children}
89+
</Flex>
90+
{open && (
91+
<Box
92+
onMouseDown={handleMouseDown}
93+
className="no-drag"
94+
style={{
95+
position: "absolute",
96+
left: isLeft ? undefined : 0,
97+
right: isLeft ? 0 : undefined,
98+
top: 0,
99+
bottom: 0,
100+
width: "4px",
101+
cursor: "col-resize",
102+
backgroundColor: "transparent",
103+
zIndex: 100,
104+
}}
105+
/>
106+
)}
107+
</Box>
108+
);
109+
};
Lines changed: 11 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
import { Box, Flex } from "@radix-ui/themes";
2-
import React from "react";
1+
import { ResizableSidebar } from "@components/ResizableSidebar";
2+
import type React from "react";
33
import { useRightSidebarStore } from "../stores/rightSidebarStore";
44

5-
const MIN_WIDTH = 140;
6-
75
export const RightSidebar: React.FC<{ children: React.ReactNode }> = ({
86
children,
97
}) => {
@@ -13,81 +11,16 @@ export const RightSidebar: React.FC<{ children: React.ReactNode }> = ({
1311
const isResizing = useRightSidebarStore((state) => state.isResizing);
1412
const setIsResizing = useRightSidebarStore((state) => state.setIsResizing);
1513

16-
const handleMouseDown = (e: React.MouseEvent) => {
17-
e.preventDefault();
18-
setIsResizing(true);
19-
document.body.style.cursor = "col-resize";
20-
document.body.style.userSelect = "none";
21-
};
22-
23-
React.useEffect(() => {
24-
const handleMouseMove = (e: MouseEvent) => {
25-
if (!isResizing) return;
26-
27-
const maxWidth = window.innerWidth * 0.5;
28-
const newWidth = Math.max(
29-
MIN_WIDTH,
30-
Math.min(maxWidth, window.innerWidth - e.clientX),
31-
);
32-
setWidth(newWidth);
33-
};
34-
35-
const handleMouseUp = () => {
36-
if (isResizing) {
37-
setIsResizing(false);
38-
document.body.style.cursor = "";
39-
document.body.style.userSelect = "";
40-
}
41-
};
42-
43-
document.addEventListener("mousemove", handleMouseMove);
44-
document.addEventListener("mouseup", handleMouseUp);
45-
46-
return () => {
47-
document.removeEventListener("mousemove", handleMouseMove);
48-
document.removeEventListener("mouseup", handleMouseUp);
49-
};
50-
}, [setWidth, isResizing, setIsResizing]);
51-
5214
return (
53-
<Box
54-
style={{
55-
width: open ? `${width}px` : "0",
56-
minWidth: open ? `${width}px` : "0",
57-
maxWidth: open ? `${width}px` : "0",
58-
height: "100%",
59-
overflow: "hidden",
60-
transition: isResizing ? "none" : "width 0.2s ease-in-out",
61-
borderLeft: open ? "1px solid var(--gray-6)" : "none",
62-
position: "relative",
63-
flexShrink: 0,
64-
}}
15+
<ResizableSidebar
16+
open={open}
17+
width={width}
18+
setWidth={setWidth}
19+
isResizing={isResizing}
20+
setIsResizing={setIsResizing}
21+
side="right"
6522
>
66-
<Flex
67-
direction="column"
68-
style={{
69-
width: `${width}px`,
70-
height: "100%",
71-
}}
72-
>
73-
{children}
74-
</Flex>
75-
{open && (
76-
<Box
77-
onMouseDown={handleMouseDown}
78-
className="no-drag"
79-
style={{
80-
position: "absolute",
81-
left: 0,
82-
top: 0,
83-
bottom: 0,
84-
width: "4px",
85-
cursor: "col-resize",
86-
backgroundColor: "transparent",
87-
zIndex: 100,
88-
}}
89-
/>
90-
)}
91-
</Box>
23+
{children}
24+
</ResizableSidebar>
9225
);
9326
};
Lines changed: 18 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { create } from "zustand";
2-
import { persist } from "zustand/middleware";
32

43
interface FileTreeStoreState {
54
// Per-task expanded folder paths - keyed by taskId, value is Set of expanded folder paths
@@ -12,56 +11,22 @@ interface FileTreeStoreActions {
1211

1312
type FileTreeStore = FileTreeStoreState & FileTreeStoreActions;
1413

15-
export const useFileTreeStore = create<FileTreeStore>()(
16-
persist(
17-
(set) => ({
18-
expandedPaths: {},
19-
togglePath: (taskId, path) =>
20-
set((state) => {
21-
const taskPaths = state.expandedPaths[taskId] ?? new Set<string>();
22-
const newPaths = new Set(taskPaths);
23-
if (newPaths.has(path)) {
24-
newPaths.delete(path);
25-
} else {
26-
newPaths.add(path);
27-
}
28-
return {
29-
expandedPaths: {
30-
...state.expandedPaths,
31-
[taskId]: newPaths,
32-
},
33-
};
34-
}),
14+
export const useFileTreeStore = create<FileTreeStore>()((set) => ({
15+
expandedPaths: {},
16+
togglePath: (taskId, path) =>
17+
set((state) => {
18+
const taskPaths = state.expandedPaths[taskId] ?? new Set<string>();
19+
const newPaths = new Set(taskPaths);
20+
if (newPaths.has(path)) {
21+
newPaths.delete(path);
22+
} else {
23+
newPaths.add(path);
24+
}
25+
return {
26+
expandedPaths: {
27+
...state.expandedPaths,
28+
[taskId]: newPaths,
29+
},
30+
};
3531
}),
36-
{
37-
name: "file-tree-storage",
38-
partialize: (state) => ({
39-
// Convert Sets to arrays for JSON serialization
40-
expandedPaths: Object.fromEntries(
41-
Object.entries(state.expandedPaths).map(([taskId, paths]) => [
42-
taskId,
43-
Array.from(paths),
44-
]),
45-
),
46-
}),
47-
merge: (persisted, current) => {
48-
const persistedState = persisted as {
49-
expandedPaths?: Record<string, string[]>;
50-
};
51-
// Convert arrays back to Sets
52-
const expandedPaths: Record<string, Set<string>> = {};
53-
if (persistedState.expandedPaths) {
54-
for (const [taskId, paths] of Object.entries(
55-
persistedState.expandedPaths,
56-
)) {
57-
expandedPaths[taskId] = new Set(paths);
58-
}
59-
}
60-
return {
61-
...current,
62-
expandedPaths,
63-
};
64-
},
65-
},
66-
),
67-
);
32+
}));

0 commit comments

Comments
 (0)