feat: make team section a floating sidebar on desktop

Add responsive 2-column layout for the encounters page:
- Desktop (lg, ≥1024px): Encounters on left, team in sticky right sidebar
- Mobile/tablet: Keep current stacked layout

The sidebar scrolls independently when team exceeds viewport height.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-22 10:11:40 +01:00
parent ac0a04e71f
commit 3dbc3f35ba
2 changed files with 74 additions and 9 deletions

View File

@@ -1,11 +1,11 @@
---
# nuzlocke-tracker-lkro
title: 'UX: Make team section a floating sidebar on desktop'
status: todo
status: in-progress
type: feature
priority: normal
created_at: 2026-03-21T21:50:48Z
updated_at: 2026-03-22T08:08:13Z
updated_at: 2026-03-22T09:10:47Z
---
## Problem
@@ -28,9 +28,9 @@ Alternative: A floating action button (FAB) that opens the team in a slide-over
## Checklist
- [ ] Add responsive 2-column layout to RunEncounters page (desktop only)
- [ ] Move team section into a sticky sidebar column
- [ ] Ensure sidebar scrolls independently if team is taller than viewport
- [ ] Keep current stacked layout on mobile/tablet
- [x] Add responsive 2-column layout to RunEncounters page (desktop only)
- [x] Move team section into a sticky sidebar column
- [x] Ensure sidebar scrolls independently if team is taller than viewport
- [x] Keep current stacked layout on mobile/tablet
- [ ] Test with various team sizes (0-6 pokemon)
- [ ] Test evolution/nickname editing still works from sidebar

View File

@@ -922,7 +922,7 @@ export function RunEncounters() {
})
return (
<div className="max-w-4xl mx-auto p-8">
<div className="max-w-4xl lg:max-w-6xl mx-auto p-8">
{/* Header */}
<div className="mb-6">
<Link
@@ -1246,9 +1246,12 @@ export function RunEncounters() {
{/* Encounters Tab */}
{activeTab === 'encounters' && (
<>
{/* Team Section */}
<div className="lg:flex lg:gap-6">
{/* Main content column */}
<div className="flex-1 min-w-0">
{/* Team Section - Mobile/Tablet only */}
{(alive.length > 0 || dead.length > 0) && (
<div className="mb-6">
<div className="mb-6 lg:hidden">
<div className="flex items-center justify-between mb-3">
<button
type="button"
@@ -1690,6 +1693,68 @@ export function RunEncounters() {
)
})}
</div>
</div>
{/* Team Sidebar - Desktop only */}
{(alive.length > 0 || dead.length > 0) && (
<div className="hidden lg:block w-64 shrink-0">
<div className="sticky top-20 max-h-[calc(100vh-6rem)] overflow-y-auto">
<div className="bg-surface-1 border border-border-default rounded-lg p-4">
<div className="flex items-center justify-between mb-3">
<h2 className="text-lg font-semibold text-text-primary">
{isActive ? 'Team' : 'Final Team'}
</h2>
<span className="text-xs text-text-muted">
{alive.length}/{alive.length + dead.length}
</span>
</div>
{alive.length > 1 && (
<select
value={teamSort}
onChange={(e) => setTeamSort(e.target.value as TeamSortKey)}
className="w-full text-sm border border-border-default rounded-lg px-3 py-1.5 bg-surface-0 text-text-primary mb-3"
>
<option value="route">Route Order</option>
<option value="level">Catch Level</option>
<option value="species">Species Name</option>
<option value="dex">National Dex</option>
</select>
)}
{alive.length > 0 && (
<div className="grid grid-cols-2 gap-2 mb-3">
{alive.map((enc) => (
<PokemonCard
key={enc.id}
encounter={enc}
onClick={
isActive && canEdit ? () => setSelectedTeamEncounter(enc) : undefined
}
/>
))}
</div>
)}
{dead.length > 0 && (
<>
<h3 className="text-sm font-medium text-text-tertiary mb-2">Graveyard</h3>
<div className="grid grid-cols-2 gap-2">
{dead.map((enc) => (
<PokemonCard
key={enc.id}
encounter={enc}
showFaintLevel
onClick={
isActive && canEdit ? () => setSelectedTeamEncounter(enc) : undefined
}
/>
))}
</div>
</>
)}
</div>
</div>
</div>
)}
</div>
{/* Encounter Modal */}
{selectedRoute && (