Files
windwiderstand/src/lib/components/QrModal.svelte
T
Peter Meier 0ab2c58eb4
Deploy / verify (push) Successful in 1m26s
Deploy / deploy (push) Successful in 2m26s
feat(header): icon nav redesign + migrate icons mdi→lucide
- Header desktop nav: social icons grouped in a wald-tinted pill with
  labels, active-state (aria-current + bold), opacity hover (no per-item bg)
- Mobile overlay: social links as icon+label rows with active-state
- Search button: lucide:search, icon-only
- Migrate all icons mdi→lucide app-wide (234 refs); brand/language logos
  (google, whatsapp, mastodon, telegram, language-*) stay on mdi
- generate-icons: emit mdi + lucide subsets; add @iconify-json/lucide
- iconify-offline: register lucide collection
- scripts/icon-map|validate|apply: reusable migration tooling

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 10:44:56 +02:00

102 lines
2.7 KiB
Svelte

<script lang="ts">
import { onMount } from "svelte";
import Icon from "@iconify/svelte";
import "$lib/iconify-offline";
import QRCode from "qrcode";
let {
value,
label = "",
sublabel = "",
onclose,
}: {
value: string | null;
label?: string;
sublabel?: string;
onclose: () => void;
} = $props();
let qrSvg = $state("");
let qrPngUrl = $state("");
$effect(() => {
if (!value) { qrSvg = ""; qrPngUrl = ""; return; }
QRCode.toString(value, {
type: "svg",
errorCorrectionLevel: "M",
margin: 2,
color: { dark: "#1c1917", light: "#ffffff" },
}).then((svg) => { qrSvg = svg; });
QRCode.toDataURL(value, {
errorCorrectionLevel: "M",
margin: 2,
width: 512,
color: { dark: "#1c1917", light: "#ffffff" },
}).then((url) => { qrPngUrl = url; });
});
function downloadPng() {
if (!qrPngUrl) return;
const a = document.createElement("a");
a.href = qrPngUrl;
a.download = `${(label || "qrcode").toLowerCase().replace(/\s+/g, "-")}.png`;
a.click();
}
function handleKeydown(e: KeyboardEvent) {
if (e.key === "Escape") onclose();
}
onMount(() => {
window.addEventListener("keydown", handleKeydown);
return () => window.removeEventListener("keydown", handleKeydown);
});
</script>
{#if value}
<!-- svelte-ignore a11y_click_events_have_key_events a11y_no_static_element_interactions -->
<div
class="not-prose fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4"
onclick={onclose}
>
<div
class="relative w-full max-w-xs rounded-sm border border-stein-200 bg-white p-6 shadow-xl"
onclick={(e) => e.stopPropagation()}
role="dialog"
aria-modal="true"
>
<button
type="button"
onclick={onclose}
class="absolute right-3 top-3 text-stein-400 hover:text-stein-700"
aria-label="Schließen"
>
<Icon icon="lucide:x" class="size-5" />
</button>
{#if label}
<p class="mb-1 text-sm font-medium text-stein-900">{label}</p>
{/if}
{#if sublabel}
<p class="mb-4 text-xs text-stein-400">{sublabel}</p>
{:else}
<div class="mb-4"></div>
{/if}
{#if qrSvg}
{@html qrSvg}
{:else}
<div class="flex h-48 items-center justify-center text-xs text-stein-300">Generiere…</div>
{/if}
<p class="mt-3 text-center text-[0.65rem] text-stein-400">QR-Code scannen</p>
<button
type="button"
onclick={downloadPng}
disabled={!qrPngUrl}
class="btn-outline btn-sm mt-3 w-full justify-center"
>
<Icon icon="lucide:download" class="size-3.5" />
Als PNG herunterladen
</button>
</div>
</div>
{/if}