mirror of
https://github.com/taogaetz/chefbible.git
synced 2025-12-06 11:47:24 -05:00
165 lines
4.5 KiB
Svelte
165 lines
4.5 KiB
Svelte
<script lang="ts">
|
|
import { goto } from '$app/navigation';
|
|
import { invalidate } from '$app/navigation';
|
|
|
|
export let recipes: any[] = [];
|
|
export let placeholder: string = 'Search recipes...';
|
|
|
|
let searchTerm = '';
|
|
let filteredRecipes: any[] = [];
|
|
let showResults = false;
|
|
|
|
function handleSearch() {
|
|
if (!searchTerm.trim()) {
|
|
filteredRecipes = [];
|
|
showResults = false;
|
|
return;
|
|
}
|
|
|
|
const term = searchTerm.toLowerCase();
|
|
filteredRecipes = recipes.filter(
|
|
(recipe) =>
|
|
recipe.name.toLowerCase().includes(term) ||
|
|
recipe.description?.toLowerCase().includes(term) ||
|
|
recipe.station.toLowerCase().includes(term) ||
|
|
recipe.time.toLowerCase().includes(term)
|
|
);
|
|
showResults = true;
|
|
}
|
|
|
|
function handleRecipeClick(recipe: any) {
|
|
window.location.href = `/recipe/${recipe.id}`;
|
|
searchTerm = '';
|
|
showResults = false;
|
|
}
|
|
|
|
function handleInputFocus() {
|
|
if (searchTerm.trim()) {
|
|
showResults = true;
|
|
}
|
|
}
|
|
|
|
let searchContainer: HTMLElement;
|
|
|
|
// Handle clicks outside the search container
|
|
function handleClickOutside(event: MouseEvent) {
|
|
if (searchContainer && !searchContainer.contains(event.target as Node)) {
|
|
showResults = false;
|
|
}
|
|
}
|
|
|
|
import { onMount } from 'svelte';
|
|
|
|
onMount(() => {
|
|
document.addEventListener('click', handleClickOutside);
|
|
|
|
return () => {
|
|
document.removeEventListener('click', handleClickOutside);
|
|
};
|
|
});
|
|
</script>
|
|
|
|
<div class="relative w-full max-w-lg" bind:this={searchContainer}>
|
|
<div
|
|
class="relative flex items-center rounded-xl border-2 border-transparent bg-white shadow-sm transition-all duration-200 focus-within:border-primary focus-within:shadow-lg"
|
|
>
|
|
<svg
|
|
class="pointer-events-none absolute left-4 text-gray-400"
|
|
width="20"
|
|
height="20"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
>
|
|
<circle cx="11" cy="11" r="8" />
|
|
<path d="m21 21-4.35-4.35" />
|
|
</svg>
|
|
<input
|
|
type="text"
|
|
bind:value={searchTerm}
|
|
on:input={handleSearch}
|
|
on:focus={handleInputFocus}
|
|
{placeholder}
|
|
class="w-full border-none bg-transparent px-4 py-3.5 pl-12 text-base text-gray-900 outline-none placeholder:text-gray-400"
|
|
/>
|
|
{#if searchTerm}
|
|
<button
|
|
class="absolute right-3 cursor-pointer rounded border-none bg-none p-1 text-gray-400 transition-colors hover:bg-gray-100 hover:text-gray-600"
|
|
aria-label="Clear search"
|
|
on:click={() => {
|
|
searchTerm = '';
|
|
showResults = false;
|
|
}}
|
|
>
|
|
<svg
|
|
width="16"
|
|
height="16"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
>
|
|
<path d="M18 6L6 18M6 6l12 12" />
|
|
</svg>
|
|
</button>
|
|
{/if}
|
|
</div>
|
|
|
|
{#if showResults && filteredRecipes.length > 0}
|
|
<div class="absolute top-full left-1/2 z-50 mt-1 w-screen -translate-x-1/2">
|
|
<div class="mx-auto max-w-7xl px-5">
|
|
<div class="rounded-xl border border-gray-200 bg-white shadow-2xl">
|
|
{#each filteredRecipes as recipe}
|
|
<button
|
|
class="flex w-full cursor-pointer items-center gap-3 border-b border-gray-100 bg-none p-4 transition-colors last:border-b-0 hover:bg-gray-50"
|
|
on:click={() => handleRecipeClick(recipe)}
|
|
>
|
|
<div class="flex-1 text-left">
|
|
<h4 class="mb-1 text-base font-semibold text-gray-900">{recipe.name}</h4>
|
|
{#if recipe.description}
|
|
<p class="mb-2 line-clamp-2 text-sm text-gray-600">{recipe.description}</p>
|
|
{/if}
|
|
<div class="flex gap-3">
|
|
<span class="rounded-md bg-green-100 px-2 py-1 text-xs font-medium text-green-700"
|
|
>{recipe.station}</span
|
|
>
|
|
<span
|
|
class="rounded-md bg-yellow-100 px-2 py-1 text-xs font-medium text-yellow-700"
|
|
>{recipe.time}</span
|
|
>
|
|
</div>
|
|
</div>
|
|
<svg
|
|
class="flex-shrink-0 text-gray-400"
|
|
width="16"
|
|
height="16"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
>
|
|
<path d="M9 18l6-6-6-6" />
|
|
</svg>
|
|
</button>
|
|
{/each}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{:else if showResults && searchTerm.trim()}
|
|
<div class="absolute top-full left-1/2 z-50 mt-1 w-screen -translate-x-1/2">
|
|
<div class="mx-auto max-w-7xl px-5">
|
|
<div class="rounded-xl border border-gray-200 bg-white shadow-2xl">
|
|
<div class="p-6 text-center text-gray-600">
|
|
<p>No recipes found for "{searchTerm}"</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
|
|
<style>
|
|
/* No component CSS needed - all styles use Tailwind utilities */
|
|
</style>
|