refactor: SearchableTextBlock Redesign, Ort-Badge Truncate
Deploy / verify (push) Successful in 59s
Deploy / deploy (push) Successful in 1m12s

- SearchableTextBlock: Farbverlauf-Header entfernt, Tag/Tags durch .chip ersetzt,
  Suche auf Adressbuch-Style ausgerichtet, Kopier-Buttons auf Icon-only reduziert,
  border-l-4 Akzent entfernt
- EventBadges: whitespace-nowrap entfernt, truncate max-w-[16rem] + title für lange Ortstexte
- PostCard: min-w-0 auf Badges-Container für korrekte Truncate-Propagation
- iconify-mdi-subset: qrcode + download Icons ergänzt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Peter Meier
2026-06-04 11:25:34 +02:00
parent 982a7a3b73
commit 6c774c4913
4 changed files with 44 additions and 79 deletions
+4 -4
View File
@@ -34,14 +34,14 @@
{/if}
{#if locationText}
{#if locationHref}
<a href={locationHref} target={locationHref.startsWith('#') ? undefined : '_blank'} rel={locationHref.startsWith('#') ? undefined : 'noopener noreferrer'} class="inline-flex shrink-0 items-center gap-1.5 whitespace-nowrap rounded-full bg-stein-800 text-stein-50! px-3 py-1 text-[.65rem] shadow-sm hover:bg-stein-700 transition-colors no-underline!">
<a href={locationHref} target={locationHref.startsWith('#') ? undefined : '_blank'} rel={locationHref.startsWith('#') ? undefined : 'noopener noreferrer'} title={locationText} class="inline-flex min-w-0 items-center gap-1.5 rounded-full bg-stein-800 text-stein-50! px-3 py-1 text-[.65rem] shadow-sm hover:bg-stein-700 transition-colors no-underline!">
<Icon icon="mdi:map-marker" class="size-3.5 shrink-0 opacity-90" aria-hidden="true" />
{locationText}
<span class="truncate max-w-[16rem]">{locationText}</span>
</a>
{:else}
<span class="inline-flex shrink-0 items-center gap-1.5 whitespace-nowrap rounded-full bg-stein-800 text-stein-50 px-3 py-1 text-[.65rem] font-medium shadow-sm no-underline! [text-decoration:none]">
<span title={locationText} class="inline-flex min-w-0 items-center gap-1.5 rounded-full bg-stein-800 text-stein-50 px-3 py-1 text-[.65rem] font-medium shadow-sm no-underline! [text-decoration:none]">
<Icon icon="mdi:map-marker" class="size-3.5 shrink-0 opacity-90" aria-hidden="true" />
{locationText}
<span class="truncate max-w-[16rem]">{locationText}</span>
</span>
{/if}
{/if}
+1 -1
View File
@@ -84,7 +84,7 @@
</div>
{/if}
{#if eventDate}
<div class="absolute bottom-2 left-2 right-2">
<div class="absolute bottom-2 left-2 right-2 min-w-0">
<EventBadges {eventDate} {locationText} />
</div>
{/if}
@@ -9,9 +9,6 @@
} from "$lib/block-types";
import "$lib/iconify-offline";
import Icon from "@iconify/svelte";
import Tag from "../Tag.svelte";
import Tags from "../Tags.svelte";
import Tooltip from "$lib/ui/Tooltip.svelte";
import { t as tStatic, T } from "$lib/translations";
import type { Translations } from "$lib/translations";
@@ -28,7 +25,6 @@
function t(key: string, replacements?: Record<string, string | number>) {
return tStatic(translations ?? null, key, replacements);
}
const helpTooltipText = $derived(t(T.searchable_text_help));
const layoutClasses = $derived(getBlockLayoutClasses(block.layout));
const fragments = $derived.by(() => {
@@ -198,14 +194,12 @@
data-block-slug={block._slug}
>
{#if block.title || descriptionHtml}
<div class="px-4 md:px-6 py-5 bg-gradient-to-br from-wald-700 to-wald-800">
<div class="px-4 md:px-6 pt-5 pb-3">
{#if block.title}
<h2 class="text-xl md:text-2xl font-bold !text-white mb-1">
{block.title}
</h2>
<h2 class="text-xl font-bold text-stein-900 mb-1">{block.title}</h2>
{/if}
{#if descriptionHtml}
<div class="markdown text-white/80 max-w-none prose prose-sm prose-invert [&_*]:text-white/80 [&_a]:text-wald-200 [&_strong]:text-white">
<div class="markdown prose prose-sm max-w-none text-stein-600">
{@html descriptionHtml}
</div>
{/if}
@@ -216,58 +210,42 @@
class="sticky top-14 z-10 px-4 md:px-6 py-3 border-b border-stein-200 bg-white/95 backdrop-blur-sm shadow-sm"
>
<div class="flex flex-col gap-2">
<div class="relative flex items-center gap-2">
<Tooltip content={helpTooltipText} placement="top">
<button
type="button"
class="shrink-0 p-1.5 rounded-xs text-stein-400 hover:text-wald-600 hover:bg-wald-50 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-wald-500 focus-visible:ring-offset-1"
aria-label={t(T.searchable_text_help_aria)}
>
<Icon icon="mdi:help-circle-outline" class="size-5" aria-hidden="true" />
</button>
</Tooltip>
<div class="relative max-w-sm flex-1">
<Icon icon="mdi:magnify" class="absolute left-2.5 top-1/2 size-4 -translate-y-1/2 text-stein-400 pointer-events-none" aria-hidden="true" />
<input
type="search"
placeholder={t(T.searchable_text_placeholder)}
class="w-full text-sm px-4 py-2.5 pr-10 border border-stein-200 rounded-xs focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-wald-500 focus-visible:border-wald-400 bg-white text-stein-900 placeholder:text-stein-400"
class="w-full rounded-sm border border-stein-200 bg-white py-1.5 pl-8 pr-8 text-sm text-stein-900 placeholder:text-stein-400 focus:border-wald-400 focus:outline-none focus:ring-1 focus:ring-wald-400"
bind:value={searchQuery}
oninput={(e) => (searchQuery = (e.target as HTMLInputElement).value)}
aria-label={t(T.searchable_text_search_aria)}
/>
{#if searchQuery}
<button
type="button"
class="absolute right-9 top-1/2 -translate-y-1/2 p-1.5 rounded-xs hover:bg-stein-100 text-stein-400 hover:text-stein-700 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-wald-500"
class="absolute right-2 top-1/2 -translate-y-1/2 text-stein-400 hover:text-stein-700 transition-colors"
onclick={() => (searchQuery = "")}
aria-label={t(T.searchable_text_clear_search)}
>
<Icon icon="mdi:close" class="size-4" aria-hidden="true" />
</button>
{/if}
<Icon
icon="mdi:magnify"
class="absolute right-3 top-1/2 -translate-y-1/2 text-stein-400 pointer-events-none size-5"
aria-hidden="true"
/>
</div>
{#if allTags.length > 0}
<div class="overflow-x-auto -mx-1 px-1 pt-1 pb-1">
<div class="flex gap-1.5 flex-wrap">
<Tag
label={`${t(T.searchable_text_tag_all)} (${tagCounts.allCount})`}
variant={selectedTag === "all" ? "green" : "white"}
active={selectedTag === "all"}
onclick={() => (selectedTag = "all")}
/>
{#each allTags as tag}
<Tag
label={`${tag} (${tagCounts.counts.get(tag) ?? 0})`}
variant={selectedTag === tag ? "green" : "white"}
active={selectedTag === tag}
onclick={() => (selectedTag = tag)}
/>
{/each}
</div>
<div class="flex flex-wrap gap-1">
<button
onclick={() => (selectedTag = "all")}
class="chip {selectedTag === 'all' ? 'border-wald-400 bg-wald-500 text-white' : 'border-stein-300 text-stein-700 hover:bg-stein-100'}"
>
{t(T.searchable_text_tag_all)} ({tagCounts.allCount})
</button>
{#each allTags as tag}
<button
onclick={() => (selectedTag = selectedTag === tag ? "all" : tag)}
class="chip {selectedTag === tag ? 'border-wald-400 bg-wald-500 text-white' : 'border-stein-300 text-stein-700 hover:bg-stein-100'}"
>
{tag} ({tagCounts.counts.get(tag) ?? 0})
</button>
{/each}
</div>
{/if}
</div>
@@ -310,9 +288,7 @@
{@const itemKey = fragment.title || String(i)}
{@const isOpen = openTitles.has(itemKey)}
<div
class="group/item border-l-4 transition-colors {isOpen
? 'border-l-wald-500 bg-wald-50/30'
: 'border-l-transparent bg-white'}"
class="group/item transition-colors {isOpen ? 'bg-wald-50/30' : 'bg-white'}"
>
<div
role="button"
@@ -334,18 +310,14 @@
<div class="shrink-0 flex items-center gap-2">
<button
type="button"
class="opacity-0 group-hover/item:opacity-100 focus-visible:opacity-100 inline-flex items-center gap-1 px-2 py-1 text-xs font-normal rounded-xs border border-stein-200 bg-white text-stein-600 hover:bg-wald-50 hover:border-wald-300 hover:text-wald-800 transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-wald-500"
class="opacity-0 group-hover/item:opacity-100 focus-visible:opacity-100 text-stein-300 transition-colors hover:text-wald-600"
onclick={(e) => {
e.stopPropagation();
copyFragmentContent(fragment, i);
}}
aria-label={t(T.searchable_text_copy_aria)}
>
{#if copiedIndex === i}
<Icon icon="mdi:check" class="size-3.5 text-wald-600" aria-hidden="true" />
{:else}
<Icon icon="mdi:content-copy" class="size-3.5" aria-hidden="true" />
{/if}
<Icon icon={copiedIndex === i ? "mdi:check" : "mdi:content-copy"} class="size-3.5 {copiedIndex === i ? 'text-wald-600' : ''}" aria-hidden="true" />
</button>
<Icon
icon="mdi:chevron-down"
@@ -355,9 +327,11 @@
</div>
</div>
{#if fragment.tags.length > 0}
<span class="flex flex-wrap gap-1">
<Tags tags={fragment.tags} variant="white" />
</span>
<div class="flex flex-wrap gap-1">
{#each fragment.tags as tag}
<span class="inline-flex items-center whitespace-nowrap rounded-xs border border-stein-200 bg-stein-100 px-1.5 py-0 text-[0.65rem] font-medium text-stein-600">{tag}</span>
{/each}
</div>
{/if}
</div>
{#if isOpen}
@@ -368,20 +342,14 @@
<div class="flex justify-end mb-3 -mt-1">
<button
type="button"
class="inline-flex items-center gap-1.5 px-2.5 py-1.5 text-xs font-medium rounded-xs border border-stein-200 bg-stein-50 text-stein-700 hover:bg-wald-50 hover:border-wald-300 hover:text-wald-800 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-wald-500"
class="text-stein-300 transition-colors hover:text-wald-600"
onclick={(e) => {
e.preventDefault();
copyFragmentContent(fragment, i);
}}
aria-label={t(T.searchable_text_copy_aria)}
>
{#if copiedIndex === i}
<Icon icon="mdi:check" class="size-4 text-wald-600" aria-hidden="true" />
<span>{t(T.searchable_text_copied)}</span>
{:else}
<Icon icon="mdi:content-copy" class="size-4" aria-hidden="true" />
<span>{t(T.searchable_text_copy_button)}</span>
{/if}
<Icon icon={copiedIndex === i ? "mdi:check" : "mdi:content-copy"} class="size-4 {copiedIndex === i ? 'text-wald-600' : ''}" aria-hidden="true" />
</button>
</div>
{@html highlightInHtml(fragment.textHtml, searchQuery.trim())}