Files
initiative/apps/web/src/hooks/use-side-panel-state.ts
T
Lukas 09a801487d
CI / check (push) Successful in 2m32s
CI / build-image (push) Successful in 19s
Add PF2e weak/elite creature adjustments with stat block toggle
Weak/Normal/Elite toggle in PF2e stat block header applies standard
adjustments (level, AC, HP, saves, Perception, attacks, damage) to
individual combatants. Adjusted stats are highlighted blue (elite) or
red (weak). Persisted via creatureAdjustment field on Combatant.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 02:24:30 +02:00

119 lines
3.2 KiB
TypeScript

import type { CombatantId, CreatureId } from "@initiative/domain";
import { useCallback, useEffect, useState } from "react";
type PanelView =
| { mode: "closed" }
| { mode: "creature"; creatureId: CreatureId; combatantId?: CombatantId }
| { mode: "bulk-import" }
| { mode: "source-manager" };
interface SidePanelState {
panelView: PanelView;
selectedCreatureId: CreatureId | null;
selectedCombatantId: CombatantId | null;
bulkImportMode: boolean;
sourceManagerMode: boolean;
isRightPanelCollapsed: boolean;
pinnedCreatureId: CreatureId | null;
isWideDesktop: boolean;
}
interface SidePanelActions {
showCreature: (creatureId: CreatureId, combatantId?: CombatantId) => void;
updateCreature: (creatureId: CreatureId, combatantId?: CombatantId) => void;
showBulkImport: () => void;
showSourceManager: () => void;
dismissPanel: () => void;
toggleCollapse: () => void;
togglePin: () => void;
unpin: () => void;
}
export function useSidePanelState(): SidePanelState & SidePanelActions {
const [panelView, setPanelView] = useState<PanelView>({ mode: "closed" });
const [isRightPanelCollapsed, setIsRightPanelCollapsed] = useState(false);
const [pinnedCreatureId, setPinnedCreatureId] = useState<CreatureId | null>(
null,
);
const [isWideDesktop, setIsWideDesktop] = useState(
() => globalThis.matchMedia("(min-width: 1280px)").matches,
);
useEffect(() => {
const mq = globalThis.matchMedia("(min-width: 1280px)");
const handler = (e: MediaQueryListEvent) => setIsWideDesktop(e.matches);
mq.addEventListener("change", handler);
return () => mq.removeEventListener("change", handler);
}, []);
const selectedCreatureId =
panelView.mode === "creature" ? panelView.creatureId : null;
const selectedCombatantId =
panelView.mode === "creature" ? (panelView.combatantId ?? null) : null;
const showCreature = useCallback(
(creatureId: CreatureId, combatantId?: CombatantId) => {
setPanelView({ mode: "creature", creatureId, combatantId });
setIsRightPanelCollapsed(false);
},
[],
);
const updateCreature = useCallback(
(creatureId: CreatureId, combatantId?: CombatantId) => {
setPanelView({ mode: "creature", creatureId, combatantId });
},
[],
);
const showBulkImport = useCallback(() => {
setPanelView({ mode: "bulk-import" });
setIsRightPanelCollapsed(false);
}, []);
const showSourceManager = useCallback(() => {
setPanelView({ mode: "source-manager" });
setIsRightPanelCollapsed(false);
}, []);
const dismissPanel = useCallback(() => {
setPanelView({ mode: "closed" });
}, []);
const toggleCollapse = useCallback(() => {
setIsRightPanelCollapsed((f) => !f);
}, []);
const togglePin = useCallback(() => {
if (selectedCreatureId) {
setPinnedCreatureId((prev) =>
prev === selectedCreatureId ? null : selectedCreatureId,
);
}
}, [selectedCreatureId]);
const unpin = useCallback(() => {
setPinnedCreatureId(null);
}, []);
return {
panelView,
selectedCreatureId,
selectedCombatantId,
bulkImportMode: panelView.mode === "bulk-import",
sourceManagerMode: panelView.mode === "source-manager",
isRightPanelCollapsed,
pinnedCreatureId,
isWideDesktop,
showCreature,
updateCreature,
showBulkImport,
showSourceManager,
dismissPanel,
toggleCollapse,
togglePin,
unpin,
};
}