fix: home sidebar flex column layout with independent scrolling sections
- Convert sidebar <aside> to flex column container (flex flex-col overflow-hidden min-h-0) - Remove sidebar-level scroll (overflow-y-auto → overflow-hidden) - Add min-h-0 to parent flex containers in both layout paths for proper height containment - Reorder sidebar sections: Clock → Projects → Drones → Recent Chats - Add shrink-0 to Clock component to keep it at intrinsic content size - Convert Projects, Drones, and Recent Chats sections to flex-1 min-h-0 flex-col with scrollable inner content - Pin section headers with shrink-0; list content scrolls independently via overflow-y-auto - Pin Recent Chats hint/footer with shrink-0 below its scrollable list - No logic changes — purely CSS/flexbox layout correction
This commit is contained in:
parent
c2028f45c5
commit
802184a487
@ -28,7 +28,7 @@ export default function Clock() {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="p-3">
|
||||
<div className="p-3 shrink-0">
|
||||
<div className="text-xs font-semibold text-text-muted uppercase tracking-wider mb-2">
|
||||
Clock
|
||||
</div>
|
||||
|
||||
@ -269,53 +269,12 @@ function DashboardSidebar({
|
||||
};
|
||||
|
||||
return (
|
||||
<aside className="w-64 border-l border-border-subtle bg-bg-secondary overflow-y-auto">
|
||||
<aside className="w-64 border-l border-border-subtle bg-bg-secondary flex flex-col overflow-hidden min-h-0">
|
||||
<Clock />
|
||||
|
||||
<div className="p-3 border-t border-border-subtle">
|
||||
<div className="text-xs font-semibold text-text-muted uppercase tracking-wider mb-2">
|
||||
Recent Chats
|
||||
</div>
|
||||
{loading ? (
|
||||
<p className="text-sm text-text-muted px-2">Loading...</p>
|
||||
) : recentChats.length === 0 ? (
|
||||
<p className="text-sm text-text-muted px-2">No recent chats.</p>
|
||||
) : (
|
||||
<div className="space-y-1">
|
||||
{recentChats.map((session) => (
|
||||
<button
|
||||
key={session._id}
|
||||
onClick={() => handleOpenChat(session)}
|
||||
className="w-full text-left px-2 py-1.5 text-sm text-text-secondary hover:bg-bg-tertiary hover:text-text-primary rounded transition-colors"
|
||||
>
|
||||
<div className="flex items-center justify-between gap-1">
|
||||
<span className="truncate font-medium text-xs">{session.name || 'Untitled'}</span>
|
||||
<span className={`text-[10px] px-1.5 py-0.5 rounded-full font-medium shrink-0 ${modeBadgeColors[session.mode] || 'bg-gray-500/20 text-gray-400'}`}>
|
||||
{session.mode}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between gap-1 mt-0.5">
|
||||
<span className="text-[11px] text-text-muted truncate">{getProjectName(session)}</span>
|
||||
<span className="text-[10px] text-text-muted shrink-0">
|
||||
{formatRelativeTime(session.lastMessageAt || session.createdAt)}
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{noDroneMessage && (
|
||||
<p className="text-xs text-yellow-400 mt-2 px-2">{noDroneMessage}</p>
|
||||
)}
|
||||
{selectedDrone && (
|
||||
<p className="text-[10px] text-text-muted mt-2 px-2 italic">
|
||||
Select a chat to resume with {selectedDrone.hostname}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="p-3 border-t border-border-subtle">
|
||||
<div className="flex items-center justify-between text-xs font-semibold text-text-muted uppercase tracking-wider mb-2">
|
||||
{/* Projects */}
|
||||
<div className="flex-1 min-h-0 flex flex-col border-t border-border-subtle">
|
||||
<div className="shrink-0 px-3 pt-3 pb-2 flex items-center justify-between text-xs font-semibold text-text-muted uppercase tracking-wider">
|
||||
<span>Projects</span>
|
||||
<Link
|
||||
to="/projects"
|
||||
@ -324,27 +283,30 @@ function DashboardSidebar({
|
||||
[+]
|
||||
</Link>
|
||||
</div>
|
||||
{loading ? (
|
||||
<p className="text-sm text-text-muted px-2">Loading...</p>
|
||||
) : projects.length === 0 ? (
|
||||
<p className="text-sm text-text-muted px-2">No projects yet.</p>
|
||||
) : (
|
||||
<div className="space-y-1">
|
||||
{projects.map((project) => (
|
||||
<button
|
||||
key={project._id}
|
||||
onClick={() => navigate(`/projects/${project.slug}`)}
|
||||
className="w-full text-left px-2 py-1.5 text-sm text-text-secondary hover:bg-bg-tertiary hover:text-text-primary rounded truncate transition-colors"
|
||||
>
|
||||
{project.name}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<div className="flex-1 min-h-0 overflow-y-auto px-3 pb-3">
|
||||
{loading ? (
|
||||
<p className="text-sm text-text-muted px-2">Loading...</p>
|
||||
) : projects.length === 0 ? (
|
||||
<p className="text-sm text-text-muted px-2">No projects yet.</p>
|
||||
) : (
|
||||
<div className="space-y-1">
|
||||
{projects.map((project) => (
|
||||
<button
|
||||
key={project._id}
|
||||
onClick={() => navigate(`/projects/${project.slug}`)}
|
||||
className="w-full text-left px-2 py-1.5 text-sm text-text-secondary hover:bg-bg-tertiary hover:text-text-primary rounded truncate transition-colors"
|
||||
>
|
||||
{project.name}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-3 border-t border-border-subtle">
|
||||
<div className="flex items-center justify-between text-xs font-semibold text-text-muted uppercase tracking-wider mb-2">
|
||||
{/* Drones */}
|
||||
<div className="flex-1 min-h-0 flex flex-col border-t border-border-subtle">
|
||||
<div className="shrink-0 px-3 pt-3 pb-2 flex items-center justify-between text-xs font-semibold text-text-muted uppercase tracking-wider">
|
||||
<span>Drones</span>
|
||||
<Link
|
||||
to="/drones"
|
||||
@ -372,51 +334,100 @@ function DashboardSidebar({
|
||||
</svg>
|
||||
</Link>
|
||||
</div>
|
||||
{loading ? (
|
||||
<p className="text-sm text-text-muted px-2">Loading...</p>
|
||||
) : drones.length === 0 ? (
|
||||
<p className="text-sm text-text-muted px-2">No drones available.</p>
|
||||
) : (
|
||||
<div className="space-y-1">
|
||||
{drones.map((drone) => (
|
||||
<button
|
||||
key={drone._id}
|
||||
onClick={() => onSelectDrone(drone)}
|
||||
className={`w-full text-left px-2 py-1 rounded transition-colors ${
|
||||
selectedDrone?._id === drone._id
|
||||
? "bg-brand text-white"
|
||||
: "text-text-secondary hover:bg-bg-tertiary hover:text-text-primary"
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<span
|
||||
className={`w-2 h-2 rounded-full shrink-0 ${
|
||||
drone.status === "available"
|
||||
? "bg-green-500"
|
||||
: "bg-yellow-500"
|
||||
}`}
|
||||
/>
|
||||
<div className="overflow-hidden">
|
||||
<div className="text-sm truncate">{drone.hostname}</div>
|
||||
{drone.workspaceDir && (
|
||||
<div
|
||||
className={`text-xs truncate ${
|
||||
selectedDrone?._id === drone._id
|
||||
? "text-white/70"
|
||||
: "text-text-muted"
|
||||
}`}
|
||||
>
|
||||
{drone.workspaceDir.length > 24
|
||||
? "..." + drone.workspaceDir.slice(-21)
|
||||
: drone.workspaceDir}
|
||||
</div>
|
||||
)}
|
||||
<div className="flex-1 min-h-0 overflow-y-auto px-3 pb-3">
|
||||
{loading ? (
|
||||
<p className="text-sm text-text-muted px-2">Loading...</p>
|
||||
) : drones.length === 0 ? (
|
||||
<p className="text-sm text-text-muted px-2">No drones available.</p>
|
||||
) : (
|
||||
<div className="space-y-1">
|
||||
{drones.map((drone) => (
|
||||
<button
|
||||
key={drone._id}
|
||||
onClick={() => onSelectDrone(drone)}
|
||||
className={`w-full text-left px-2 py-1 rounded transition-colors ${
|
||||
selectedDrone?._id === drone._id
|
||||
? "bg-brand text-white"
|
||||
: "text-text-secondary hover:bg-bg-tertiary hover:text-text-primary"
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<span
|
||||
className={`w-2 h-2 rounded-full shrink-0 ${
|
||||
drone.status === "available"
|
||||
? "bg-green-500"
|
||||
: "bg-yellow-500"
|
||||
}`}
|
||||
/>
|
||||
<div className="overflow-hidden">
|
||||
<div className="text-sm truncate">{drone.hostname}</div>
|
||||
{drone.workspaceDir && (
|
||||
<div
|
||||
className={`text-xs truncate ${
|
||||
selectedDrone?._id === drone._id
|
||||
? "text-white/70"
|
||||
: "text-text-muted"
|
||||
}`}
|
||||
>
|
||||
{drone.workspaceDir.length > 24
|
||||
? "..." + drone.workspaceDir.slice(-21)
|
||||
: drone.workspaceDir}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Recent Chats */}
|
||||
<div className="flex-1 min-h-0 flex flex-col border-t border-border-subtle">
|
||||
<div className="shrink-0 px-3 pt-3 pb-2 text-xs font-semibold text-text-muted uppercase tracking-wider">
|
||||
Recent Chats
|
||||
</div>
|
||||
<div className="flex-1 min-h-0 overflow-y-auto px-3 pb-3">
|
||||
{loading ? (
|
||||
<p className="text-sm text-text-muted px-2">Loading...</p>
|
||||
) : recentChats.length === 0 ? (
|
||||
<p className="text-sm text-text-muted px-2">No recent chats.</p>
|
||||
) : (
|
||||
<div className="space-y-1">
|
||||
{recentChats.map((session) => (
|
||||
<button
|
||||
key={session._id}
|
||||
onClick={() => handleOpenChat(session)}
|
||||
className="w-full text-left px-2 py-1.5 text-sm text-text-secondary hover:bg-bg-tertiary hover:text-text-primary rounded transition-colors"
|
||||
>
|
||||
<div className="flex items-center justify-between gap-1">
|
||||
<span className="truncate font-medium text-xs">{session.name || 'Untitled'}</span>
|
||||
<span className={`text-[10px] px-1.5 py-0.5 rounded-full font-medium shrink-0 ${modeBadgeColors[session.mode] || 'bg-gray-500/20 text-gray-400'}`}>
|
||||
{session.mode}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between gap-1 mt-0.5">
|
||||
<span className="text-[11px] text-text-muted truncate">{getProjectName(session)}</span>
|
||||
<span className="text-[10px] text-text-muted shrink-0">
|
||||
{formatRelativeTime(session.lastMessageAt || session.createdAt)}
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="shrink-0 px-3 pb-3">
|
||||
{noDroneMessage && (
|
||||
<p className="text-xs text-yellow-400 mt-2 px-2">{noDroneMessage}</p>
|
||||
)}
|
||||
{selectedDrone && (
|
||||
<p className="text-[10px] text-text-muted mt-2 px-2 italic">
|
||||
Select a chat to resume with {selectedDrone.hostname}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</aside>
|
||||
@ -447,7 +458,7 @@ export default function Home({ user }: HomeProps) {
|
||||
}
|
||||
|
||||
const mainContent = selectedDrone ? (
|
||||
<div className="relative z-10 flex-1 flex">
|
||||
<div className="relative z-10 flex-1 flex min-h-0">
|
||||
<DroneInspector
|
||||
drone={selectedDrone}
|
||||
onClose={() => setSelectedDrone(null)}
|
||||
@ -463,8 +474,8 @@ export default function Home({ user }: HomeProps) {
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="relative z-10 flex-1 flex flex-col">
|
||||
<div className="flex-1 flex">
|
||||
<div className="relative z-10 flex-1 flex flex-col min-h-0">
|
||||
<div className="flex-1 flex min-h-0">
|
||||
<div className="flex-1 flex items-center justify-center p-8">
|
||||
<div className="text-center">
|
||||
<h1 className="text-2xl font-semibold mb-4">
|
||||
|
||||
Loading…
Reference in New Issue
Block a user