Extract PlayerCharacterSection component from App.tsx
Move player character modal state (createPlayerOpen, managementOpen, editingPlayer) into a self-contained component with an imperative ref handle. Closing the create/edit modal now returns to management. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -17,8 +17,10 @@ import {
|
|||||||
} from "react";
|
} from "react";
|
||||||
import { ActionBar } from "./components/action-bar";
|
import { ActionBar } from "./components/action-bar";
|
||||||
import { CombatantRow } from "./components/combatant-row";
|
import { CombatantRow } from "./components/combatant-row";
|
||||||
import { CreatePlayerModal } from "./components/create-player-modal";
|
import {
|
||||||
import { PlayerManagement } from "./components/player-management";
|
PlayerCharacterSection,
|
||||||
|
type PlayerCharacterSectionHandle,
|
||||||
|
} from "./components/player-character-section";
|
||||||
import { StatBlockPanel } from "./components/stat-block-panel";
|
import { StatBlockPanel } from "./components/stat-block-panel";
|
||||||
import { Toast } from "./components/toast";
|
import { Toast } from "./components/toast";
|
||||||
import { TurnNavigation } from "./components/turn-navigation";
|
import { TurnNavigation } from "./components/turn-navigation";
|
||||||
@@ -97,12 +99,6 @@ export function App() {
|
|||||||
deleteCharacter: deletePlayerCharacter,
|
deleteCharacter: deletePlayerCharacter,
|
||||||
} = usePlayerCharacters();
|
} = usePlayerCharacters();
|
||||||
|
|
||||||
const [createPlayerOpen, setCreatePlayerOpen] = useState(false);
|
|
||||||
const [managementOpen, setManagementOpen] = useState(false);
|
|
||||||
const [editingPlayer, setEditingPlayer] = useState<
|
|
||||||
(typeof playerCharacters)[number] | undefined
|
|
||||||
>(undefined);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
search,
|
search,
|
||||||
getCreature,
|
getCreature,
|
||||||
@@ -184,6 +180,7 @@ export function App() {
|
|||||||
}, [sidePanel.dismissPanel, bulkImport.reset]);
|
}, [sidePanel.dismissPanel, bulkImport.reset]);
|
||||||
|
|
||||||
const actionBarInputRef = useRef<HTMLInputElement>(null);
|
const actionBarInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const playerCharacterRef = useRef<PlayerCharacterSectionHandle>(null);
|
||||||
const actionBarAnim = useActionBarAnimation(encounter.combatants.length);
|
const actionBarAnim = useActionBarAnimation(encounter.combatants.length);
|
||||||
|
|
||||||
// Auto-scroll to the active combatant when the turn changes
|
// Auto-scroll to the active combatant when the turn changes
|
||||||
@@ -256,7 +253,9 @@ export function App() {
|
|||||||
inputRef={actionBarInputRef}
|
inputRef={actionBarInputRef}
|
||||||
playerCharacters={playerCharacters}
|
playerCharacters={playerCharacters}
|
||||||
onAddFromPlayerCharacter={addFromPlayerCharacter}
|
onAddFromPlayerCharacter={addFromPlayerCharacter}
|
||||||
onManagePlayers={() => setManagementOpen(true)}
|
onManagePlayers={() =>
|
||||||
|
playerCharacterRef.current?.openManagement()
|
||||||
|
}
|
||||||
onRollAllInitiative={handleRollAllInitiative}
|
onRollAllInitiative={handleRollAllInitiative}
|
||||||
showRollAllInitiative={hasCreatureCombatants}
|
showRollAllInitiative={hasCreatureCombatants}
|
||||||
rollAllInitiativeDisabled={!canRollAllInitiative}
|
rollAllInitiativeDisabled={!canRollAllInitiative}
|
||||||
@@ -313,7 +312,9 @@ export function App() {
|
|||||||
inputRef={actionBarInputRef}
|
inputRef={actionBarInputRef}
|
||||||
playerCharacters={playerCharacters}
|
playerCharacters={playerCharacters}
|
||||||
onAddFromPlayerCharacter={addFromPlayerCharacter}
|
onAddFromPlayerCharacter={addFromPlayerCharacter}
|
||||||
onManagePlayers={() => setManagementOpen(true)}
|
onManagePlayers={() =>
|
||||||
|
playerCharacterRef.current?.openManagement()
|
||||||
|
}
|
||||||
onRollAllInitiative={handleRollAllInitiative}
|
onRollAllInitiative={handleRollAllInitiative}
|
||||||
showRollAllInitiative={hasCreatureCombatants}
|
showRollAllInitiative={hasCreatureCombatants}
|
||||||
rollAllInitiativeDisabled={!canRollAllInitiative}
|
rollAllInitiativeDisabled={!canRollAllInitiative}
|
||||||
@@ -403,43 +404,12 @@ export function App() {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<CreatePlayerModal
|
<PlayerCharacterSection
|
||||||
open={createPlayerOpen}
|
ref={playerCharacterRef}
|
||||||
onClose={() => {
|
|
||||||
setCreatePlayerOpen(false);
|
|
||||||
setEditingPlayer(undefined);
|
|
||||||
}}
|
|
||||||
onSave={(name, ac, maxHp, color, icon) => {
|
|
||||||
if (editingPlayer) {
|
|
||||||
editPlayerCharacter?.(editingPlayer.id, {
|
|
||||||
name,
|
|
||||||
ac,
|
|
||||||
maxHp,
|
|
||||||
color: color ?? null,
|
|
||||||
icon: icon ?? null,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
createPlayerCharacter(name, ac, maxHp, color, icon);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
playerCharacter={editingPlayer}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<PlayerManagement
|
|
||||||
open={managementOpen}
|
|
||||||
onClose={() => setManagementOpen(false)}
|
|
||||||
characters={playerCharacters}
|
characters={playerCharacters}
|
||||||
onEdit={(pc) => {
|
onCreateCharacter={createPlayerCharacter}
|
||||||
setEditingPlayer(pc);
|
onEditCharacter={editPlayerCharacter}
|
||||||
setCreatePlayerOpen(true);
|
onDeleteCharacter={deletePlayerCharacter}
|
||||||
setManagementOpen(false);
|
|
||||||
}}
|
|
||||||
onDelete={(id) => deletePlayerCharacter?.(id)}
|
|
||||||
onCreate={() => {
|
|
||||||
setEditingPlayer(undefined);
|
|
||||||
setCreatePlayerOpen(true);
|
|
||||||
setManagementOpen(false);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
91
apps/web/src/components/player-character-section.tsx
Normal file
91
apps/web/src/components/player-character-section.tsx
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
import type { PlayerCharacter, PlayerCharacterId } from "@initiative/domain";
|
||||||
|
import { forwardRef, useImperativeHandle, useState } from "react";
|
||||||
|
import { CreatePlayerModal } from "./create-player-modal.js";
|
||||||
|
import { PlayerManagement } from "./player-management.js";
|
||||||
|
|
||||||
|
export interface PlayerCharacterSectionHandle {
|
||||||
|
openManagement: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PlayerCharacterSectionProps {
|
||||||
|
characters: readonly PlayerCharacter[];
|
||||||
|
onCreateCharacter: (
|
||||||
|
name: string,
|
||||||
|
ac: number,
|
||||||
|
maxHp: number,
|
||||||
|
color: string | undefined,
|
||||||
|
icon: string | undefined,
|
||||||
|
) => void;
|
||||||
|
onEditCharacter: (
|
||||||
|
id: PlayerCharacterId,
|
||||||
|
fields: {
|
||||||
|
name?: string;
|
||||||
|
ac?: number;
|
||||||
|
maxHp?: number;
|
||||||
|
color?: string | null;
|
||||||
|
icon?: string | null;
|
||||||
|
},
|
||||||
|
) => void;
|
||||||
|
onDeleteCharacter: (id: PlayerCharacterId) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PlayerCharacterSection = forwardRef<
|
||||||
|
PlayerCharacterSectionHandle,
|
||||||
|
PlayerCharacterSectionProps
|
||||||
|
>(function PlayerCharacterSection(
|
||||||
|
{ characters, onCreateCharacter, onEditCharacter, onDeleteCharacter },
|
||||||
|
ref,
|
||||||
|
) {
|
||||||
|
const [managementOpen, setManagementOpen] = useState(false);
|
||||||
|
const [createOpen, setCreateOpen] = useState(false);
|
||||||
|
const [editingPlayer, setEditingPlayer] = useState<
|
||||||
|
PlayerCharacter | undefined
|
||||||
|
>();
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
openManagement: () => setManagementOpen(true),
|
||||||
|
}));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<CreatePlayerModal
|
||||||
|
open={createOpen}
|
||||||
|
onClose={() => {
|
||||||
|
setCreateOpen(false);
|
||||||
|
setEditingPlayer(undefined);
|
||||||
|
setManagementOpen(true);
|
||||||
|
}}
|
||||||
|
onSave={(name, ac, maxHp, color, icon) => {
|
||||||
|
if (editingPlayer) {
|
||||||
|
onEditCharacter(editingPlayer.id, {
|
||||||
|
name,
|
||||||
|
ac,
|
||||||
|
maxHp,
|
||||||
|
color: color ?? null,
|
||||||
|
icon: icon ?? null,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
onCreateCharacter(name, ac, maxHp, color, icon);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
playerCharacter={editingPlayer}
|
||||||
|
/>
|
||||||
|
<PlayerManagement
|
||||||
|
open={managementOpen}
|
||||||
|
onClose={() => setManagementOpen(false)}
|
||||||
|
characters={characters}
|
||||||
|
onEdit={(pc) => {
|
||||||
|
setEditingPlayer(pc);
|
||||||
|
setCreateOpen(true);
|
||||||
|
setManagementOpen(false);
|
||||||
|
}}
|
||||||
|
onDelete={(id) => onDeleteCharacter(id)}
|
||||||
|
onCreate={() => {
|
||||||
|
setEditingPlayer(undefined);
|
||||||
|
setCreateOpen(true);
|
||||||
|
setManagementOpen(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user