Files
SnapOtter/apps/web/src/components/tools/qr-generate-preview.tsx
T
Siddharth Kumar Sah 93c588b239 fix(lint): resolve all biome warnings across API, web, and image-engine
API:
- Replace string concatenation with template literals (batch, pipeline,
  tool-factory, content-aware-resize, passport-photo, remove-background)
- Remove unused imports (teams, color-adjustments, image-enhancement)
- Replace non-null assertions with guard clauses in bg-effects and stitch
- Use optional chaining in docs route

Image-engine:
- Remove unused OutputFormat imports (compress, convert)
- Use local variables instead of reassigning parameters (optimize-for-web, sharpen)

Web:
- Fix useExhaustiveDependencies: remove genuinely redundant deps, add
  biome-ignore comments for intentional patterns (src prop, cleanup fns)
- Replace non-null assertions with null-safe alternatives
- Add accessible titles to inline SVGs (color-settings, image-enhancement-settings)
- Fix noLabelWithoutControl: associate labels via htmlFor/id or use <span>
  (image-to-base64-settings, edit-metadata-settings, qr-generate-settings)
- Replace div[role="button"] with <button> (pdf-to-image-settings)
- Use stable keys instead of array indices (collage-preview, collage-settings)
- Fix noStaticElementInteractions in collage-preview (role="none")
- Remove unused function parameters and imports
2026-04-14 22:15:37 +08:00

129 lines
3.9 KiB
TypeScript

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
// biome-ignore lint/correctness/useExhaustiveDependencies: options is intentionally excluded to only create QR instance once on mount
useEffect(() => {
const qr = new QRCodeStyling(options as never);
qrRef.current = qr;
if (containerRef.current) {
clearChildren(containerRef.current);
qr.append(containerRef.current);
}
}, []);
// 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>
);
}