diff options
| author | Fuwn <[email protected]> | 2025-05-14 01:43:10 -0700 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2025-05-14 01:43:10 -0700 |
| commit | 93901232b9836a6add3e75c7c7ec745371d44cec (patch) | |
| tree | 6753d192536ae5f1174fe3bc043317bbeaf1eb34 /src | |
| parent | feat(CommandPalette): Global toggle fading (diff) | |
| download | due.moe-93901232b9836a6add3e75c7c7ec745371d44cec.tar.xz due.moe-93901232b9836a6add3e75c7c7ec745371d44cec.zip | |
feat(CommandPalette): Animate search results
Diffstat (limited to 'src')
| -rw-r--r-- | src/lib/CommandPalette/CommandPalette.svelte | 65 |
1 files changed, 48 insertions, 17 deletions
diff --git a/src/lib/CommandPalette/CommandPalette.svelte b/src/lib/CommandPalette/CommandPalette.svelte index 56542270..538eea98 100644 --- a/src/lib/CommandPalette/CommandPalette.svelte +++ b/src/lib/CommandPalette/CommandPalette.svelte @@ -1,5 +1,7 @@ <script lang="ts"> import { onMount } from 'svelte'; + import { fly, fade } from 'svelte/transition'; + import { flip } from 'svelte/animate'; interface CommandPaletteItem { name: string; @@ -17,10 +19,17 @@ let inputRef: HTMLInputElement; let isVisible = false; let timeoutID: ReturnType<typeof setTimeout> | null = null; + let itemIDs = new Map<string, number>(); - $: filtered = items - .filter((item) => item.name.toLowerCase().includes(search.toLowerCase())) - .slice(0, 10); + $: { + items.forEach((item, index) => { + if (!itemIDs.has(item.url)) itemIDs.set(item.url, index); + }); + + filtered = items + .filter((item) => item.name.toLowerCase().includes(search.toLowerCase())) + .slice(0, 10); + } $: if (selectedIndex >= filtered.length) selectedIndex = filtered.length - 1; $: if (selectedIndex < 0 && filtered.length > 0) selectedIndex = 0; @@ -103,6 +112,7 @@ <div class="command-palette-overlay {open ? 'fade-in' : 'fade-out'}" on:click={() => (open = false)} + transition:fade={{ duration: 150 }} /> <div class="dropdown {open ? 'fade-in' : 'fade-out'}"> @@ -115,20 +125,29 @@ on:keydown={handleKey} /> - {#each filtered as item, i} - <a - href={item.url} - class="header-item {selectedIndex === i ? 'selected' : ''}" - on:click={(e) => { - if (item.preventDefault) e.preventDefault(); - if (item.onClick) item.onClick(); - - open = false; - }} - > - {item.name} - </a> - {/each} + <div class="results-container"> + {#each filtered as item, i (item.url)} + <a + href={item.url} + class="header-item {selectedIndex === i ? 'selected' : ''}" + in:fly={{ y: 20, duration: 150, delay: i * 30 }} + out:fly={{ y: -20, duration: 150 }} + animate:flip={{ duration: 200 }} + on:click={(e) => { + if (item.preventDefault) e.preventDefault(); + if (item.onClick) item.onClick(); + + open = false; + }} + > + {item.name} + </a> + {/each} + + {#if filtered.length === 0 && search !== ''} + <div class="no-results opaque" in:fade={{ duration: 150 }}>No results found</div> + {/if} + </div> </div> </div> {/if} @@ -150,6 +169,17 @@ outline: none; } + .no-results { + padding: 0.75em; + text-align: center; + } + + .results-container { + max-height: 60vh; + overflow-y: auto; + overflow-x: hidden; + } + .command-palette-overlay { position: fixed; top: 0; @@ -261,6 +291,7 @@ margin: 0.5em 0.75em; backdrop-filter: blur(0px); font-weight: 450; + transform-origin: center left; } .dropdown-content a:hover, |