Character section rollup: clickable title, count, persisted collapse

Match campaigns rollup pattern. Title button toggles collapse (chevron
left, like campaigns). Character count in header. Collapse state persisted
to localStorage key ttrpg.charactersCollapsed.
This commit is contained in:
david raistrick
2026-07-07 14:51:45 -04:00
parent a5b6d61e83
commit 95db6fdfdc
2 changed files with 14 additions and 21 deletions
-13
View File
@@ -50,19 +50,6 @@ lots of updates
- `isActive = id => updatedParticipants.find(p => p.id === id && p.isActive)`
- Returns participant obj (truthy) not boolean. Works by accident, fragile.
### BUG: select campaign during active combat = screen flicker, no action
- In active encounter, click different campaign → flicker, no nav, no end.
- Either block (toast: end encounter first) or auto-end current + switch.
- Decide UX before fix.
### FEAT: generic/non-5e rules mode
- Campaign/encounter ruleset toggle: `5e` vs `generic`.
- Generic mode turns off death saves/state machine.
- Generic mode allows negative HP.
- 5e mode rejects negative damage/heal and clamps HP at 0.
### FEAT: clarify "Is NPC" in add-participant
- Ambiguous label. May expand work based on what NPC means here (ally? monster?
+14 -8
View File
@@ -5,7 +5,7 @@ import {
PlusCircle, Users, Swords, Trash2, Eye, Edit3, Save, XCircle, ChevronsUpDown,
UserCheck, UserX, HeartCrack, HeartPulse, Zap, EyeOff, ExternalLink, AlertTriangle,
Play as PlayIcon, Pause as PauseIcon, SkipForward as SkipForwardIcon,
StopCircle as StopCircleIcon, Users2, Dices, ChevronUp, ChevronDown, ScrollText,
StopCircle as StopCircleIcon, Users2, Dices, ChevronDown, ScrollText,
Maximize2, Minimize2, Moon, Coffee, Clock, ChevronRight, X,
Undo2, Redo2, Crosshair
} from 'lucide-react';
@@ -682,7 +682,14 @@ function CharacterManager({ campaignId, campaignCharacters }) {
const [editingCharacter, setEditingCharacter] = useState(null);
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
const [itemToDelete, setItemToDelete] = useState(null);
const [isOpen, setIsOpen] = useState(true);
const [isOpen, setIsOpen] = useState(() => {
try { return localStorage.getItem('ttrpg.charactersCollapsed') !== 'true'; }
catch { return true; }
});
useEffect(() => {
try { localStorage.setItem('ttrpg.charactersCollapsed', String(!isOpen)); }
catch {}
}, [isOpen]);
const handleAddCharacter = async () => {
if (!db || !characterName.trim() || !campaignId) return;
@@ -769,15 +776,14 @@ function CharacterManager({ campaignId, campaignCharacters }) {
<>
<div className="p-4 bg-stone-900 rounded-lg shadow">
<div className="flex justify-between items-center mb-3">
<h3 className="text-xl font-semibold text-amber-300 font-cinzel tracking-wide flex items-center">
<Users size={24} className="mr-2" /> Campaign Characters
</h3>
<button
onClick={() => setIsOpen(!isOpen)}
className="p-1 text-stone-400 hover:text-stone-200"
aria-label={isOpen ? "Collapse" : "Expand"}
className="flex items-center text-xl font-semibold text-amber-300 font-cinzel tracking-wide hover:text-amber-200 transition-colors"
aria-expanded={isOpen}
>
{isOpen ? <ChevronUp size={20} /> : <ChevronDown size={20} />}
{isOpen ? <ChevronDown size={20} className="mr-1" /> : <ChevronRight size={20} className="mr-1" />}
<Users size={24} className="mr-2" /> Campaign Characters
<span className="text-sm font-normal text-stone-400 ml-1">({campaignCharacters.length})</span>
</button>
</div>