refactor: Split CharacterSheet.tsx into separate components
This commit is contained in:
232
src/components/ClassesPage.tsx
Normal file
232
src/components/ClassesPage.tsx
Normal file
@@ -0,0 +1,232 @@
|
||||
import React from "react";
|
||||
import { ClassEntry } from "../types";
|
||||
import { autoResize } from "../utils";
|
||||
|
||||
interface ClassesPageProps {
|
||||
isActive: boolean;
|
||||
primaryClasses: ClassEntry[];
|
||||
setPrimaryClasses: React.Dispatch<React.SetStateAction<ClassEntry[]>>;
|
||||
otherClasses: ClassEntry[];
|
||||
setOtherClasses: React.Dispatch<React.SetStateAction<ClassEntry[]>>;
|
||||
heroicSkills: string;
|
||||
onHeroicSkillsChange: (v: string) => void;
|
||||
}
|
||||
|
||||
export default function ClassesPage({
|
||||
isActive,
|
||||
primaryClasses,
|
||||
setPrimaryClasses,
|
||||
otherClasses,
|
||||
setOtherClasses,
|
||||
heroicSkills,
|
||||
onHeroicSkillsChange,
|
||||
}: ClassesPageProps) {
|
||||
return (
|
||||
<div className={`page${isActive ? " active" : ""}`} id="page-classes">
|
||||
<div className="grid-2" style={{ marginBottom: 20 }}>
|
||||
<div className="section">
|
||||
<div className="section-title">
|
||||
<span className="icon">✦</span> Primary Classes (up to 3)
|
||||
</div>
|
||||
{primaryClasses.map((cls, idx) => (
|
||||
<div key={idx} className="class-block">
|
||||
<div className="class-header">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Class name…"
|
||||
value={cls.name || ""}
|
||||
onChange={(e) =>
|
||||
setPrimaryClasses((prev) =>
|
||||
prev.map((c, i) => (i === idx ? { ...c, name: e.target.value } : c))
|
||||
)
|
||||
}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Free benefits…"
|
||||
value={cls.benefits || ""}
|
||||
onChange={(e) =>
|
||||
setPrimaryClasses((prev) =>
|
||||
prev.map((c, i) => (i === idx ? { ...c, benefits: e.target.value } : c))
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="class-skills">
|
||||
<textarea
|
||||
placeholder="Skill information…"
|
||||
value={cls.skills || ""}
|
||||
ref={(el) => {
|
||||
if (el) autoResize(el);
|
||||
}}
|
||||
onInput={(e) => autoResize(e.currentTarget)}
|
||||
onChange={(e) =>
|
||||
setPrimaryClasses((prev) =>
|
||||
prev.map((c, i) => (i === idx ? { ...c, skills: e.target.value } : c))
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
padding: "6px 10px",
|
||||
borderTop: "1px solid var(--border)",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<label
|
||||
style={{ display: "flex", alignItems: "center", gap: "6px", fontSize: "0.85em" }}
|
||||
>
|
||||
Level
|
||||
<input
|
||||
type="number"
|
||||
min={1}
|
||||
max={10}
|
||||
value={cls.level ?? 1}
|
||||
onChange={(e) =>
|
||||
setPrimaryClasses((prev) =>
|
||||
prev.map((c, i) =>
|
||||
i === idx ? { ...c, level: Number(e.target.value) } : c
|
||||
)
|
||||
)
|
||||
}
|
||||
style={{ width: "50px" }}
|
||||
/>
|
||||
</label>
|
||||
<button
|
||||
type="button"
|
||||
className="spell-del-btn"
|
||||
onClick={() =>
|
||||
setPrimaryClasses((prev) => prev.filter((_, i) => i !== idx))
|
||||
}
|
||||
>
|
||||
✕ Remove
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<button
|
||||
type="button"
|
||||
className="add-btn"
|
||||
disabled={primaryClasses.length >= 3}
|
||||
onClick={() =>
|
||||
setPrimaryClasses((prev) => [
|
||||
...prev,
|
||||
{ name: "", level: 1, benefits: "", skills: "" },
|
||||
])
|
||||
}
|
||||
>
|
||||
+ Add Primary Class
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="section">
|
||||
<div className="section-title">
|
||||
<span className="icon">◎</span> Other Classes (max 3 non-mastered)
|
||||
</div>
|
||||
{otherClasses.map((cls, idx) => (
|
||||
<div key={idx} className="class-block">
|
||||
<div className="class-header">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Class name…"
|
||||
value={cls.name || ""}
|
||||
onChange={(e) =>
|
||||
setOtherClasses((prev) =>
|
||||
prev.map((c, i) => (i === idx ? { ...c, name: e.target.value } : c))
|
||||
)
|
||||
}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Free benefits…"
|
||||
value={cls.benefits || ""}
|
||||
onChange={(e) =>
|
||||
setOtherClasses((prev) =>
|
||||
prev.map((c, i) => (i === idx ? { ...c, benefits: e.target.value } : c))
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="class-skills">
|
||||
<textarea
|
||||
placeholder="Skill information…"
|
||||
value={cls.skills || ""}
|
||||
onChange={(e) =>
|
||||
setOtherClasses((prev) =>
|
||||
prev.map((c, i) => (i === idx ? { ...c, skills: e.target.value } : c))
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
padding: "6px 10px",
|
||||
borderTop: "1px solid var(--border)",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<label
|
||||
style={{ display: "flex", alignItems: "center", gap: "6px", fontSize: "0.85em" }}
|
||||
>
|
||||
Level
|
||||
<input
|
||||
type="number"
|
||||
min={1}
|
||||
max={10}
|
||||
value={cls.level ?? 1}
|
||||
onChange={(e) =>
|
||||
setOtherClasses((prev) =>
|
||||
prev.map((c, i) =>
|
||||
i === idx ? { ...c, level: Number(e.target.value) } : c
|
||||
)
|
||||
)
|
||||
}
|
||||
style={{ width: "50px" }}
|
||||
/>
|
||||
</label>
|
||||
<button
|
||||
type="button"
|
||||
className="spell-del-btn"
|
||||
onClick={() =>
|
||||
setOtherClasses((prev) => prev.filter((_, i) => i !== idx))
|
||||
}
|
||||
>
|
||||
✕ Remove
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<button
|
||||
type="button"
|
||||
className="add-btn"
|
||||
onClick={() =>
|
||||
setOtherClasses((prev) => [
|
||||
...prev,
|
||||
{ name: "", level: 1, benefits: "", skills: "" },
|
||||
])
|
||||
}
|
||||
>
|
||||
+ Add Class
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="section">
|
||||
<div className="section-title">
|
||||
<span className="icon">★</span> Heroic Skills
|
||||
</div>
|
||||
<textarea
|
||||
value={heroicSkills}
|
||||
onChange={(e) => onHeroicSkillsChange(e.target.value)}
|
||||
placeholder="Record your heroic skill abilities here…"
|
||||
style={{ minHeight: 100 }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user