Add a sixth tool with live SVG preview, customizable foreground/background colors, error correction level, margin control, and export as PNG (256–2048px) or SVG. URL params enable shareable state. All processing runs client-side via the qrcode package. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
133 lines
4.6 KiB
TypeScript
133 lines
4.6 KiB
TypeScript
'use client';
|
|
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Skeleton } from '@/components/ui/skeleton';
|
|
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
|
|
import {
|
|
Tooltip,
|
|
TooltipContent,
|
|
TooltipTrigger,
|
|
} from '@/components/ui/tooltip';
|
|
import {
|
|
Empty,
|
|
EmptyDescription,
|
|
EmptyHeader,
|
|
EmptyMedia,
|
|
EmptyTitle,
|
|
} from '@/components/ui/empty';
|
|
import { Copy, Share2, Image as ImageIcon, FileCode, QrCode } from 'lucide-react';
|
|
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;
|
|
}
|
|
|
|
export function QRPreview({
|
|
svgString,
|
|
isGenerating,
|
|
exportSize,
|
|
onExportSizeChange,
|
|
onCopyImage,
|
|
onShare,
|
|
onDownloadPng,
|
|
onDownloadSvg,
|
|
}: QRPreviewProps) {
|
|
return (
|
|
<Card className="h-full flex flex-col">
|
|
<CardHeader className="flex flex-row items-center justify-between flex-wrap gap-2">
|
|
<CardTitle>Preview</CardTitle>
|
|
<div className="flex items-center gap-1.5 flex-wrap">
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<Button variant="outline" size="xs" onClick={onCopyImage} disabled={!svgString}>
|
|
<Copy className="h-3 w-3 mr-1" />
|
|
Copy
|
|
</Button>
|
|
</TooltipTrigger>
|
|
<TooltipContent>Copy image to clipboard</TooltipContent>
|
|
</Tooltip>
|
|
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<Button variant="outline" size="xs" onClick={onShare} disabled={!svgString}>
|
|
<Share2 className="h-3 w-3 mr-1" />
|
|
Share
|
|
</Button>
|
|
</TooltipTrigger>
|
|
<TooltipContent>Copy shareable URL</TooltipContent>
|
|
</Tooltip>
|
|
|
|
<div className="flex items-center gap-1">
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<Button variant="outline" size="xs" onClick={onDownloadPng} disabled={!svgString}>
|
|
<ImageIcon className="h-3 w-3 mr-1" />
|
|
PNG
|
|
</Button>
|
|
</TooltipTrigger>
|
|
<TooltipContent>Download as PNG</TooltipContent>
|
|
</Tooltip>
|
|
<ToggleGroup
|
|
type="single"
|
|
value={String(exportSize)}
|
|
onValueChange={(v) => v && onExportSizeChange(Number(v) as ExportSize)}
|
|
variant="outline"
|
|
size="sm"
|
|
>
|
|
<ToggleGroupItem value="256" className="h-6 px-1.5 min-w-0 text-[10px]">256</ToggleGroupItem>
|
|
<ToggleGroupItem value="512" className="h-6 px-1.5 min-w-0 text-[10px]">512</ToggleGroupItem>
|
|
<ToggleGroupItem value="1024" className="h-6 px-1.5 min-w-0 text-[10px]">1k</ToggleGroupItem>
|
|
<ToggleGroupItem value="2048" className="h-6 px-1.5 min-w-0 text-[10px]">2k</ToggleGroupItem>
|
|
</ToggleGroup>
|
|
</div>
|
|
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<Button variant="outline" size="xs" onClick={onDownloadSvg} disabled={!svgString}>
|
|
<FileCode className="h-3 w-3 mr-1" />
|
|
SVG
|
|
</Button>
|
|
</TooltipTrigger>
|
|
<TooltipContent>Download as SVG</TooltipContent>
|
|
</Tooltip>
|
|
</div>
|
|
</CardHeader>
|
|
<CardContent className="flex-1 flex flex-col">
|
|
<div className="flex-1 min-h-[200px] rounded-lg p-4 flex items-center justify-center"
|
|
style={{
|
|
backgroundImage: 'repeating-conic-gradient(hsl(var(--muted)) 0% 25%, transparent 0% 50%)',
|
|
backgroundSize: '16px 16px',
|
|
}}
|
|
>
|
|
{isGenerating ? (
|
|
<Skeleton className="h-[200px] w-[200px]" />
|
|
) : svgString ? (
|
|
<div
|
|
className="w-full max-w-[400px] aspect-square [&>svg]:w-full [&>svg]:h-full"
|
|
dangerouslySetInnerHTML={{ __html: svgString }}
|
|
/>
|
|
) : (
|
|
<Empty>
|
|
<EmptyHeader>
|
|
<EmptyMedia variant="icon">
|
|
<QrCode />
|
|
</EmptyMedia>
|
|
<EmptyTitle>Enter text to generate a QR code</EmptyTitle>
|
|
<EmptyDescription>Type text or a URL in the input field above</EmptyDescription>
|
|
</EmptyHeader>
|
|
</Empty>
|
|
)}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|