setCustomType(v.toLowerCase())}
+ />
diff --git a/client/src/components/InfoPanel.tsx b/client/src/components/InfoPanel.tsx
index 8579e78..a91624e 100644
--- a/client/src/components/InfoPanel.tsx
+++ b/client/src/components/InfoPanel.tsx
@@ -1,6 +1,7 @@
import { useRef, useEffect } from "react";
import type { Character } from "../types";
import TalentList from "./TalentList";
+import SelectDropdown from "./SelectDropdown";
import styles from "./InfoPanel.module.css";
const CLASSES = ["Fighter", "Priest", "Thief", "Wizard"];
@@ -100,31 +101,19 @@ export default function InfoPanel({
-
+ options={CLASSES}
+ onChange={(v) => handleField("class", v)}
+ />
-
+ options={ANCESTRIES}
+ onChange={(v) => handleField("ancestry", v)}
+ />
@@ -140,17 +129,11 @@ export default function InfoPanel({
-
+ options={ALIGNMENTS}
+ onChange={(v) => handleField("alignment", v)}
+ />
diff --git a/client/src/components/SelectDropdown.module.css b/client/src/components/SelectDropdown.module.css
new file mode 100644
index 0000000..99c6d09
--- /dev/null
+++ b/client/src/components/SelectDropdown.module.css
@@ -0,0 +1,74 @@
+.container {
+ position: relative;
+ width: 100%;
+}
+
+.trigger {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ width: 100%;
+ padding: 0.3rem 0.5rem;
+ background: var(--bg-inset);
+ border: 1px solid rgba(var(--gold-rgb), 0.15);
+ border-radius: 4px;
+ color: var(--text-primary);
+ font-size: 0.85rem;
+ font-family: "Alegreya", Georgia, serif;
+ cursor: pointer;
+ text-align: left;
+}
+
+.trigger:hover {
+ border-color: rgba(var(--gold-rgb), 0.35);
+}
+
+.arrow {
+ font-size: 0.65rem;
+ color: var(--text-tertiary);
+ margin-left: 0.5rem;
+}
+
+.dropdown {
+ position: absolute;
+ top: 100%;
+ left: 0;
+ right: 0;
+ margin-top: 0.2rem;
+ background-color: var(--bg-modal);
+ background-image: var(--texture-surface);
+ background-size: 256px 256px;
+ background-repeat: repeat;
+ border: 1px solid rgba(var(--gold-rgb), 0.3);
+ border-radius: 4px;
+ z-index: 200;
+ max-height: 180px;
+ overflow-y: auto;
+ box-shadow:
+ 0 4px 16px rgba(var(--shadow-rgb), 0.5),
+ inset 0 1px 0 rgba(var(--gold-rgb), 0.06);
+ scrollbar-width: thin;
+ scrollbar-color: rgba(var(--gold-rgb), 0.15) transparent;
+}
+
+.option {
+ display: block;
+ width: 100%;
+ padding: 0.35rem 0.6rem;
+ background: none;
+ border: none;
+ color: var(--text-primary);
+ font-size: 0.85rem;
+ font-family: "Alegreya", Georgia, serif;
+ cursor: pointer;
+ text-align: left;
+}
+
+.option:hover {
+ background: rgba(var(--gold-rgb), 0.12);
+}
+
+.option.active {
+ color: var(--gold);
+ font-weight: 600;
+}
diff --git a/client/src/components/SelectDropdown.tsx b/client/src/components/SelectDropdown.tsx
new file mode 100644
index 0000000..535e44f
--- /dev/null
+++ b/client/src/components/SelectDropdown.tsx
@@ -0,0 +1,63 @@
+import { useState, useRef, useEffect } from "react";
+import styles from "./SelectDropdown.module.css";
+
+interface SelectDropdownProps {
+ value: string;
+ options: string[];
+ onChange: (value: string) => void;
+ className?: string;
+}
+
+export default function SelectDropdown({
+ value,
+ options,
+ onChange,
+ className,
+}: SelectDropdownProps) {
+ const [open, setOpen] = useState(false);
+ const containerRef = useRef
(null);
+
+ useEffect(() => {
+ if (!open) return;
+ function handleClickOutside(e: MouseEvent) {
+ if (
+ containerRef.current &&
+ !containerRef.current.contains(e.target as Node)
+ ) {
+ setOpen(false);
+ }
+ }
+ document.addEventListener("mousedown", handleClickOutside);
+ return () => document.removeEventListener("mousedown", handleClickOutside);
+ }, [open]);
+
+ return (
+
+
+ {open && (
+
+ {options.map((opt) => (
+
+ ))}
+
+ )}
+
+ );
+}
diff --git a/client/src/pages/CampaignView.tsx b/client/src/pages/CampaignView.tsx
index 83cda54..ea455d8 100644
--- a/client/src/pages/CampaignView.tsx
+++ b/client/src/pages/CampaignView.tsx
@@ -19,6 +19,7 @@ import CharacterCard from "../components/CharacterCard";
import CharacterDetail from "../components/CharacterDetail";
import RollLog from "../components/RollLog";
import DiceTray from "../components/DiceTray";
+import SelectDropdown from "../components/SelectDropdown";
import styles from "./CampaignView.module.css";
const CLASSES = ["Fighter", "Priest", "Thief", "Wizard"];
@@ -400,35 +401,19 @@ export default function CampaignView() {
-
+ options={CLASSES}
+ onChange={(v) => setNewChar({ ...newChar, class: v })}
+ />
-
+ options={ANCESTRIES}
+ onChange={(v) => setNewChar({ ...newChar, ancestry: v })}
+ />