fix(badges): fix search-result layout shift + unify empty/loading states

Scrollbar shift:
- Add [scrollbar-gutter:stable] to #ae_main_content in events layout so a
  scrollbar appearing on first results load no longer reflows the centered
  search form (was shifting ~8px left on Linux)

Empty/loading state consistency:
- Move search_status prop into ae_comp__badge_obj_li so it can swap its own
  empty state: spinner + "Searching..." while a search is in progress,
  UserSearch icon + prompt text otherwise
- Unify p-16 / size-3em / mb-2 / text-xl across all three states (initial
  load, searching, no results) so height never jumps between transitions
- Pass search_status from +page.svelte to the component

Transitions:
- transition:fade on initial-load spinner div
- transition:slide on Create/Upload badge button row (appears with edit mode)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Scott Idem
2026-06-02 16:38:30 -04:00
parent 3773758eb5
commit ac17417f3c
3 changed files with 21 additions and 10 deletions

View File

@@ -210,7 +210,7 @@ function clear_sess() {
container m-auto flex h-full min-h-full
w-full max-w-7xl
min-w-full flex-col gap-1
overflow-auto
overflow-auto [scrollbar-gutter:stable]
bg-gray-50 text-gray-800
dark:bg-gray-900 dark:text-gray-200

View File

@@ -1,5 +1,6 @@
<script lang="ts">
import { untrack } from 'svelte';
import { slide, fade } from 'svelte/transition';
interface Props {
/** @type {import('./$types').PageData} */
data: any;
@@ -472,7 +473,7 @@ async function handle_search_refresh(params: any) {
></Comp_badge_search>
{#if $ae_loc.trusted_access && $ae_loc.edit_mode}
<div class="flex flex-row gap-1 items-center justify-center">
<div transition:slide={{ duration: 200 }} class="flex flex-row gap-1 items-center justify-center">
{#if badges_loc.current.enable_add_badge_btn ?? true}
<button
type="button"
@@ -593,12 +594,16 @@ async function handle_search_refresh(params: any) {
{#if $events_sess?.badges?.search_status === 'loading' && event_badge_id_li.length === 0}
<div
class="flex flex-col items-center justify-center p-10 text-center opacity-50">
<LoaderCircle size="3em" class="mx-auto mb-4 animate-spin" />
transition:fade={{ duration: 200 }}
class="flex w-full flex-col items-center justify-center p-16 text-center opacity-50">
<LoaderCircle size="3em" class="mx-auto mb-2 animate-spin" />
<p class="text-xl">Loading badges...</p>
</div>
{:else}
<Comp_badge_obj_li {lq__event_badge_obj_li} log_lvl={1}></Comp_badge_obj_li>
<Comp_badge_obj_li
{lq__event_badge_obj_li}
search_status={$events_sess?.badges?.search_status ?? null}
log_lvl={1} />
{/if}
<style>

View File

@@ -2,6 +2,7 @@
interface Props {
container_class_li?: string | Array<string>;
lq__event_badge_obj_li: any;
search_status?: string | null;
log_lvl?: number;
show_sensitive_fields?: boolean;
hide_affiliations?: boolean;
@@ -12,6 +13,7 @@ interface Props {
let {
container_class_li = [],
lq__event_badge_obj_li,
search_status = null,
log_lvl = $bindable(0),
show_sensitive_fields = true,
hide_affiliations = false,
@@ -408,12 +410,16 @@ let visible_badge_obj_li = $derived(
</ul>
{:else}
<div
class="flex w-full flex-col items-center justify-center p-20 text-center opacity-50">
<UserSearch size="3em" class="mx-auto mb-2 opacity-20" />
{#if !is_trusted && !(badges_loc.current.fulltext_search_qry_str ?? '').trim()}
<p>Enter your name above to find your badge.</p>
class="flex w-full flex-col items-center justify-center p-16 text-center opacity-50">
{#if search_status === 'loading'}
<LoaderCircle size="3em" class="mx-auto mb-2 animate-spin" />
<p class="text-xl">Searching...</p>
{:else if !is_trusted && !(badges_loc.current.fulltext_search_qry_str ?? '').trim()}
<UserSearch size="3em" class="mx-auto mb-2 opacity-20" />
<p class="text-xl">Enter your name above to find your badge.</p>
{:else}
<p>No badges found matching your criteria. Try adjusting your filters.</p>
<UserSearch size="3em" class="mx-auto mb-2 opacity-20" />
<p class="text-xl">No badges found matching your criteria.</p>
{/if}
</div>
{/if}