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:
@@ -65,6 +65,8 @@ const EXTRA_ICONS = [
|
||||
'tag-outline',
|
||||
'flag-outline',
|
||||
'information-outline',
|
||||
// Windkarte: dynamic wind-turbine used in panel
|
||||
'wind-turbine',
|
||||
// OrganisationsMapBlock: activate-overlay
|
||||
'cursor-default-click-outline',
|
||||
// CalendarBlock: social-image modal button
|
||||
|
||||
@@ -486,6 +486,12 @@ export interface WindMapBlockData {
|
||||
subtitle?: string;
|
||||
/** Explizite Auswahl von wind_area-Slugs. Leer/null = alle published areas. */
|
||||
areas?: (string | { _slug: string;[key: string]: unknown })[];
|
||||
/** Optionales Disclaimer-Accordion oberhalb der Karte. HTML/Markdown aus
|
||||
* dem CMS — Komponente rendert es via {@html}. */
|
||||
hinweis_text?: string;
|
||||
/** Beschreibender Fließtext direkt unter dem Titel (Statistik, Interaktions-
|
||||
* Hinweise, Methodik). HTML aus dem CMS. */
|
||||
info_text?: string;
|
||||
layout?: BlockLayout;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -84,6 +84,18 @@
|
||||
"download": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M5 20h14v-2H5m14-9h-4V3H9v6H5l7 7z\"/>"
|
||||
},
|
||||
"ruler-square": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M3 5v16h6v-1.5H7V18h2v-1.5H5V15h4v-1.5H7V12h2v-1.5H5V9h4V5h1.5v4H12V7h1.5v2H15V5h1.5v4H18V7h1.5v2H21V3H5a2 2 0 0 0-2 2m3 2a1 1 0 0 1-1-1a1 1 0 0 1 1-1a1 1 0 0 1 1 1a1 1 0 0 1-1 1\"/>"
|
||||
},
|
||||
"wind-turbine": {
|
||||
"body": "<path fill=\"currentColor\" d=\"m13.33 11.67l2.88 2.91c1.41-1.42 0-2.83 0-2.83l-1.49-1.51c.18-.38.28-.8.28-1.24c0-1.05-.54-1.97-1.36-2.5L15 2.11c-1.91-.58-2.5 1.33-2.5 1.33l-.81 2.59c-1.23.13-2.23.97-2.56 2.15L4.67 9.63c.64 1.9 2.53 1.27 2.53 1.27l2.07-.67c.34.74.96 1.31 1.73 1.59V19s-2 0-2 2v1h6v-1s0-2-2-2v-7.18c.12-.04.23-.1.33-.15M10.5 9A1.5 1.5 0 0 1 12 7.5A1.5 1.5 0 0 1 13.5 9a1.5 1.5 0 0 1-1.5 1.5A1.5 1.5 0 0 1 10.5 9\"/>"
|
||||
},
|
||||
"information-outline": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M11 9h2V7h-2m1 13c-4.41 0-8-3.59-8-8s3.59-8 8-8s8 3.59 8 8s-3.59 8-8 8m0-18A10 10 0 0 0 2 12a10 10 0 0 0 10 10a10 10 0 0 0 10-10A10 10 0 0 0 12 2m-1 15h2v-6h-2z\"/>"
|
||||
},
|
||||
"domain": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M18 15h-2v2h2m0-6h-2v2h2m2 6h-8v-2h2v-2h-2v-2h2v-2h-2V9h8M10 7H8V5h2m0 6H8V9h2m0 6H8v-2h2m0 6H8v-2h2M6 7H4V5h2m0 6H4V9h2m0 6H4v-2h2m0 6H4v-2h2m6-10V3H2v18h20V7z\"/>"
|
||||
},
|
||||
"loading": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M12 4V2A10 10 0 0 0 2 12h2a8 8 0 0 1 8-8\"/>"
|
||||
},
|
||||
@@ -165,9 +177,6 @@
|
||||
"vector-square": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M2 2h6v2h8V2h6v6h-2v8h2v6h-6v-2H8v2H2v-6h2V8H2zm14 6V6H8v2H6v8h2v2h8v-2h2V8zM4 4v2h2V4zm14 0v2h2V4zM4 18v2h2v-2zm14 0v2h2v-2z\"/>"
|
||||
},
|
||||
"domain": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M18 15h-2v2h2m0-6h-2v2h2m2 6h-8v-2h2v-2h-2v-2h2v-2h-2V9h8M10 7H8V5h2m0 6H8V9h2m0 6H8v-2h2m0 6H8v-2h2M6 7H4V5h2m0 6H4V9h2m0 6H4v-2h2m0 6H4v-2h2m6-10V3H2v18h20V7z\"/>"
|
||||
},
|
||||
"flag-outline": {
|
||||
"body": "<path fill=\"currentColor\" d=\"m12.36 6l.4 2H18v6h-3.36l-.4-2H7V6zM14 4H5v17h2v-7h5.6l.4 2h7V6h-5.6\"/>"
|
||||
},
|
||||
@@ -210,9 +219,6 @@
|
||||
"alert-circle": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M13 13h-2V7h2m0 10h-2v-2h2M12 2A10 10 0 0 0 2 12a10 10 0 0 0 10 10a10 10 0 0 0 10-10A10 10 0 0 0 12 2\"/>"
|
||||
},
|
||||
"information-outline": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M11 9h2V7h-2m1 13c-4.41 0-8-3.59-8-8s3.59-8 8-8s8 3.59 8 8s-3.59 8-8 8m0-18A10 10 0 0 0 2 12a10 10 0 0 0 10 10a10 10 0 0 0 10-10A10 10 0 0 0 12 2m-1 15h2v-6h-2z\"/>"
|
||||
},
|
||||
"play": {
|
||||
"body": "<path fill=\"currentColor\" d=\"M8 5.14v14l11-7z\"/>"
|
||||
},
|
||||
|
||||
@@ -207,6 +207,7 @@ const TRANSLATION_KEYS = [
|
||||
"windkarte_status_entwurf_3",
|
||||
"windkarte_label_flaeche",
|
||||
"windkarte_label_anlagen_geplant",
|
||||
"windkarte_label_anlagen_hinweis",
|
||||
"windkarte_label_hoehe_max",
|
||||
"windkarte_label_investor",
|
||||
"windkarte_label_gemeinden",
|
||||
|
||||
@@ -14,6 +14,7 @@ export type WindArea = {
|
||||
status?: "rechtskraeftig" | "entwurf_2" | "entwurf_2_voraussichtlich" | "entwurf_3";
|
||||
flaeche_ha?: number;
|
||||
anlagen_geplant?: number;
|
||||
anlagen_geplant_hinweis?: string;
|
||||
hoehe_max_m?: number;
|
||||
investor?: string;
|
||||
gemeinden?: string[];
|
||||
|
||||
Reference in New Issue
Block a user