refactor: simplify Card HTML and extract search filter composable
All checks were successful
CI / update (push) Successful in 1m23s
All checks were successful
CI / update (push) Successful in 1m23s
- Remove unnecessary wrapper divs in Card component (.card_anchor, .div_div_image) - Flatten Card HTML from 4 levels to 2 levels of nesting - Create reusable createSearchFilter composable in $lib/js/searchFilter.svelte.ts - Apply search filter composable to category, tag, and favorites pages
This commit is contained in:
@@ -39,13 +39,6 @@ const img_alt = $derived(
|
||||
);
|
||||
</script>
|
||||
<style>
|
||||
.card_anchor{
|
||||
border-radius: var(--radius-card);
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
.card-main-link {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
@@ -76,11 +69,12 @@ const img_alt = $derived(
|
||||
width: 300px;
|
||||
border-radius: var(--radius-card);
|
||||
background-size: contain;
|
||||
display: flex;
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
justify-content: end;
|
||||
background-color: var(--blue);
|
||||
box-shadow: var(--shadow-lg);
|
||||
color: inherit;
|
||||
}
|
||||
/* Position/size overrides for global g-icon-badge */
|
||||
.icon{
|
||||
@@ -106,9 +100,11 @@ const img_alt = $derived(
|
||||
.backdrop_blur{
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
.div_image,
|
||||
.div_div_image{
|
||||
.card-image{
|
||||
width: 300px;
|
||||
height: 255px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
@@ -116,12 +112,6 @@ const img_alt = $derived(
|
||||
border-top-left-radius: inherit;
|
||||
border-top-right-radius: inherit;
|
||||
}
|
||||
.div_div_image{
|
||||
height: 255px;
|
||||
position: absolute;
|
||||
width: 300px;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.card:hover,
|
||||
.card:focus-within{
|
||||
@@ -238,19 +228,16 @@ const img_alt = $derived(
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class=card_anchor class:search_me={search} data-tags="[{recipe.tags}]">
|
||||
<div class="card" class:margin_right={do_margin_right}>
|
||||
<div class="card" class:search_me={search} class:margin_right={do_margin_right} data-tags="[{recipe.tags}]">
|
||||
<a href="{routePrefix}/{recipe.short_name}" class="card-main-link" aria-label="View recipe: {recipe.name}">
|
||||
<span class="visually-hidden">View recipe: {recipe.name}</span>
|
||||
</a>
|
||||
<div class=div_div_image >
|
||||
<div class=div_image style="background-image:url(https://bocken.org/static/rezepte/placeholder/{img_name})">
|
||||
<div class="card-image" style="background-image:url(https://bocken.org/static/rezepte/placeholder/{img_name})">
|
||||
<noscript>
|
||||
<img class="image backdrop_blur" src="https://bocken.org/static/rezepte/thumb/{img_name}" loading={loading_strat} alt="{img_alt}"/>
|
||||
</noscript>
|
||||
<img class="image backdrop_blur" class:blur={!isloaded} src={'https://bocken.org/static/rezepte/thumb/' + img_name} loading={loading_strat} alt="{img_alt}" onload={() => isloaded=true}/>
|
||||
</div>
|
||||
</div>
|
||||
{#if showFavoriteIndicator && isFavorite}
|
||||
<div class="favorite-indicator">❤️</div>
|
||||
{/if}
|
||||
@@ -281,4 +268,3 @@ const img_alt = $derived(
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
27
src/lib/js/searchFilter.svelte.ts
Normal file
27
src/lib/js/searchFilter.svelte.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Shared search filter composable for recipe list pages.
|
||||
* Extracts duplicated search state logic from multiple pages.
|
||||
*/
|
||||
|
||||
type Recipe = { _id: string; [key: string]: any };
|
||||
|
||||
export function createSearchFilter<T extends Recipe>(getRecipes: () => T[]) {
|
||||
let matchedRecipeIds = $state(new Set<string>());
|
||||
let hasActiveSearch = $state(false);
|
||||
|
||||
function handleSearchResults(ids: Set<string>, _categories?: Set<string>) {
|
||||
matchedRecipeIds = ids;
|
||||
hasActiveSearch = ids.size < getRecipes().length;
|
||||
}
|
||||
|
||||
const filtered = $derived.by(() => {
|
||||
if (!hasActiveSearch) return getRecipes();
|
||||
return getRecipes().filter(r => matchedRecipeIds.has(r._id));
|
||||
});
|
||||
|
||||
return {
|
||||
get filtered() { return filtered; },
|
||||
get hasActiveSearch() { return hasActiveSearch; },
|
||||
handleSearchResults
|
||||
};
|
||||
}
|
||||
@@ -2,32 +2,18 @@
|
||||
import type { PageData } from './$types';
|
||||
import Recipes from '$lib/components/Recipes.svelte';
|
||||
import Search from '$lib/components/Search.svelte';
|
||||
import Card from '$lib/components/Card.svelte';
|
||||
import { rand_array } from '$lib/js/randomize';
|
||||
import { createSearchFilter } from '$lib/js/searchFilter.svelte';
|
||||
|
||||
let { data } = $props<{ data: PageData }>();
|
||||
let current_month = new Date().getMonth() + 1;
|
||||
import Card from '$lib/components/Card.svelte'
|
||||
import { rand_array } from '$lib/js/randomize';
|
||||
|
||||
const isEnglish = $derived(data.lang === 'en');
|
||||
const label = $derived(isEnglish ? 'Recipes in Category' : 'Rezepte in Kategorie');
|
||||
const siteTitle = $derived(isEnglish ? 'Bocken Recipes' : 'Bocken Rezepte');
|
||||
|
||||
// Search state
|
||||
let matchedRecipeIds = $state(new Set());
|
||||
let hasActiveSearch = $state(false);
|
||||
|
||||
// Handle search results from Search component
|
||||
function handleSearchResults(ids, categories) {
|
||||
matchedRecipeIds = ids;
|
||||
hasActiveSearch = ids.size < data.recipes.length;
|
||||
}
|
||||
|
||||
// Filter recipes based on search
|
||||
const filteredRecipes = $derived.by(() => {
|
||||
if (!hasActiveSearch) {
|
||||
return data.recipes;
|
||||
}
|
||||
return data.recipes.filter(r => matchedRecipeIds.has(r._id));
|
||||
});
|
||||
const { filtered: filteredRecipes, handleSearchResults } = createSearchFilter(() => data.recipes);
|
||||
</script>
|
||||
<style>
|
||||
h1 {
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
import Recipes from '$lib/components/Recipes.svelte';
|
||||
import Card from '$lib/components/Card.svelte';
|
||||
import Search from '$lib/components/Search.svelte';
|
||||
import { createSearchFilter } from '$lib/js/searchFilter.svelte';
|
||||
|
||||
let { data } = $props<{ data: PageData }>();
|
||||
let current_month = new Date().getMonth() + 1;
|
||||
|
||||
@@ -28,23 +30,7 @@
|
||||
recipesLink: isEnglish ? 'recipe' : 'Rezept'
|
||||
});
|
||||
|
||||
// Search state
|
||||
let matchedRecipeIds = $state(new Set());
|
||||
let hasActiveSearch = $state(false);
|
||||
|
||||
// Handle search results from Search component
|
||||
function handleSearchResults(ids, categories) {
|
||||
matchedRecipeIds = ids;
|
||||
hasActiveSearch = ids.size < data.favorites.length;
|
||||
}
|
||||
|
||||
// Filter recipes based on search
|
||||
const filteredFavorites = $derived.by(() => {
|
||||
if (!hasActiveSearch) {
|
||||
return data.favorites;
|
||||
}
|
||||
return data.favorites.filter(r => matchedRecipeIds.has(r._id));
|
||||
});
|
||||
const { filtered: filteredFavorites, handleSearchResults } = createSearchFilter(() => data.favorites);
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -1,33 +1,19 @@
|
||||
<script lang="ts">
|
||||
import type { PageData } from './$types';
|
||||
import Recipes from '$lib/components/Recipes.svelte';
|
||||
let { data } = $props<{ data: PageData }>();
|
||||
let current_month = new Date().getMonth() + 1;
|
||||
import Card from '$lib/components/Card.svelte'
|
||||
import Card from '$lib/components/Card.svelte';
|
||||
import Search from '$lib/components/Search.svelte';
|
||||
import { rand_array } from '$lib/js/randomize';
|
||||
import { createSearchFilter } from '$lib/js/searchFilter.svelte';
|
||||
|
||||
let { data } = $props<{ data: PageData }>();
|
||||
let current_month = new Date().getMonth() + 1;
|
||||
|
||||
const isEnglish = $derived(data.lang === 'en');
|
||||
const label = $derived(isEnglish ? 'Recipes with Keyword' : 'Rezepte mit Stichwort');
|
||||
const siteTitle = $derived(isEnglish ? 'Bocken Recipes' : 'Bocken Rezepte');
|
||||
|
||||
// Search state
|
||||
let matchedRecipeIds = $state(new Set());
|
||||
let hasActiveSearch = $state(false);
|
||||
|
||||
// Handle search results from Search component
|
||||
function handleSearchResults(ids, categories) {
|
||||
matchedRecipeIds = ids;
|
||||
hasActiveSearch = ids.size < data.recipes.length;
|
||||
}
|
||||
|
||||
// Filter recipes based on search
|
||||
const filteredRecipes = $derived.by(() => {
|
||||
if (!hasActiveSearch) {
|
||||
return data.recipes;
|
||||
}
|
||||
return data.recipes.filter(r => matchedRecipeIds.has(r._id));
|
||||
});
|
||||
const { filtered: filteredRecipes, handleSearchResults } = createSearchFilter(() => data.recipes);
|
||||
</script>
|
||||
<style>
|
||||
h1 {
|
||||
|
||||
Reference in New Issue
Block a user