Skip to content
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
.PHONY: dev build lint format test test-watch test-e2e build-release clean

dev: ## Start dev mode with isolated DB (safe to run alongside prod app)
xattr -cr node_modules/electron/dist/Electron.app 2>/dev/null || true
E2E_DATA_DIR=$(HOME)/.codez-dev npm run dev

build:
Expand Down
126 changes: 83 additions & 43 deletions src/renderer/components/Sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export function Sidebar() {

const [archiveOpen, setArchiveOpen] = useState(false);
const [metaHeld, setMetaHeld] = useState(false);
const [collapsed, setCollapsed] = useState(false);
const repoPickerRef = useRef<HTMLDivElement>(null);

const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 8 } }));
Expand Down Expand Up @@ -175,62 +176,83 @@ export function Sidebar() {
]);

return (
<aside className="w-60 border-r border-white/[0.06] bg-transparent flex flex-col">
<aside
className={`${collapsed ? "w-10" : "w-60"} border-r border-white/[0.06] bg-transparent flex flex-col transition-[width] duration-200 ease-in-out overflow-hidden`}
>
{/* Draggable title bar + header */}
<div className="h-12 flex items-center px-4 [-webkit-app-region:drag]">
<span className="text-[13px] font-medium text-text-muted ml-16 flex-1">Codez</span>
<div className="[-webkit-app-region:no-drag]" ref={repoPickerRef}>
<div className="h-12 flex items-center px-2 [-webkit-app-region:drag] shrink-0">
{!collapsed && (
<span className="text-[13px] font-medium text-text-muted ml-16 flex-1 whitespace-nowrap overflow-hidden">
Codez
</span>
)}
<div
className={`flex items-center gap-1 [-webkit-app-region:no-drag] ${collapsed ? "mx-auto" : ""}`}
ref={repoPickerRef}
>
{!collapsed && (
<button
type="button"
onClick={handleNewSessionClick}
className="w-6 h-6 flex items-center justify-center rounded-md text-text-muted hover:text-text-primary hover:bg-white/10 transition-colors"
title="New session"
>
<PlusIcon />
</button>
)}
<button
type="button"
onClick={handleNewSessionClick}
onClick={() => setCollapsed(!collapsed)}
className="w-6 h-6 flex items-center justify-center rounded-md text-text-muted hover:text-text-primary hover:bg-white/10 transition-colors"
title="New session"
title={collapsed ? "Expand sidebar" : "Collapse sidebar"}
>
<PlusIcon />
<CollapseIcon collapsed={collapsed} />
</button>
</div>
</div>

{/* Session list — sorted by user-defined order */}
<div className="flex-1 overflow-y-auto px-1.5 py-1 space-y-0.5">
{sessions.length === 0 ? (
<div className="flex items-center justify-center h-32">
<p className="text-xs text-text-muted">
{repos.length === 0 ? "Add a folder to get started" : "No sessions yet"}
</p>
</div>
) : (
<DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
<SortableContext items={sessions.map((s) => s.id)} strategy={verticalListSortingStrategy}>
{sessions.map((session, index) => (
<SortableSessionItem
key={session.id}
session={session}
isActive={session.id === activeSessionId}
onClick={() => setActiveSession(session.id)}
onArchive={() => handleArchiveSession(session)}
branchName={session.branchName ?? branches.get(session.repoPath)}
shortcutNumber={metaHeld && index < 9 ? index + 1 : null}
/>
))}
</SortableContext>
</DndContext>
)}
{!collapsed && (
<div className="flex-1 overflow-y-auto px-1.5 py-1 space-y-0.5">
{sessions.length === 0 ? (
<div className="flex items-center justify-center h-32">
<p className="text-xs text-text-muted">
{repos.length === 0 ? "Add a folder to get started" : "No sessions yet"}
</p>
</div>
) : (
<DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
<SortableContext items={sessions.map((s) => s.id)} strategy={verticalListSortingStrategy}>
{sessions.map((session, index) => (
<SortableSessionItem
key={session.id}
session={session}
isActive={session.id === activeSessionId}
onClick={() => setActiveSession(session.id)}
onArchive={() => handleArchiveSession(session)}
branchName={session.branchName ?? branches.get(session.repoPath)}
shortcutNumber={metaHeld && index < 9 ? index + 1 : null}
/>
))}
</SortableContext>
</DndContext>
)}

{/* Add folder link — only when no repos exist */}
{repos.length === 0 && (
<button
type="button"
onClick={addRepoViaDialog}
className="w-full text-left px-3 py-1.5 text-xs text-text-muted hover:text-accent transition-colors [-webkit-app-region:no-drag]"
>
+ Add folder...
</button>
)}
</div>
{/* Add folder link — only when no repos exist */}
{repos.length === 0 && (
<button
type="button"
onClick={addRepoViaDialog}
className="w-full text-left px-3 py-1.5 text-xs text-text-muted hover:text-accent transition-colors [-webkit-app-region:no-drag]"
>
+ Add folder...
</button>
)}
</div>
)}

{/* Archive accordion */}
{archivedSessions.length > 0 && (
{!collapsed && archivedSessions.length > 0 && (
<div className="border-t border-white/[0.06]">
<button
type="button"
Expand Down Expand Up @@ -306,6 +328,24 @@ function PlusIcon() {
);
}

function CollapseIcon({ collapsed }: { collapsed: boolean }) {
return (
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className={`transition-transform ${collapsed ? "rotate-180" : ""}`}
>
<polyline points="15 18 9 12 15 6" />
</svg>
);
}

function ChevronIcon({ open }: { open: boolean }) {
return (
<svg
Expand Down
Loading