Extract useSidePanelState hook from App.tsx
Move panel view state, fold/pin state, isWideDesktop media query, and all related handlers into a dedicated hook, reducing App.tsx by ~80 lines of state management boilerplate. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
101
apps/web/src/hooks/use-side-panel-state.ts
Normal file
101
apps/web/src/hooks/use-side-panel-state.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import type { CreatureId } from "@initiative/domain";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
|
||||
type PanelView =
|
||||
| { mode: "closed" }
|
||||
| { mode: "creature"; creatureId: CreatureId }
|
||||
| { mode: "bulk-import" }
|
||||
| { mode: "source-manager" };
|
||||
|
||||
interface SidePanelState {
|
||||
panelView: PanelView;
|
||||
selectedCreatureId: CreatureId | null;
|
||||
bulkImportMode: boolean;
|
||||
sourceManagerMode: boolean;
|
||||
isRightPanelFolded: boolean;
|
||||
pinnedCreatureId: CreatureId | null;
|
||||
isWideDesktop: boolean;
|
||||
}
|
||||
|
||||
interface SidePanelActions {
|
||||
showCreature: (creatureId: CreatureId) => void;
|
||||
showBulkImport: () => void;
|
||||
showSourceManager: () => void;
|
||||
dismissPanel: () => void;
|
||||
toggleFold: () => void;
|
||||
togglePin: () => void;
|
||||
unpin: () => void;
|
||||
}
|
||||
|
||||
export function useSidePanelState(): SidePanelState & SidePanelActions {
|
||||
const [panelView, setPanelView] = useState<PanelView>({ mode: "closed" });
|
||||
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 selectedCreatureId =
|
||||
panelView.mode === "creature" ? panelView.creatureId : null;
|
||||
|
||||
const showCreature = useCallback((creatureId: CreatureId) => {
|
||||
setPanelView({ mode: "creature", creatureId });
|
||||
setIsRightPanelFolded(false);
|
||||
}, []);
|
||||
|
||||
const showBulkImport = useCallback(() => {
|
||||
setPanelView({ mode: "bulk-import" });
|
||||
setIsRightPanelFolded(false);
|
||||
}, []);
|
||||
|
||||
const showSourceManager = useCallback(() => {
|
||||
setPanelView({ mode: "source-manager" });
|
||||
setIsRightPanelFolded(false);
|
||||
}, []);
|
||||
|
||||
const dismissPanel = useCallback(() => {
|
||||
setPanelView({ mode: "closed" });
|
||||
}, []);
|
||||
|
||||
const toggleFold = useCallback(() => {
|
||||
setIsRightPanelFolded((f) => !f);
|
||||
}, []);
|
||||
|
||||
const togglePin = useCallback(() => {
|
||||
if (selectedCreatureId) {
|
||||
setPinnedCreatureId((prev) =>
|
||||
prev === selectedCreatureId ? null : selectedCreatureId,
|
||||
);
|
||||
}
|
||||
}, [selectedCreatureId]);
|
||||
|
||||
const unpin = useCallback(() => {
|
||||
setPinnedCreatureId(null);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
panelView,
|
||||
selectedCreatureId,
|
||||
bulkImportMode: panelView.mode === "bulk-import",
|
||||
sourceManagerMode: panelView.mode === "source-manager",
|
||||
isRightPanelFolded,
|
||||
pinnedCreatureId,
|
||||
isWideDesktop,
|
||||
showCreature,
|
||||
showBulkImport,
|
||||
showSourceManager,
|
||||
dismissPanel,
|
||||
toggleFold,
|
||||
togglePin,
|
||||
unpin,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user