79 lines
3.0 KiB
Markdown
79 lines
3.0 KiB
Markdown
# UI Component Contracts: UI Baseline
|
|
|
|
**Feature**: 010-ui-baseline | **Date**: 2026-03-05
|
|
|
|
## Layout Contract
|
|
|
|
The encounter screen follows a single-column layout with three zones:
|
|
|
|
```
|
|
┌─────────────────────────────────┐
|
|
│ EncounterHeader │
|
|
│ Title + Round/Turn status │
|
|
├─────────────────────────────────┤
|
|
│ CombatantList │
|
|
│ ┌─ CombatantRow (active) ───┐ │
|
|
│ │ Init │ Name │ HP │ Actions│ │
|
|
│ └───────────────────────────┘ │
|
|
│ ┌─ CombatantRow ────────────┐ │
|
|
│ │ Init │ Name │ HP │ Actions│ │
|
|
│ └───────────────────────────┘ │
|
|
│ ... (scrollable if overflow) │
|
|
│ │
|
|
│ [EmptyState if no combatants] │
|
|
├─────────────────────────────────┤
|
|
│ ActionBar │
|
|
│ [Name input] [Add] [Next Turn] │
|
|
└─────────────────────────────────┘
|
|
```
|
|
|
|
## CombatantRow Contract
|
|
|
|
**Props**:
|
|
- `combatant: Combatant` — domain entity
|
|
- `isActive: boolean` — whether this is the active turn
|
|
- `onRename: (id, newName) => void`
|
|
- `onSetInitiative: (id, value) => void`
|
|
- `onRemove: (id) => void`
|
|
- `onSetHp: (id, maxHp) => void`
|
|
- `onAdjustHp: (id, delta) => void`
|
|
|
|
**Visual contract**:
|
|
- Row uses consistent column widths across all combatant rows
|
|
- Active row has visually distinct highlight (accent background or left border)
|
|
- Name column truncates with ellipsis at max width
|
|
- Remove action is an icon button (no text label)
|
|
- All inputs use design system styling (no browser defaults)
|
|
|
|
**Interaction contract**:
|
|
- Click name → enter inline edit mode
|
|
- Enter/blur in edit mode → commit change
|
|
- Escape in edit mode → cancel
|
|
- Initiative input is always visible (not click-to-edit), direct typing only
|
|
- HP inputs are direct-entry text fields with numeric keyboard (`inputmode="numeric"`)
|
|
- All numeric inputs: no browser spinners, ch-based widths (`6ch`), tabular numerals, centered text
|
|
|
|
## ActionBar Contract
|
|
|
|
**Props**:
|
|
- `onAddCombatant: (name: string) => void`
|
|
- `onAdvanceTurn: () => void`
|
|
|
|
**Visual contract**:
|
|
- Visually separated from combatant list (spacing, background, or border)
|
|
- Add form: text input + submit button in a row
|
|
- Next Turn: distinct button, visually secondary to Add
|
|
|
|
**Interaction contract**:
|
|
- Enter in name input → add combatant + clear input
|
|
- Empty name → no action (button may be disabled or form simply ignores)
|
|
|
|
## EmptyState Contract
|
|
|
|
**Displayed when**: `encounter.combatants.length === 0`
|
|
|
|
**Visual contract**:
|
|
- Centered message in the combatant list area
|
|
- Muted/secondary text color
|
|
- Suggests adding a combatant
|