diff --git a/web/src/index.css b/web/src/index.css index ae7c55b..00f691e 100644 --- a/web/src/index.css +++ b/web/src/index.css @@ -542,12 +542,34 @@ button:disabled { } /* Rules Page */ +.rules-search { + width: 100%; + padding: var(--spacing-sm) var(--spacing-md); + margin-bottom: var(--spacing-sm); + border: 1px solid var(--color-border); + border-radius: var(--radius); + background: var(--color-surface); + color: var(--color-text); + font-size: 1rem; +} + +.rules-search::placeholder { + color: var(--color-text-muted); +} + .rules-actions { display: flex; + align-items: center; gap: var(--spacing-sm); margin-bottom: var(--spacing-md); } +.rules-count { + font-size: 0.85rem; + color: var(--color-text-muted); + margin-left: auto; +} + .rules-sections { display: flex; flex-direction: column; diff --git a/web/src/pages/RulesPage.tsx b/web/src/pages/RulesPage.tsx index b25546f..3ffacf8 100644 --- a/web/src/pages/RulesPage.tsx +++ b/web/src/pages/RulesPage.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useState, useMemo } from 'react'; import Markdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; import TopBar from '../components/layout/TopBar'; @@ -7,6 +7,15 @@ import { battleReferenceSections } from '../rules/battle-reference'; export default function RulesPage() { const [openSections, setOpenSections] = useState>(new Set()); + const [search, setSearch] = useState(''); + + const filtered = useMemo(() => { + if (!search.trim()) return battleReferenceSections.map((s, i) => ({ ...s, originalIndex: i })); + const q = search.toLowerCase(); + return battleReferenceSections + .map((s, i) => ({ ...s, originalIndex: i })) + .filter((s) => s.title.toLowerCase().includes(q) || s.content.toLowerCase().includes(q)); + }, [search]); const toggle = (index: number) => { setOpenSections((prev) => { @@ -21,7 +30,7 @@ export default function RulesPage() { }; const expandAll = () => { - setOpenSections(new Set(battleReferenceSections.map((_, i) => i))); + setOpenSections(new Set(filtered.map((s) => s.originalIndex))); }; const collapseAll = () => { @@ -32,6 +41,14 @@ export default function RulesPage() { <> + setSearch(e.target.value)} + /> +
+ {search.trim() && ( + {filtered.length} result{filtered.length !== 1 ? 's' : ''} + )}
- {battleReferenceSections.map((section, i) => ( -
- { e.preventDefault(); toggle(i); }}> + {filtered.map((section) => ( +
+ { e.preventDefault(); toggle(section.originalIndex); }}> {section.title}
@@ -59,6 +79,11 @@ export default function RulesPage() {
))} + {filtered.length === 0 && ( +

+ No sections match "{search}". +

+ )}