feat(UI): collapsible Campaigns section

Campaigns heading -> clickable button. ChevronDown expanded,
ChevronRight collapsed. Toggles grid visibility. Shows count
(e.g. 'Campaigns (61)'). Preserves Create Campaign button.

State: campaignsCollapsed in AdminView. aria-expanded/controls for a11y.
This commit is contained in:
david raistrick
2026-07-02 11:17:04 -04:00
parent c0998da0a7
commit 4b8b8bccfb
+24 -8
View File
@@ -8,7 +8,7 @@ import {
UserCheck, UserX, HeartCrack, HeartPulse, Zap, EyeOff, ExternalLink, AlertTriangle, UserCheck, UserX, HeartCrack, HeartPulse, Zap, EyeOff, ExternalLink, AlertTriangle,
Play as PlayIcon, Pause as PauseIcon, SkipForward as SkipForwardIcon, Play as PlayIcon, Pause as PauseIcon, SkipForward as SkipForwardIcon,
StopCircle as StopCircleIcon, Users2, Dices, ChevronUp, ChevronDown, ScrollText, StopCircle as StopCircleIcon, Users2, Dices, ChevronUp, ChevronDown, ScrollText,
Maximize2, Minimize2, Moon, Coffee, Clock Maximize2, Minimize2, Moon, Coffee, Clock, ChevronRight
} from 'lucide-react'; } from 'lucide-react';
// Custom CSS for death animation (player view only) // Custom CSS for death animation (player view only)
@@ -2195,6 +2195,7 @@ function AdminView({ userId }) {
const [showCreateModal, setShowCreateModal] = useState(false); const [showCreateModal, setShowCreateModal] = useState(false);
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
const [itemToDelete, setItemToDelete] = useState(null); const [itemToDelete, setItemToDelete] = useState(null);
const [campaignsCollapsed, setCampaignsCollapsed] = useState(false);
useEffect(() => { useEffect(() => {
if (campaignsData && db) { if (campaignsData && db) {
@@ -2322,7 +2323,18 @@ function AdminView({ userId }) {
<div className="space-y-6"> <div className="space-y-6">
<div> <div>
<div className="flex justify-between items-center mb-4"> <div className="flex justify-between items-center mb-4">
<h2 className="text-2xl font-semibold text-amber-300 font-cinzel tracking-wide">Campaigns</h2> <button
onClick={() => setCampaignsCollapsed(c => !c)}
className="flex items-center gap-2 text-2xl font-semibold text-amber-300 font-cinzel tracking-wide hover:text-amber-200 transition-colors"
aria-expanded={!campaignsCollapsed}
aria-controls="campaigns-grid"
>
{campaignsCollapsed
? <ChevronRight size={24} />
: <ChevronDown size={24} />}
Campaigns
<span className="text-sm font-normal text-stone-400">({campaignsWithDetails.length})</span>
</button>
<button <button
onClick={() => setShowCreateModal(true)} onClick={() => setShowCreateModal(true)}
className="bg-red-700 hover:bg-red-800 text-white font-bold py-2 px-4 rounded-lg flex items-center transition-colors" className="bg-red-700 hover:bg-red-800 text-white font-bold py-2 px-4 rounded-lg flex items-center transition-colors"
@@ -2331,12 +2343,14 @@ function AdminView({ userId }) {
</button> </button>
</div> </div>
{campaignsWithDetails.length === 0 && !isLoadingCampaigns && ( {!campaignsCollapsed && (
<p className="text-stone-400">No campaigns yet. Create one to get started!</p> <>
)} {campaignsWithDetails.length === 0 && !isLoadingCampaigns && (
<p className="text-stone-400">No campaigns yet. Create one to get started!</p>
)}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> <div id="campaigns-grid" className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{campaignsWithDetails.map(campaign => { {campaignsWithDetails.map(campaign => {
const cardStyle = campaign.playerDisplayBackgroundUrl const cardStyle = campaign.playerDisplayBackgroundUrl
? { backgroundImage: `url(${campaign.playerDisplayBackgroundUrl})` } ? { backgroundImage: `url(${campaign.playerDisplayBackgroundUrl})` }
: {}; : {};
@@ -2382,7 +2396,9 @@ function AdminView({ userId }) {
</div> </div>
); );
})} })}
</div> </div>
</>
)}
</div> </div>
{showCreateModal && ( {showCreateModal && (