0ab2c58eb4
- 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>
102 lines
2.7 KiB
Svelte
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}
|