feat(windkarte): InfoAccordion, anlagen-Hinweis, Tooltip ohne Icons
- Disclaimer aus CMS-Feld (hinweis_text) via InfoAccordion statt Hardcode - info_text-Feld für Block-Fließtext ergänzt - anlagen_geplant_hinweis-Feld in WindArea + Panel-Anzeige - Tooltip-Icons entfernt (square-outline/wind-turbine/map-marker inline SVGs) - Panel: square-outline → ruler-square für Fläche - Iconify-Subset neu generiert Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
<!--
|
||||
Reusable disclosure box for short collapsible info notes.
|
||||
- `title` — always-visible summary line
|
||||
- `body` — HTML rendered inside the panel ({@html})
|
||||
- `variant` — colour palette without callers passing raw Tailwind:
|
||||
stein → neutral data-disclaimer tone
|
||||
amber → soft warning, used for uncertainty-flagged hints
|
||||
- `open` — initial state; default closed
|
||||
-->
|
||||
<script lang="ts">
|
||||
import Icon from "@iconify/svelte";
|
||||
|
||||
type Variant = "stein" | "amber";
|
||||
|
||||
let {
|
||||
title,
|
||||
body = "",
|
||||
variant = "stein",
|
||||
open = false,
|
||||
iconName = "mdi:information-outline",
|
||||
}: {
|
||||
title: string;
|
||||
body?: string;
|
||||
variant?: Variant;
|
||||
open?: boolean;
|
||||
iconName?: string;
|
||||
} = $props();
|
||||
|
||||
const palettes = {
|
||||
stein: {
|
||||
container: "border-stein-200 bg-stein-50",
|
||||
summary: "text-stein-700 hover:bg-stein-100",
|
||||
icon: "text-stein-500",
|
||||
body: "border-stein-200 text-stein-600",
|
||||
},
|
||||
amber: {
|
||||
container: "border-amber-300 bg-amber-100/70",
|
||||
summary: "text-amber-800 hover:bg-amber-200/70",
|
||||
icon: "text-amber-700",
|
||||
body: "border-amber-200 text-stein-700",
|
||||
},
|
||||
} as const;
|
||||
|
||||
const p = $derived(palettes[variant]);
|
||||
</script>
|
||||
|
||||
<details class="info-accordion rounded-md border text-xs {p.container}" {open}>
|
||||
<summary class="flex cursor-pointer items-center gap-1.5 px-3 py-1.5 font-medium leading-snug list-none {p.summary}">
|
||||
<Icon icon={iconName} class="size-3.5 shrink-0 {p.icon}" />
|
||||
<span class="flex-1">{title}</span>
|
||||
<Icon icon="mdi:chevron-down" class="info-accordion-chevron size-3.5 shrink-0 transition-transform {p.icon}" />
|
||||
</summary>
|
||||
<div class="markdown prose prose-sm prose-zinc max-w-none border-t px-3 py-2 leading-relaxed {p.body}">{@html body}</div>
|
||||
</details>
|
||||
|
||||
<style>
|
||||
.info-accordion[open] :global(.info-accordion-chevron) {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
</style>
|
||||
@@ -3,6 +3,7 @@
|
||||
import "$lib/iconify-offline";
|
||||
import type { WindArea, TextFragment } from "$lib/windkarte";
|
||||
import { useTranslate, T } from "$lib/translations";
|
||||
import InfoAccordion from "$lib/components/InfoAccordion.svelte";
|
||||
|
||||
let {
|
||||
area,
|
||||
@@ -119,22 +120,53 @@
|
||||
<div class="flex flex-col divide-y divide-stein-100 border border-stein-100 rounded-lg overflow-hidden text-sm">
|
||||
{#if area.flaeche_ha != null}
|
||||
<div class="flex items-center justify-between px-3 py-1.5">
|
||||
<span class="text-xs text-stein-500">{t(T.windkarte_label_flaeche)}</span>
|
||||
<span class="flex items-center gap-1.5 text-xs text-stein-500">
|
||||
<Icon icon="mdi:ruler-square" class="size-3.5 text-stein-400" />
|
||||
{t(T.windkarte_label_flaeche)}
|
||||
</span>
|
||||
<span class="font-semibold text-stein-800">{area.flaeche_ha.toFixed(1)} ha</span>
|
||||
</div>
|
||||
{/if}
|
||||
{#if area.anlagen_geplant != null}
|
||||
<div class="flex items-center justify-between px-3 py-1.5">
|
||||
<span class="flex items-center gap-1.5 text-xs text-stein-500">
|
||||
<Icon icon="mdi:wind-turbine" class="size-3.5 text-stein-400" />
|
||||
{t(T.windkarte_label_anlagen_geplant)}
|
||||
</span>
|
||||
<span class="flex items-center gap-1.5 font-semibold text-stein-800">
|
||||
{area.anlagen_geplant}
|
||||
{#if area.anlagen_geplant_hinweis}
|
||||
<Icon icon="mdi:information-outline" class="size-3.5 text-amber-700" />
|
||||
{/if}
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
{#if area.investor}
|
||||
<div class="flex items-center justify-between px-3 py-1.5">
|
||||
<span class="text-xs text-stein-500">{t(T.windkarte_label_investor)}</span>
|
||||
<span class="flex items-center gap-1.5 text-xs text-stein-500">
|
||||
<Icon icon="mdi:domain" class="size-3.5 text-stein-400" />
|
||||
{t(T.windkarte_label_investor)}
|
||||
</span>
|
||||
<span class="font-semibold text-stein-800">{area.investor}</span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if area.anlagen_geplant_hinweis}
|
||||
<InfoAccordion
|
||||
title={t(T.windkarte_label_anlagen_hinweis)}
|
||||
body={area.anlagen_geplant_hinweis}
|
||||
variant="amber"
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<!-- Gemeinden -->
|
||||
{#if area.gemeinden && area.gemeinden.length > 0}
|
||||
<div>
|
||||
<p class="mb-2 text-xs font-medium uppercase tracking-wide text-stein-500">{t(T.windkarte_label_gemeinden)}</p>
|
||||
<p class="mb-2 flex items-center gap-1.5 text-xs font-medium uppercase tracking-wide text-stein-500">
|
||||
<Icon icon="mdi:map-marker" class="size-3.5 text-stein-400" />
|
||||
{t(T.windkarte_label_gemeinden)}
|
||||
</p>
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
{#each area.gemeinden as g}
|
||||
<span class="rounded-full bg-himmel-100 px-2.5 py-0.5 text-xs font-medium text-himmel-800">{g}</span>
|
||||
|
||||
@@ -283,20 +283,30 @@
|
||||
await selectFeature(L, feature, layer, area);
|
||||
});
|
||||
|
||||
// Hover tooltip
|
||||
// Hover tooltip — labels rendered bold via <strong>, an info glyph
|
||||
// gets appended after the count when the editor supplied a hint
|
||||
// string so users know there's more context in the side panel.
|
||||
const nrLabel = area.gebiets_nr ?? p.name ?? "";
|
||||
const title = area.bezeichnung
|
||||
? `${nrLabel} – ${area.bezeichnung}`
|
||||
: nrLabel;
|
||||
const lines: string[] = [];
|
||||
if (area.flaeche_ha != null) lines.push(`${t(T.windkarte_label_flaeche)}: ${area.flaeche_ha.toFixed(1)} ha`);
|
||||
if (area.gemeinden && area.gemeinden.length > 0) {
|
||||
lines.push(`${t(T.windkarte_label_gemeinden)}: ${area.gemeinden.join(', ')}`);
|
||||
}
|
||||
const esc = (s: string) => s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||
const row = (label: string, value: string, suffix = "") =>
|
||||
`<strong>${esc(label)}:</strong> ${esc(value)}${suffix}`;
|
||||
const lines: string[] = [];
|
||||
if (area.flaeche_ha != null) {
|
||||
lines.push(row(t(T.windkarte_label_flaeche), `${area.flaeche_ha.toFixed(1)} ha`));
|
||||
}
|
||||
if (area.anlagen_geplant != null) {
|
||||
const hint = area.anlagen_geplant_hinweis ? " *" : "";
|
||||
lines.push(row(t(T.windkarte_label_anlagen_geplant), `${area.anlagen_geplant}`, hint));
|
||||
}
|
||||
if (area.gemeinden && area.gemeinden.length > 0) {
|
||||
lines.push(row(t(T.windkarte_label_gemeinden), area.gemeinden.join(', ')));
|
||||
}
|
||||
const tooltipHtml =
|
||||
`<div class="font-semibold">${esc(title)}</div>` +
|
||||
(lines.length ? `<div class="mt-0.5 text-xs opacity-90">${lines.map(esc).join('<br/>')}</div>` : '');
|
||||
`<div class="text-sm font-bold pb-1 mb-1 border-b border-stein-200/60">${esc(title)}</div>` +
|
||||
(lines.length ? `<div class="text-xs opacity-90 leading-relaxed">${lines.join('<br/>')}</div>` : '');
|
||||
layer.bindTooltip(tooltipHtml, { sticky: true, className: "windkarte-tooltip" });
|
||||
|
||||
// Permanent label via divIcon marker at polygon center
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import type { WindMapBlockData } from "$lib/block-types";
|
||||
import type { WindArea } from "$lib/windkarte";
|
||||
import WindAreaPanel from "$lib/components/WindAreaPanel.svelte";
|
||||
import InfoAccordion from "$lib/components/InfoAccordion.svelte";
|
||||
import { useTranslate, T } from "$lib/translations";
|
||||
import { getBlockLayoutClasses } from "$lib/block-layout";
|
||||
import Icon from "@iconify/svelte";
|
||||
@@ -103,7 +104,7 @@ let areas = $state<WindArea[]>([]);
|
||||
data-block-type="wind_map"
|
||||
data-block-slug={block._slug}
|
||||
>
|
||||
{#if block.title || block.subtitle}
|
||||
{#if block.title || block.subtitle || block.info_text}
|
||||
<div class="mb-4">
|
||||
{#if block.title}
|
||||
<h2 class="text-xl font-semibold text-stein-800">{block.title}</h2>
|
||||
@@ -111,20 +112,18 @@ let areas = $state<WindArea[]>([]);
|
||||
{#if block.subtitle}
|
||||
<p class="mt-0.5 text-sm text-stein-500">{block.subtitle}</p>
|
||||
{/if}
|
||||
{#if block.info_text}
|
||||
<div class="markdown prose prose-sm prose-zinc max-w-none mt-2 text-stein-600">{@html block.info_text}</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Daten-Disclaimer (default geschlossen, oberhalb der Karte) -->
|
||||
<details class="data-disclaimer mb-3 rounded-md border border-stein-200 bg-stein-50 text-xs text-stein-600">
|
||||
<summary class="flex cursor-pointer items-center gap-1.5 px-3 py-1.5 font-medium text-stein-700 hover:bg-stein-100 list-none">
|
||||
<Icon icon="mdi:information-outline" class="size-3.5 shrink-0 text-stein-500" />
|
||||
<span class="flex-1">Hinweis zu den Daten</span>
|
||||
<Icon icon="mdi:chevron-down" class="disclaimer-chevron size-4 shrink-0 text-stein-500 transition-transform" />
|
||||
</summary>
|
||||
<p class="border-t border-stein-200 px-3 py-2 leading-relaxed">
|
||||
Diese Karte zeigt zu den Vorranggebieten die amtlichen Angaben aus den Prüfbögen, also Flächengröße und Windhöffigkeit. Wir hatten zusätzlich eigene Schätzwerte zu Anlagenzahl und Anlagenhöhe ergänzt. Diese waren keine amtlichen Festlegungen, der Regionalplan legt beides nicht fest, das entscheidet sich erst im Genehmigungsverfahren. Wir haben diese Schätzwerte entfernt, um Missverständnisse zu vermeiden. Wer einen Fehler findet, kann sich gern bei uns melden.
|
||||
</p>
|
||||
</details>
|
||||
<!-- Daten-Disclaimer aus dem CMS-Feld `hinweis_text` -->
|
||||
{#if block.hinweis_text}
|
||||
<div class="mb-3">
|
||||
<InfoAccordion title="Hinweis zu den Daten" body={block.hinweis_text} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="map-container relative overflow-hidden rounded-xl border border-stein-200 shadow-sm">
|
||||
<!-- Reset button -->
|
||||
@@ -235,10 +234,6 @@ let areas = $state<WindArea[]>([]);
|
||||
height: clamp(460px, 75vh, 620px);
|
||||
}
|
||||
|
||||
.data-disclaimer[open] :global(.disclaimer-chevron) {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
/* Panel: mobile = bottom sheet, sm+ = right side panel */
|
||||
.panel-slide {
|
||||
position: absolute;
|
||||
|
||||
Reference in New Issue
Block a user