2026-02-28 00:58:57 +01:00
|
|
|
'use client';
|
|
|
|
|
|
|
|
|
|
import { Copy, Share2, Image as ImageIcon, FileCode, QrCode } from 'lucide-react';
|
2026-03-03 10:26:53 +01:00
|
|
|
import { cn, actionBtn } from '@/lib/utils';
|
2026-02-28 00:58:57 +01:00
|
|
|
import type { ExportSize } from '@/types/qrcode';
|
|
|
|
|
|
|
|
|
|
interface QRPreviewProps {
|
|
|
|
|
svgString: string;
|
|
|
|
|
isGenerating: boolean;
|
|
|
|
|
exportSize: ExportSize;
|
|
|
|
|
onExportSizeChange: (size: ExportSize) => void;
|
|
|
|
|
onCopyImage: () => void;
|
|
|
|
|
onShare: () => void;
|
|
|
|
|
onDownloadPng: () => void;
|
|
|
|
|
onDownloadSvg: () => void;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-01 08:37:39 +01:00
|
|
|
const EXPORT_SIZES: { value: ExportSize; label: string }[] = [
|
|
|
|
|
{ value: 256, label: '256' },
|
|
|
|
|
{ value: 512, label: '512' },
|
|
|
|
|
{ value: 1024, label: '1k' },
|
|
|
|
|
{ value: 2048, label: '2k' },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
2026-02-28 00:58:57 +01:00
|
|
|
export function QRPreview({
|
|
|
|
|
svgString,
|
|
|
|
|
isGenerating,
|
|
|
|
|
exportSize,
|
|
|
|
|
onExportSizeChange,
|
|
|
|
|
onCopyImage,
|
|
|
|
|
onShare,
|
|
|
|
|
onDownloadPng,
|
|
|
|
|
onDownloadSvg,
|
|
|
|
|
}: QRPreviewProps) {
|
|
|
|
|
return (
|
2026-03-01 08:37:39 +01:00
|
|
|
<div className="glass rounded-xl p-4 flex flex-col flex-1 min-h-0 overflow-hidden">
|
2026-02-28 00:58:57 +01:00
|
|
|
|
2026-03-01 08:37:39 +01:00
|
|
|
{/* Action bar */}
|
|
|
|
|
<div className="flex items-center gap-1.5 mb-4 shrink-0 flex-wrap">
|
|
|
|
|
<span className="text-[10px] font-semibold text-muted-foreground uppercase tracking-widest mr-auto">
|
|
|
|
|
Preview
|
|
|
|
|
</span>
|
2026-02-28 00:58:57 +01:00
|
|
|
|
2026-03-01 08:37:39 +01:00
|
|
|
<button onClick={onCopyImage} disabled={!svgString} className={actionBtn}>
|
|
|
|
|
<Copy className="w-3 h-3" />Copy
|
|
|
|
|
</button>
|
2026-02-28 00:58:57 +01:00
|
|
|
|
2026-03-01 08:37:39 +01:00
|
|
|
<button onClick={onShare} disabled={!svgString} className={actionBtn}>
|
|
|
|
|
<Share2 className="w-3 h-3" />Share
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
{/* PNG + inline size selector */}
|
|
|
|
|
<div className="flex items-center glass rounded-md border border-border/30">
|
|
|
|
|
<button
|
|
|
|
|
onClick={onDownloadPng}
|
|
|
|
|
disabled={!svgString}
|
|
|
|
|
className="flex items-center gap-1 pl-2.5 pr-1.5 py-1 text-xs text-muted-foreground hover:text-primary transition-all disabled:opacity-40 disabled:cursor-not-allowed border-r border-border/20"
|
|
|
|
|
>
|
|
|
|
|
<ImageIcon className="w-3 h-3" />PNG
|
|
|
|
|
</button>
|
|
|
|
|
<div className="flex items-center px-1 gap-0.5">
|
|
|
|
|
{EXPORT_SIZES.map(({ value, label }) => (
|
|
|
|
|
<button
|
|
|
|
|
key={value}
|
|
|
|
|
onClick={() => onExportSizeChange(value)}
|
|
|
|
|
className={cn(
|
|
|
|
|
'text-[9px] font-mono px-1.5 py-0.5 rounded transition-all',
|
|
|
|
|
exportSize === value
|
|
|
|
|
? 'text-primary bg-primary/10'
|
|
|
|
|
: 'text-muted-foreground/40 hover:text-muted-foreground'
|
|
|
|
|
)}
|
|
|
|
|
>
|
|
|
|
|
{label}
|
|
|
|
|
</button>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
2026-02-28 00:58:57 +01:00
|
|
|
</div>
|
2026-03-01 08:37:39 +01:00
|
|
|
|
|
|
|
|
<button onClick={onDownloadSvg} disabled={!svgString} className={actionBtn}>
|
|
|
|
|
<FileCode className="w-3 h-3" />SVG
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* QR canvas */}
|
|
|
|
|
<div
|
|
|
|
|
className="flex-1 min-h-0 rounded-xl flex items-center justify-center"
|
|
|
|
|
style={{
|
|
|
|
|
backgroundImage: 'repeating-conic-gradient(rgba(255,255,255,0.025) 0% 25%, transparent 0% 50%)',
|
|
|
|
|
backgroundSize: '16px 16px',
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{isGenerating ? (
|
|
|
|
|
<div className="w-56 h-56 rounded-xl bg-white/5 animate-pulse" />
|
|
|
|
|
) : svgString ? (
|
|
|
|
|
<div
|
|
|
|
|
className="w-full max-w-sm aspect-square [&>svg]:w-full [&>svg]:h-full p-6"
|
|
|
|
|
dangerouslySetInnerHTML={{ __html: svgString }}
|
|
|
|
|
/>
|
|
|
|
|
) : (
|
|
|
|
|
<div className="flex flex-col items-center gap-3 text-center">
|
|
|
|
|
<div className="w-14 h-14 rounded-full bg-primary/10 flex items-center justify-center">
|
|
|
|
|
<QrCode className="w-6 h-6 text-primary/40" />
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<p className="text-sm font-medium text-foreground/40">No QR code yet</p>
|
|
|
|
|
<p className="text-[10px] text-muted-foreground/30 font-mono mt-1">Enter text or a URL to generate</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-02-28 00:58:57 +01:00
|
|
|
);
|
|
|
|
|
}
|