Add scrollbar to NPC actor sheet

- Create npc-body.hbs as combined body template with all sections
- Simplify PARTS to header + body instead of individual sections
- Add flex layout and custom scrollbar CSS for sheet-body
- Preload npc-body.hbs template in vagabond.mjs

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Cal Corum 2025-12-18 13:47:19 -06:00
parent 8f9a3dc14a
commit ee124af202
4 changed files with 326 additions and 13 deletions

View File

@ -47,17 +47,8 @@ export default class VagabondNPCSheet extends VagabondActorSheet {
header: {
template: "systems/vagabond/templates/actor/npc-header.hbs",
},
stats: {
template: "systems/vagabond/templates/actor/npc-stats.hbs",
},
actions: {
template: "systems/vagabond/templates/actor/npc-actions.hbs",
},
abilities: {
template: "systems/vagabond/templates/actor/npc-abilities.hbs",
},
notes: {
template: "systems/vagabond/templates/actor/npc-notes.hbs",
body: {
template: "systems/vagabond/templates/actor/npc-body.hbs",
},
};
@ -172,8 +163,8 @@ export default class VagabondNPCSheet extends VagabondActorSheet {
_configureRenderOptions(options) {
super._configureRenderOptions(options);
// NPC sheets render all parts (no tabs)
options.parts = ["header", "stats", "actions", "abilities", "notes"];
// NPC sheets render header and body (no tabs)
options.parts = ["header", "body"];
}
/* -------------------------------------------- */

View File

@ -67,6 +67,7 @@ async function preloadHandlebarsTemplates() {
"systems/vagabond/templates/actor/parts/status-bar.hbs",
// NPC sheet parts
"systems/vagabond/templates/actor/npc-header.hbs",
"systems/vagabond/templates/actor/npc-body.hbs",
"systems/vagabond/templates/actor/npc-stats.hbs",
"systems/vagabond/templates/actor/npc-actions.hbs",
"systems/vagabond/templates/actor/npc-abilities.hbs",

View File

@ -2141,6 +2141,16 @@
min-width: 450px;
min-height: 500px;
// Form is a flex column to allow scrollable body
display: flex;
flex-direction: column;
height: 100%;
// Header wrapper stays fixed at top
.header-wrapper {
flex-shrink: 0;
}
// ----------------------------------------
// NPC Header
// ----------------------------------------
@ -2383,6 +2393,7 @@
overflow-y: auto;
flex: 1;
min-height: 0;
@include custom-scrollbar;
}
// ----------------------------------------

View File

@ -0,0 +1,310 @@
{{!-- NPC Sheet Body - Scrollable Content Wrapper --}}
<div class="sheet-body">
{{!-- Stats Section --}}
<section class="npc-stats">
<div class="stats-row">
{{!-- Zone --}}
<div class="stat-group zone">
<label>{{localize "VAGABOND.Zone"}}</label>
<select name="system.zone">
{{#each zoneOptions}}
<option value="{{@key}}" {{#if (eq @key ../zone)}}selected{{/if}}>
{{localize this}}
</option>
{{/each}}
</select>
<p class="zone-hint">{{zoneBehavior}}</p>
</div>
{{!-- Speed --}}
<div class="stat-group speed">
<label>{{localize "VAGABOND.Speed"}}</label>
<div class="speed-value">
<input type="number" name="system.speed.value" value="{{speed}}" min="0" />
<span class="unit">ft</span>
</div>
</div>
{{!-- Size & Type --}}
<div class="stat-group type">
<div class="type-field">
<label>{{localize "VAGABOND.Size"}}</label>
<select name="system.size">
{{#each sizeOptions}}
<option value="{{@key}}" {{#if (eq @key ../size)}}selected{{/if}}>
{{localize this}}
</option>
{{/each}}
</select>
</div>
<div class="type-field">
<label>{{localize "VAGABOND.BeingType"}}</label>
<select name="system.beingType">
{{#each beingTypeOptions}}
<option value="{{@key}}" {{#if (eq @key ../beingType)}}selected{{/if}}>
{{localize this}}
</option>
{{/each}}
</select>
</div>
</div>
{{!-- Appearing --}}
<div class="stat-group appearing">
<label class="rollable" data-action="rollAppearing" data-tooltip="{{localize 'VAGABOND.RollAppearing'}}">
<i class="fa-solid fa-dice"></i>
{{localize "VAGABOND.Appearing"}}
</label>
<input type="text" name="system.appearing" value="{{appearing}}" placeholder="1d6" />
</div>
</div>
{{!-- Movement Types --}}
<div class="biography-section movement">
<h2 class="section-header">{{localize "VAGABOND.Movement"}}</h2>
<div class="movement-grid">
<div class="movement-field">
<label data-tooltip="{{localize 'VAGABOND.MovementClimbHint'}}">
<input type="checkbox" name="system.movement.climb" {{#if movement.climb}}checked{{/if}} />
{{localize "VAGABOND.Climb"}}
</label>
</div>
<div class="movement-field">
<label data-tooltip="{{localize 'VAGABOND.MovementClingHint'}}">
<input type="checkbox" name="system.movement.cling" {{#if movement.cling}}checked{{/if}} />
{{localize "VAGABOND.Cling"}}
</label>
</div>
<div class="movement-field">
<label data-tooltip="{{localize 'VAGABOND.MovementFlyHint'}}">
<input type="checkbox" name="system.movement.fly" {{#if movement.fly}}checked{{/if}} />
{{localize "VAGABOND.Fly"}}
</label>
</div>
<div class="movement-field">
<label data-tooltip="{{localize 'VAGABOND.MovementPhaseHint'}}">
<input type="checkbox" name="system.movement.phase" {{#if movement.phase}}checked{{/if}} />
{{localize "VAGABOND.Phase"}}
</label>
</div>
<div class="movement-field">
<label data-tooltip="{{localize 'VAGABOND.MovementSwimHint'}}">
<input type="checkbox" name="system.movement.swim" {{#if movement.swim}}checked{{/if}} />
{{localize "VAGABOND.Swim"}}
</label>
</div>
</div>
</div>
{{!-- Senses --}}
{{#if hasSenses}}
<div class="senses-row">
<label>{{localize "VAGABOND.Senses"}}:</label>
{{#if senses.allsight}}
<span class="sense-tag">{{localize "VAGABOND.Allsight"}}</span>
{{/if}}
{{#if senses.blindsight}}
<span class="sense-tag">{{localize "VAGABOND.Blindsight"}}</span>
{{/if}}
{{#if senses.darkvision}}
<span class="sense-tag">{{localize "VAGABOND.Darkvision"}}</span>
{{/if}}
{{#if senses.echolocation}}
<span class="sense-tag">{{localize "VAGABOND.Echolocation"}}</span>
{{/if}}
{{#if senses.seismicsense}}
<span class="sense-tag">{{localize "VAGABOND.Seismicsense"}}</span>
{{/if}}
{{#if senses.telepathy}}
<span class="sense-tag">{{localize "VAGABOND.Telepathy"}}</span>
{{/if}}
</div>
{{/if}}
{{!-- Damage Modifiers --}}
{{#if hasDamageModifiers}}
<div class="damage-modifiers">
{{#if immunities.length}}
<div class="modifier-row immunities">
<label>{{localize "VAGABOND.Immunities"}}:</label>
<div class="modifier-tags">
{{#each immunities}}
<span class="modifier-tag immune">{{this}}</span>
{{/each}}
</div>
</div>
{{/if}}
{{#if resistances.length}}
<div class="modifier-row resistances">
<label>{{localize "VAGABOND.Resistances"}}:</label>
<div class="modifier-tags">
{{#each resistances}}
<span class="modifier-tag resist">{{this}}</span>
{{/each}}
</div>
</div>
{{/if}}
{{#if weaknesses.length}}
<div class="modifier-row weaknesses">
<label>{{localize "VAGABOND.Weaknesses"}}:</label>
<div class="modifier-tags">
{{#each weaknesses}}
<span class="modifier-tag weak">{{this}}</span>
{{/each}}
</div>
</div>
{{/if}}
</div>
{{/if}}
</section>
{{!-- Actions Section --}}
<section class="npc-actions">
<div class="section-header-row">
<h2 class="section-header">{{localize "VAGABOND.Actions"}}</h2>
<button type="button" class="action-add" data-action="addAction"
data-tooltip="{{localize 'VAGABOND.AddAction'}}">
<i class="fa-solid fa-plus"></i>
</button>
</div>
<ul class="action-list">
{{#each actions}}
<li class="action-item" data-action-index="{{this.index}}">
<div class="action-header">
<input type="text" class="action-name" name="system.actions.{{this.index}}.name"
value="{{this.name}}" placeholder="{{localize 'VAGABOND.ActionName'}}" />
<div class="action-controls">
<button type="button" class="action-roll" data-action="rollAction"
data-action-index="{{this.index}}" data-tooltip="{{localize 'VAGABOND.RollAction'}}">
<i class="fa-solid fa-dice-d20"></i>
</button>
<button type="button" class="action-delete" data-action="deleteAction"
data-action-index="{{this.index}}" data-tooltip="{{localize 'VAGABOND.DeleteAction'}}">
<i class="fa-solid fa-trash"></i>
</button>
</div>
</div>
<div class="action-details">
<div class="action-field attack-type">
<label>{{localize "VAGABOND.AttackType"}}</label>
<select name="system.actions.{{this.index}}.attackType">
{{#each ../attackTypeOptions}}
<option value="{{@key}}" {{#if (eq @key ../this.attackType)}}selected{{/if}}>
{{localize this}}
</option>
{{/each}}
</select>
</div>
<div class="action-field damage">
<label>{{localize "VAGABOND.Damage"}}</label>
<input type="text" name="system.actions.{{this.index}}.damage"
value="{{this.damage}}" placeholder="1d6" />
</div>
<div class="action-field damage-type">
<label>{{localize "VAGABOND.DamageType"}}</label>
<select name="system.actions.{{this.index}}.damageType">
{{#each ../damageTypeOptions}}
<option value="{{@key}}" {{#if (eq @key ../this.damageType)}}selected{{/if}}>
{{localize this}}
</option>
{{/each}}
</select>
</div>
<div class="action-field range">
<label>{{localize "VAGABOND.Range"}}</label>
<input type="text" name="system.actions.{{this.index}}.range"
value="{{this.range}}" placeholder="60 ft" />
</div>
</div>
<div class="action-description">
<textarea name="system.actions.{{this.index}}.description"
placeholder="{{localize 'VAGABOND.ActionDescription'}}">{{this.description}}</textarea>
</div>
</li>
{{else}}
<li class="action-item empty">
<p>{{localize "VAGABOND.NoActions"}}</p>
<button type="button" data-action="addAction">
<i class="fa-solid fa-plus"></i>
{{localize "VAGABOND.AddAction"}}
</button>
</li>
{{/each}}
</ul>
</section>
{{!-- Abilities Section --}}
<section class="npc-abilities">
<div class="section-header-row">
<h2 class="section-header">{{localize "VAGABOND.Abilities"}}</h2>
<button type="button" class="ability-add" data-action="addAbility"
data-tooltip="{{localize 'VAGABOND.AddAbility'}}">
<i class="fa-solid fa-plus"></i>
</button>
</div>
<ul class="ability-list">
{{#each abilities}}
<li class="ability-item" data-ability-index="{{this.index}}">
<div class="ability-header">
<input type="text" class="ability-name" name="system.abilities.{{this.index}}.name"
value="{{this.name}}" placeholder="{{localize 'VAGABOND.AbilityName'}}" />
<div class="ability-controls">
<label class="ability-passive">
<input type="checkbox" name="system.abilities.{{this.index}}.passive"
{{#if this.passive}}checked{{/if}} />
{{localize "VAGABOND.Passive"}}
</label>
<button type="button" class="ability-delete" data-action="deleteAbility"
data-ability-index="{{this.index}}" data-tooltip="{{localize 'VAGABOND.DeleteAbility'}}">
<i class="fa-solid fa-trash"></i>
</button>
</div>
</div>
<div class="ability-description">
<textarea name="system.abilities.{{this.index}}.description"
placeholder="{{localize 'VAGABOND.AbilityDescription'}}">{{this.description}}</textarea>
</div>
</li>
{{else}}
<li class="ability-item empty">
<p>{{localize "VAGABOND.NoAbilities"}}</p>
<button type="button" data-action="addAbility">
<i class="fa-solid fa-plus"></i>
{{localize "VAGABOND.AddAbility"}}
</button>
</li>
{{/each}}
</ul>
</section>
{{!-- Notes Section --}}
<section class="npc-notes">
{{!-- Loot --}}
<div class="notes-section loot">
<h2 class="section-header">{{localize "VAGABOND.Loot"}}</h2>
<div class="editor-container">
<textarea name="system.loot"
placeholder="{{localize 'VAGABOND.LootPlaceholder'}}">{{loot}}</textarea>
</div>
</div>
{{!-- GM Notes --}}
<div class="notes-section gm-notes">
<h2 class="section-header">{{localize "VAGABOND.GMNotes"}}</h2>
<div class="editor-container">
<textarea name="system.gmNotes"
placeholder="{{localize 'VAGABOND.GMNotesPlaceholder'}}">{{gmNotes}}</textarea>
</div>
</div>
</section>
</div>