mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
feat(qr-generate): add live QR preview panel with qr-code-styling
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
import QRCodeStyling from "qr-code-styling";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { encodeQrData, useQrStore } from "@/stores/qr-store";
|
||||
|
||||
const CHECKER_BG = "repeating-conic-gradient(#e5e5e5 0% 25%, #ffffff 0% 50%) 0 0 / 20px 20px";
|
||||
|
||||
function clearChildren(el: HTMLElement) {
|
||||
while (el.firstChild) {
|
||||
el.removeChild(el.firstChild);
|
||||
}
|
||||
}
|
||||
|
||||
export function QrGeneratePreview() {
|
||||
const store = useQrStore();
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const qrRef = useRef<QRCodeStyling | null>(null);
|
||||
const [isEmpty, setIsEmpty] = useState(true);
|
||||
|
||||
const data = encodeQrData(store);
|
||||
|
||||
const options = useMemo(() => {
|
||||
const hasData = !!data;
|
||||
setIsEmpty(!hasData);
|
||||
|
||||
const dotsColor = store.dotGradientEnabled ? undefined : store.dotColor;
|
||||
const dotsGradient = store.dotGradientEnabled
|
||||
? {
|
||||
type: store.dotGradientType as "linear" | "radial",
|
||||
rotation: store.dotGradientRotation * (Math.PI / 180),
|
||||
colorStops: [
|
||||
{ offset: 0, color: store.dotGradientColor1 },
|
||||
{ offset: 1, color: store.dotGradientColor2 },
|
||||
],
|
||||
}
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
width: 300,
|
||||
height: 300,
|
||||
data: hasData ? data : "https://example.com",
|
||||
margin: 8,
|
||||
qrOptions: {
|
||||
errorCorrectionLevel: store.errorCorrection,
|
||||
},
|
||||
dotsOptions: {
|
||||
type: store.dotType,
|
||||
...(dotsColor ? { color: dotsColor } : {}),
|
||||
...(dotsGradient ? { gradient: dotsGradient } : {}),
|
||||
},
|
||||
cornersSquareOptions: {
|
||||
type: store.cornerSquareType,
|
||||
color: store.useCustomCornerColors ? store.cornerSquareColor : undefined,
|
||||
},
|
||||
cornersDotOptions: {
|
||||
type: store.cornerDotType,
|
||||
color: store.useCustomCornerColors ? store.cornerDotColor : undefined,
|
||||
},
|
||||
backgroundOptions: store.bgTransparent ? { color: "transparent" } : { color: store.bgColor },
|
||||
...(store.logoDataUrl
|
||||
? {
|
||||
image: store.logoDataUrl,
|
||||
imageOptions: {
|
||||
hideBackgroundDots: store.hideBackgroundDots,
|
||||
imageSize: store.logoSize,
|
||||
margin: store.logoMargin,
|
||||
crossOrigin: "anonymous" as const,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
}, [
|
||||
data,
|
||||
store.dotType,
|
||||
store.dotColor,
|
||||
store.dotGradientEnabled,
|
||||
store.dotGradientType,
|
||||
store.dotGradientColor1,
|
||||
store.dotGradientColor2,
|
||||
store.dotGradientRotation,
|
||||
store.cornerSquareType,
|
||||
store.cornerDotType,
|
||||
store.cornerSquareColor,
|
||||
store.cornerDotColor,
|
||||
store.useCustomCornerColors,
|
||||
store.bgColor,
|
||||
store.bgTransparent,
|
||||
store.errorCorrection,
|
||||
store.logoDataUrl,
|
||||
store.logoSize,
|
||||
store.logoMargin,
|
||||
store.hideBackgroundDots,
|
||||
]);
|
||||
|
||||
// Create QR instance on mount
|
||||
useEffect(() => {
|
||||
const qr = new QRCodeStyling(options as never);
|
||||
qrRef.current = qr;
|
||||
if (containerRef.current) {
|
||||
clearChildren(containerRef.current);
|
||||
qr.append(containerRef.current);
|
||||
}
|
||||
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
// Update QR on state changes (debounced)
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
if (qrRef.current) {
|
||||
qrRef.current.update(options as never);
|
||||
}
|
||||
}, 150);
|
||||
return () => clearTimeout(timer);
|
||||
}, [options]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center h-full w-full gap-4">
|
||||
<div
|
||||
className="rounded-xl border border-border p-6 shadow-sm"
|
||||
style={store.bgTransparent ? { background: CHECKER_BG } : undefined}
|
||||
>
|
||||
<div ref={containerRef} className="flex items-center justify-center" />
|
||||
</div>
|
||||
{isEmpty && (
|
||||
<p className="text-sm text-muted-foreground">Enter content to generate a QR code</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user