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).
on_revert is optional — shown only when a saved override exists.
is_dirty: Save is hidden when clean (unchanged), warning-styled when dirty.
Layout when dirty: [Revert?] [⚠ Save] [X] / when clean: [Revert?] [X] -->
is_dirty: Save uses warning style when changed, invisible when clean.
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)}
{@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">
{#if on_revert}
<button
type="button"
class="btn btn-sm preset-tonal-warning shrink-0"
onclick={on_revert}
disabled={status === 'saving'}
title="Remove override — restore original imported value"
aria-label="Revert to original value"
><RotateCcw size="13" /></button>
{/if}
{#if show_save}
<button
type="button"
class="btn btn-sm flex-1 transition-colors"
class:preset-filled-warning={is_dirty && (!status || status === 'idle')}
class:preset-tonal-surface={status === 'saving'}
class:preset-filled-success={status === 'done'}
class:preset-tonal-error={status === 'error'}
disabled={status === 'saving'}
onclick={on_save}
title="Save changes"
aria-label="Save changes"
>
{#if status === 'saving'}
<LoaderCircle size="14" class="animate-spin mr-1" /> Saving…
{:else if status === 'done'}
<Check size="14" class="mr-1" /> Saved
{:else if status === 'error'}
Error — retry
{:else}
<Check size="14" class="mr-1" /> Save
{/if}
</button>
{/if}
<!-- Revert: always occupies left slot; invisible when no override exists -->
<button
type="button"
class="btn btn-sm preset-tonal-warning shrink-0"
class:invisible={!on_revert}
class:pointer-events-none={!on_revert}
onclick={on_revert}
disabled={!on_revert || status === 'saving'}
tabindex={on_revert ? 0 : -1}
title="Remove override restore original imported value"
aria-label="Revert to original value"
aria-hidden={!on_revert}
><RotateCcw size="13" /></button>
<!-- Save: always in centre slot; invisible+inert when clean -->
<button
type="button"
class="btn btn-sm flex-1 transition-colors"
class:invisible={!save_visible}
class:pointer-events-none={!save_visible}
class:preset-filled-warning={save_visible && is_dirty && (!status || status === 'idle')}
class:preset-tonal-surface={status === 'saving'}
class:preset-filled-success={status === 'done'}
class:preset-tonal-error={status === 'error'}
disabled={!save_visible || status === 'saving'}
tabindex={save_visible ? 0 : -1}
onclick={on_save}
title="Save changes"
aria-label="Save changes"
aria-hidden={!save_visible}
>
{#if status === 'saving'}
<LoaderCircle size="14" class="animate-spin mr-1" /> Saving…
{:else if status === 'done'}
<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
type="button"
class="btn btn-sm preset-tonal-surface"
@@ -749,32 +757,34 @@
<span class="text-xs">Allow exhibitor lead scanning</span>
</label>
<!-- 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">
{#if is_dirty_allow_tracking || (field_save_status['allow_tracking'] && field_save_status['allow_tracking'] !== 'idle')}
<button
type="button"
class="btn btn-sm flex-1 transition-colors"
class:preset-filled-warning={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-success={field_save_status['allow_tracking'] === 'done'}
class:preset-tonal-error={field_save_status['allow_tracking'] === 'error'}
disabled={field_save_status['allow_tracking'] === 'saving'}
onclick={() => save_field('allow_tracking', { allow_tracking: edit_allow_tracking })}
title="Save changes"
aria-label="Save changes"
>
{#if field_save_status['allow_tracking'] === 'saving'}
<LoaderCircle size="14" class="animate-spin mr-1" /> Saving…
{:else if field_save_status['allow_tracking'] === 'done'}
<Check size="14" class="mr-1" /> Saved
{:else if field_save_status['allow_tracking'] === 'error'}
Error — retry
{:else}
<Check size="14" class="mr-1" /> Save
{/if}
</button>
{/if}
<button
type="button"
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:pointer-events-none={!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:preset-tonal-surface={field_save_status['allow_tracking'] === 'saving'}
class:preset-filled-success={field_save_status['allow_tracking'] === 'done'}
class:preset-tonal-error={field_save_status['allow_tracking'] === 'error'}
disabled={!is_dirty_allow_tracking || field_save_status['allow_tracking'] === 'saving'}
tabindex={is_dirty_allow_tracking ? 0 : -1}
onclick={() => save_field('allow_tracking', { allow_tracking: edit_allow_tracking })}
title="Save changes"
aria-label="Save changes"
aria-hidden={!is_dirty_allow_tracking && (!field_save_status['allow_tracking'] || field_save_status['allow_tracking'] === 'idle')}
>
{#if field_save_status['allow_tracking'] === 'saving'}
<LoaderCircle size="14" class="animate-spin mr-1" /> Saving…
{:else if field_save_status['allow_tracking'] === 'done'}
<Check size="14" class="mr-1" /> Saved
{:else if field_save_status['allow_tracking'] === 'error'}
Error — retry
{:else}
<Check size="14" class="mr-1" /> Save
{/if}
</button>
<button
type="button"
class="btn btn-sm preset-tonal-surface"