141 lines
5.3 KiB
Markdown
141 lines
5.3 KiB
Markdown
# UI Contracts: Bestiary Search & Stat Block Display
|
|
|
|
**Branch**: `021-bestiary-statblock` | **Date**: 2026-03-06
|
|
|
|
## Component Contracts
|
|
|
|
### BestiarySearch
|
|
|
|
**Purpose**: Search input with autocomplete dropdown for creature selection.
|
|
|
|
**Props**:
|
|
- `onSelectCreature: (creature: Creature) => void` — Called when user selects a creature from results
|
|
- `onClose: () => void` — Called when search is dismissed (Escape, click outside)
|
|
|
|
**Behavior**:
|
|
- Opens focused on the search input
|
|
- Filters creatures by case-insensitive substring match on name
|
|
- Minimum 2 characters before showing results
|
|
- Maximum 10 results shown at a time
|
|
- Keyboard navigation: ArrowUp/ArrowDown to navigate, Enter to select, Escape to close
|
|
- Shows "No creatures found" for zero-match queries
|
|
- Shows source tag next to creature name (e.g., "Goblin (MM 2024)")
|
|
|
|
### StatBlock
|
|
|
|
**Purpose**: Renders a complete creature stat block.
|
|
|
|
**Props**:
|
|
- `creature: Creature` — The creature data to display
|
|
|
|
**Sections rendered** (in order, omitted if data absent):
|
|
1. Header: name, size, type, alignment
|
|
2. Stats bar: AC, HP (average + formula), Speed
|
|
3. Ability scores: STR/DEX/CON/INT/WIS/CHA with modifiers
|
|
4. Properties: Saving Throws, Skills, Damage Vulnerabilities, Damage Resistances, Damage Immunities, Condition Immunities, Senses, Languages, CR + Proficiency Bonus
|
|
5. Traits
|
|
6. Spellcasting (rendered as a separate section after traits)
|
|
7. Actions
|
|
8. Bonus Actions
|
|
9. Reactions
|
|
10. Legendary Actions (with preamble text)
|
|
|
|
### StatBlockPanel
|
|
|
|
**Purpose**: Responsive wrapper — side panel on desktop, drawer on mobile.
|
|
|
|
**Props**:
|
|
- `creature: Creature | null` — Currently displayed creature (null = closed)
|
|
- `onClose: () => void` — Close the panel/drawer
|
|
|
|
**Behavior**:
|
|
- Desktop (>= 1024px): Renders as a right-side panel, tracker shifts left
|
|
- Mobile (< 1024px): Renders as a slide-over drawer from right with backdrop
|
|
- Close button always visible
|
|
- Panel scrolls independently of the main content
|
|
|
|
### ActionBar (modified)
|
|
|
|
**Extended Props**:
|
|
- `onAddCombatant: (name: string) => void` — Existing: add plain combatant
|
|
- `onAddFromBestiary: (creature: Creature) => void` — New: add creature with stat pre-fill
|
|
- `suggestions: Creature[]` — Matching creatures for current name input
|
|
- `onSearchChange: (query: string) => void` — Notify parent of input changes for suggestion filtering
|
|
|
|
**New Elements**:
|
|
- Magnifying glass icon button next to input → opens dedicated BestiarySearch
|
|
- Autocomplete suggestion list below input when typing (>= 2 chars and matches exist)
|
|
|
|
## Hook Contracts
|
|
|
|
### useBestiary
|
|
|
|
**Purpose**: Provides creature search and lookup from the bundled bestiary.
|
|
|
|
**Returns**:
|
|
- `search: (query: string) => Creature[]` — Filter creatures by name substring
|
|
- `getCreature: (id: CreatureId) => Creature | undefined` — Look up creature by ID
|
|
- `allCreatures: Creature[]` — Full list (for potential future use)
|
|
|
|
**Behavior**:
|
|
- Loads and normalizes bestiary data once on initialization (lazy, memoized)
|
|
- Search is synchronous (in-memory filter)
|
|
- Returns results sorted alphabetically by name
|
|
|
|
### useEncounter (extended)
|
|
|
|
**Extended return**:
|
|
- `addFromBestiary: (creature: Creature) => void` — New: adds combatant with auto-numbered name, pre-filled HP/AC, and creatureId link
|
|
|
|
**Auto-numbering behavior**:
|
|
- Checks existing combatant names for conflicts with the creature's base name
|
|
- If no conflict: uses creature name as-is
|
|
- If one existing match: renames existing to "Name 1", new one becomes "Name 2"
|
|
- If N existing matches: new one becomes "Name N+1"
|
|
- Names remain editable after auto-numbering
|
|
|
|
## Layout Contract
|
|
|
|
### App (modified)
|
|
|
|
**Desktop layout (>= 1024px, stat block open)**:
|
|
```
|
|
+--------------------------------------------------+
|
|
| Header |
|
|
+-------------------------+------------------------+
|
|
| Tracker (flex-1) | Stat Block (~400px) |
|
|
| - Turn Navigation | - Scrollable |
|
|
| - Combatant List | - Close button |
|
|
| - Action Bar | |
|
|
+-------------------------+------------------------+
|
|
```
|
|
|
|
**Desktop layout (stat block closed)**:
|
|
```
|
|
+--------------------------------------------------+
|
|
| Header |
|
|
| Tracker (max-w-2xl, centered) |
|
|
| - Turn Navigation |
|
|
| - Combatant List |
|
|
| - Action Bar |
|
|
+--------------------------------------------------+
|
|
```
|
|
|
|
**Mobile layout (< 1024px, stat block open)**:
|
|
```
|
|
+--------------------------------------------------+
|
|
| Header |
|
|
| Tracker (full width) |
|
|
| - Turn Navigation |
|
|
| - Combatant List |
|
|
| - Action Bar |
|
|
| |
|
|
| +----------------------------------------------+ |
|
|
| | Drawer (slide from right, 85% width) | |
|
|
| | - Close button | |
|
|
| | - Stat Block (scrollable) | |
|
|
| +----------------------------------------------+ |
|
|
| | Backdrop (click to close) | |
|
|
+--------------------------------------------------+
|
|
```
|