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:
Rob Colbert 2026-05-18 09:04:25 -04:00
parent c2028f45c5
commit 802184a487
2 changed files with 123 additions and 112 deletions

View File

@ -28,7 +28,7 @@ export default function Clock() {
}, []); }, []);
return ( 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"> <div className="text-xs font-semibold text-text-muted uppercase tracking-wider mb-2">
Clock Clock
</div> </div>

View File

@ -269,53 +269,12 @@ function DashboardSidebar({
}; };
return ( 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 /> <Clock />
<div className="p-3 border-t border-border-subtle"> {/* Projects */}
<div className="text-xs font-semibold text-text-muted uppercase tracking-wider mb-2"> <div className="flex-1 min-h-0 flex flex-col border-t border-border-subtle">
Recent Chats <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">
</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">
<span>Projects</span> <span>Projects</span>
<Link <Link
to="/projects" to="/projects"
@ -324,6 +283,7 @@ function DashboardSidebar({
[+] [+]
</Link> </Link>
</div> </div>
<div className="flex-1 min-h-0 overflow-y-auto px-3 pb-3">
{loading ? ( {loading ? (
<p className="text-sm text-text-muted px-2">Loading...</p> <p className="text-sm text-text-muted px-2">Loading...</p>
) : projects.length === 0 ? ( ) : projects.length === 0 ? (
@ -342,9 +302,11 @@ function DashboardSidebar({
</div> </div>
)} )}
</div> </div>
</div>
<div className="p-3 border-t border-border-subtle"> {/* Drones */}
<div className="flex items-center justify-between text-xs font-semibold text-text-muted uppercase tracking-wider mb-2"> <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> <span>Drones</span>
<Link <Link
to="/drones" to="/drones"
@ -372,6 +334,7 @@ function DashboardSidebar({
</svg> </svg>
</Link> </Link>
</div> </div>
<div className="flex-1 min-h-0 overflow-y-auto px-3 pb-3">
{loading ? ( {loading ? (
<p className="text-sm text-text-muted px-2">Loading...</p> <p className="text-sm text-text-muted px-2">Loading...</p>
) : drones.length === 0 ? ( ) : drones.length === 0 ? (
@ -418,6 +381,54 @@ function DashboardSidebar({
</div> </div>
)} )}
</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> </aside>
); );
@ -447,7 +458,7 @@ export default function Home({ user }: HomeProps) {
} }
const mainContent = selectedDrone ? ( const mainContent = selectedDrone ? (
<div className="relative z-10 flex-1 flex"> <div className="relative z-10 flex-1 flex min-h-0">
<DroneInspector <DroneInspector
drone={selectedDrone} drone={selectedDrone}
onClose={() => setSelectedDrone(null)} onClose={() => setSelectedDrone(null)}
@ -463,8 +474,8 @@ export default function Home({ user }: HomeProps) {
/> />
</div> </div>
) : ( ) : (
<div className="relative z-10 flex-1 flex flex-col"> <div className="relative z-10 flex-1 flex flex-col min-h-0">
<div className="flex-1 flex"> <div className="flex-1 flex min-h-0">
<div className="flex-1 flex items-center justify-center p-8"> <div className="flex-1 flex items-center justify-center p-8">
<div className="text-center"> <div className="text-center">
<h1 className="text-2xl font-semibold mb-4"> <h1 className="text-2xl font-semibold mb-4">