Implement stat block panel fold/unfold and pin-to-second-panel
Replace the close button and heading with fold/unfold controls that collapse the panel to a slim right-edge tab showing the creature name vertically, and add a pin button (xl+ viewports with creature loaded) that opens the creature in a second left-side panel for simultaneous reference. Fold state is respected on turn change. 19 acceptance tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -53,11 +53,29 @@ export function App() {
|
||||
useState<CreatureId | null>(null);
|
||||
const [bulkImportMode, setBulkImportMode] = useState(false);
|
||||
const [sourceManagerOpen, setSourceManagerOpen] = useState(false);
|
||||
const [isRightPanelFolded, setIsRightPanelFolded] = useState(false);
|
||||
const [pinnedCreatureId, setPinnedCreatureId] = useState<CreatureId | null>(
|
||||
null,
|
||||
);
|
||||
const [isWideDesktop, setIsWideDesktop] = useState(
|
||||
() => window.matchMedia("(min-width: 1280px)").matches,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const mq = window.matchMedia("(min-width: 1280px)");
|
||||
const handler = (e: MediaQueryListEvent) => setIsWideDesktop(e.matches);
|
||||
mq.addEventListener("change", handler);
|
||||
return () => mq.removeEventListener("change", handler);
|
||||
}, []);
|
||||
|
||||
const selectedCreature: Creature | null = selectedCreatureId
|
||||
? (getCreature(selectedCreatureId) ?? null)
|
||||
: null;
|
||||
|
||||
const pinnedCreature: Creature | null = pinnedCreatureId
|
||||
? (getCreature(pinnedCreatureId) ?? null)
|
||||
: null;
|
||||
|
||||
const handleAddFromBestiary = useCallback(
|
||||
(result: SearchResult) => {
|
||||
addFromBestiary(result);
|
||||
@@ -75,6 +93,7 @@ export function App() {
|
||||
|
||||
const handleCombatantStatBlock = useCallback((creatureId: string) => {
|
||||
setSelectedCreatureId(creatureId as CreatureId);
|
||||
setIsRightPanelFolded(false);
|
||||
}, []);
|
||||
|
||||
const handleRollInitiative = useCallback(
|
||||
@@ -110,6 +129,25 @@ export function App() {
|
||||
bulkImport.reset();
|
||||
}, [bulkImport.reset]);
|
||||
|
||||
const handleDismissBrowsePanel = useCallback(() => {
|
||||
setSelectedCreatureId(null);
|
||||
setBulkImportMode(false);
|
||||
}, []);
|
||||
|
||||
const handleToggleFold = useCallback(() => {
|
||||
setIsRightPanelFolded((f) => !f);
|
||||
}, []);
|
||||
|
||||
const handlePin = useCallback(() => {
|
||||
if (selectedCreatureId) {
|
||||
setPinnedCreatureId(selectedCreatureId);
|
||||
}
|
||||
}, [selectedCreatureId]);
|
||||
|
||||
const handleUnpin = useCallback(() => {
|
||||
setPinnedCreatureId(null);
|
||||
}, []);
|
||||
|
||||
// Auto-scroll to the active combatant when the turn changes
|
||||
const activeRowRef = useRef<HTMLDivElement>(null);
|
||||
useEffect(() => {
|
||||
@@ -202,7 +240,27 @@ export function App() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Stat Block Panel */}
|
||||
{/* Pinned Stat Block Panel (left) */}
|
||||
{pinnedCreatureId && isWideDesktop && (
|
||||
<StatBlockPanel
|
||||
creatureId={pinnedCreatureId}
|
||||
creature={pinnedCreature}
|
||||
isSourceCached={isSourceCached}
|
||||
fetchAndCacheSource={fetchAndCacheSource}
|
||||
uploadAndCacheSource={uploadAndCacheSource}
|
||||
refreshCache={refreshCache}
|
||||
panelRole="pinned"
|
||||
isFolded={false}
|
||||
onToggleFold={() => {}}
|
||||
onPin={() => {}}
|
||||
onUnpin={handleUnpin}
|
||||
showPinButton={false}
|
||||
side="left"
|
||||
onDismiss={() => {}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Browse Stat Block Panel (right) */}
|
||||
<StatBlockPanel
|
||||
creatureId={selectedCreatureId}
|
||||
creature={selectedCreature}
|
||||
@@ -210,10 +268,14 @@ export function App() {
|
||||
fetchAndCacheSource={fetchAndCacheSource}
|
||||
uploadAndCacheSource={uploadAndCacheSource}
|
||||
refreshCache={refreshCache}
|
||||
onClose={() => {
|
||||
setSelectedCreatureId(null);
|
||||
setBulkImportMode(false);
|
||||
}}
|
||||
panelRole="browse"
|
||||
isFolded={isRightPanelFolded}
|
||||
onToggleFold={handleToggleFold}
|
||||
onPin={handlePin}
|
||||
onUnpin={() => {}}
|
||||
showPinButton={isWideDesktop && !!selectedCreature}
|
||||
side="right"
|
||||
onDismiss={handleDismissBrowsePanel}
|
||||
bulkImportMode={bulkImportMode}
|
||||
bulkImportState={bulkImport.state}
|
||||
onStartBulkImport={handleStartBulkImport}
|
||||
|
||||
Reference in New Issue
Block a user