Badges: fix button layout \u2014 Revert/Save/X always in fixed positions

This commit is contained in:
Scott Idem
2026-03-12 15:54:06 -04:00
parent 23422c8f27
commit 7b340de139

View File

@@ -399,46 +399,54 @@
<!-- Save/cancel row (inside accordion edit forms). <!-- Save/cancel row (inside accordion edit forms).
on_revert is optional — shown only when a saved override exists. on_revert is optional — shown only when a saved override exists.
is_dirty: Save is hidden when clean (unchanged), warning-styled when dirty. is_dirty: Save uses warning style when changed, invisible when clean.
Layout when dirty: [Revert?] [⚠ Save] [X] / when clean: [Revert?] [X] --> Layout always fixed: [Revert (or gap)] [Save] [X]
Buttons are always rendered to keep X pinned at the right end. -->
{#snippet field_actions(field_key: string, on_save: () => void, on_cancel: () => void, on_revert?: () => void, is_dirty = false)} {#snippet field_actions(field_key: string, on_save: () => void, on_cancel: () => void, on_revert?: () => void, is_dirty = false)}
{@const status = field_save_status[field_key]} {@const status = field_save_status[field_key]}
{@const show_save = is_dirty || (status && status !== 'idle')} {@const save_visible = is_dirty || (status && status !== 'idle')}
<div class="flex gap-2 mt-2"> <div class="flex gap-2 mt-2">
{#if on_revert} <!-- Revert: always occupies left slot; invisible when no override exists -->
<button <button
type="button" type="button"
class="btn btn-sm preset-tonal-warning shrink-0" class="btn btn-sm preset-tonal-warning shrink-0"
onclick={on_revert} class:invisible={!on_revert}
disabled={status === 'saving'} class:pointer-events-none={!on_revert}
title="Remove override — restore original imported value" onclick={on_revert}
aria-label="Revert to original value" disabled={!on_revert || status === 'saving'}
><RotateCcw size="13" /></button> tabindex={on_revert ? 0 : -1}
{/if} title="Remove override restore original imported value"
{#if show_save} aria-label="Revert to original value"
<button aria-hidden={!on_revert}
type="button" ><RotateCcw size="13" /></button>
class="btn btn-sm flex-1 transition-colors" <!-- Save: always in centre slot; invisible+inert when clean -->
class:preset-filled-warning={is_dirty && (!status || status === 'idle')} <button
class:preset-tonal-surface={status === 'saving'} type="button"
class:preset-filled-success={status === 'done'} class="btn btn-sm flex-1 transition-colors"
class:preset-tonal-error={status === 'error'} class:invisible={!save_visible}
disabled={status === 'saving'} class:pointer-events-none={!save_visible}
onclick={on_save} class:preset-filled-warning={save_visible && is_dirty && (!status || status === 'idle')}
title="Save changes" class:preset-tonal-surface={status === 'saving'}
aria-label="Save changes" class:preset-filled-success={status === 'done'}
> class:preset-tonal-error={status === 'error'}
{#if status === 'saving'} disabled={!save_visible || status === 'saving'}
<LoaderCircle size="14" class="animate-spin mr-1" /> Saving… tabindex={save_visible ? 0 : -1}
{:else if status === 'done'} onclick={on_save}
<Check size="14" class="mr-1" /> Saved title="Save changes"
{:else if status === 'error'} aria-label="Save changes"
Error — retry aria-hidden={!save_visible}
{:else} >
<Check size="14" class="mr-1" /> Save {#if status === 'saving'}
{/if} <LoaderCircle size="14" class="animate-spin mr-1" /> Saving…
</button> {:else if status === 'done'}
{/if} <Check size="14" class="mr-1" /> Saved
{:else if status === 'error'}
Error — retry
{:else}
<Check size="14" class="mr-1" /> Save
{/if}
</button>
<!-- Cancel: always visible at right end -->
<button <button
type="button" type="button"
class="btn btn-sm preset-tonal-surface" class="btn btn-sm preset-tonal-surface"
@@ -749,32 +757,34 @@
<span class="text-xs">Allow exhibitor lead scanning</span> <span class="text-xs">Allow exhibitor lead scanning</span>
</label> </label>
<!-- Inline actions — not shared snippet because this field adds a TC info button. <!-- Inline actions — not shared snippet because this field adds a TC info button.
Save hidden until the checkbox value differs from the saved value. --> Layout always fixed: [Save] [X] [ⓘ] — Save invisible when clean. -->
<div class="flex gap-2 mt-2"> <div class="flex gap-2 mt-2">
{#if is_dirty_allow_tracking || (field_save_status['allow_tracking'] && field_save_status['allow_tracking'] !== 'idle')} <button
<button type="button"
type="button" class="btn btn-sm flex-1 transition-colors"
class="btn btn-sm flex-1 transition-colors" class:invisible={!is_dirty_allow_tracking && (!field_save_status['allow_tracking'] || field_save_status['allow_tracking'] === 'idle')}
class:preset-filled-warning={is_dirty_allow_tracking && (!field_save_status['allow_tracking'] || field_save_status['allow_tracking'] === 'idle')} class:pointer-events-none={!is_dirty_allow_tracking && (!field_save_status['allow_tracking'] || field_save_status['allow_tracking'] === 'idle')}
class:preset-tonal-surface={field_save_status['allow_tracking'] === 'saving'} class:preset-filled-warning={is_dirty_allow_tracking && (!field_save_status['allow_tracking'] || field_save_status['allow_tracking'] === 'idle')}
class:preset-filled-success={field_save_status['allow_tracking'] === 'done'} class:preset-tonal-surface={field_save_status['allow_tracking'] === 'saving'}
class:preset-tonal-error={field_save_status['allow_tracking'] === 'error'} class:preset-filled-success={field_save_status['allow_tracking'] === 'done'}
disabled={field_save_status['allow_tracking'] === 'saving'} class:preset-tonal-error={field_save_status['allow_tracking'] === 'error'}
onclick={() => save_field('allow_tracking', { allow_tracking: edit_allow_tracking })} disabled={!is_dirty_allow_tracking || field_save_status['allow_tracking'] === 'saving'}
title="Save changes" tabindex={is_dirty_allow_tracking ? 0 : -1}
aria-label="Save changes" onclick={() => save_field('allow_tracking', { allow_tracking: edit_allow_tracking })}
> title="Save changes"
{#if field_save_status['allow_tracking'] === 'saving'} aria-label="Save changes"
<LoaderCircle size="14" class="animate-spin mr-1" /> Saving… aria-hidden={!is_dirty_allow_tracking && (!field_save_status['allow_tracking'] || field_save_status['allow_tracking'] === 'idle')}
{:else if field_save_status['allow_tracking'] === 'done'} >
<Check size="14" class="mr-1" /> Saved {#if field_save_status['allow_tracking'] === 'saving'}
{:else if field_save_status['allow_tracking'] === 'error'} <LoaderCircle size="14" class="animate-spin mr-1" /> Saving…
Error — retry {:else if field_save_status['allow_tracking'] === 'done'}
{:else} <Check size="14" class="mr-1" /> Saved
<Check size="14" class="mr-1" /> Save {:else if field_save_status['allow_tracking'] === 'error'}
{/if} Error — retry
</button> {:else}
{/if} <Check size="14" class="mr-1" /> Save
{/if}
</button>
<button <button
type="button" type="button"
class="btn btn-sm preset-tonal-surface" class="btn btn-sm preset-tonal-surface"