refactor: Move search modal to layout root for proper z-index stacking

- Extract SearchModal component from SearchBar for root-level rendering
- Add isModalOpen state to search store with open/close methods
- Simplify SearchBar to trigger button only
- Update Modal with proper overflow handling and scroll-to-close
- Fix layout background to use void color

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-07 16:32:24 +01:00
parent d01e0d28f6
commit cdc70403f9
5 changed files with 189 additions and 107 deletions

View File

@@ -1,116 +1,30 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { Search, Command } from 'lucide-svelte';
import { search } from '$lib/stores';
import Modal from '$lib/components/ui/Modal.svelte';
let open = $state(false);
let query = $state('');
let searchInput: HTMLInputElement;
// Keyboard shortcut: Cmd/Ctrl + K
const handleKeydown = (e: KeyboardEvent) => {
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
e.preventDefault();
open = true;
setTimeout(() => searchInput?.focus(), 100);
search.openModal();
}
};
const handleSearch = (e: Event) => {
e.preventDefault();
if (!query.trim()) return;
// Add to recent searches
search.addRecentSearch(query);
// Navigate to matches page with search query
goto(`/matches?search=${encodeURIComponent(query)}`);
// Close modal and clear
open = false;
query = '';
};
const handleRecentClick = (recentQuery: string) => {
query = recentQuery;
handleSearch(new Event('submit'));
};
const handleClearRecent = () => {
search.clearRecentSearches();
};
</script>
<svelte:window onkeydown={handleKeydown} />
<!-- Search Button (Header) -->
<button
class="btn btn-ghost gap-2"
onclick={() => {
open = true;
setTimeout(() => searchInput?.focus(), 100);
}}
aria-label="Search"
class="flex items-center gap-2 rounded-lg px-3 py-2 text-white/70 transition-colors hover:bg-neon-blue/10 hover:text-neon-blue focus:outline-none focus-visible:ring-2 focus-visible:ring-neon-blue"
onclick={() => search.openModal()}
aria-label="Search matches and players"
>
<Search class="h-5 w-5" />
<Search class="h-5 w-5" aria-hidden="true" />
<span class="hidden md:inline">Search</span>
<kbd class="kbd kbd-sm hidden lg:inline-flex">
<Command class="h-3 w-3" />
K
<kbd
class="hidden items-center gap-0.5 rounded border border-neon-blue/30 bg-void px-1.5 py-0.5 text-xs text-white/50 lg:inline-flex"
>
<Command class="h-3 w-3" aria-hidden="true" />
<span>K</span>
</kbd>
</button>
<!-- Search Modal -->
<Modal bind:open size="lg">
<div class="space-y-4">
<form onsubmit={handleSearch}>
<label class="input input-bordered flex items-center gap-2">
<Search class="h-5 w-5 text-base-content/60" />
<input
bind:this={searchInput}
bind:value={query}
type="text"
class="grow"
placeholder="Search matches, players, share codes..."
autocomplete="off"
/>
<kbd class="kbd kbd-sm">
<Command class="h-3 w-3" />
K
</kbd>
</label>
</form>
<!-- Recent Searches -->
{#if $search.recentSearches.length > 0}
<div class="space-y-2">
<div class="flex items-center justify-between">
<h3 class="text-sm font-semibold text-base-content/70">Recent Searches</h3>
<button class="btn btn-ghost btn-xs" onclick={handleClearRecent}>Clear</button>
</div>
<div class="flex flex-wrap gap-2">
{#each $search.recentSearches as recent}
<button
class="badge badge-outline badge-lg gap-2 hover:badge-primary"
onclick={() => handleRecentClick(recent)}
>
<Search class="h-3 w-3" />
{recent}
</button>
{/each}
</div>
</div>
{/if}
<!-- Search Tips -->
<div class="rounded-lg bg-base-200 p-4">
<h4 class="mb-2 text-sm font-semibold text-base-content">Search Tips</h4>
<ul class="space-y-1 text-xs text-base-content/70">
<li>• Search by player name or Steam ID</li>
<li>• Enter share code to find specific match</li>
<li>• Use map name to filter matches (e.g., "de_dust2")</li>
</ul>
</div>
</div>
</Modal>