Import source: src/components/CenterPanel/DownloadButton
Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
This commit is contained in:
@@ -0,0 +1,109 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { useQR } from '../../context/QRContext';
|
||||||
|
import { useTheme } from '../../context/ThemeContext';
|
||||||
|
import { useToast } from '../../context/ToastContext';
|
||||||
|
import { useHistory } from '../../context/HistoryContext';
|
||||||
|
import { encodeQR } from '../../utils/encodeQR';
|
||||||
|
import { downloadQR, copyQR } from './QRPreview';
|
||||||
|
|
||||||
|
export function DownloadButton() {
|
||||||
|
const { state } = useQR();
|
||||||
|
const { isDark } = useTheme();
|
||||||
|
const { show } = useToast();
|
||||||
|
const { push } = useHistory();
|
||||||
|
const [showFmts, setShowFmts] = useState(false);
|
||||||
|
const isActive = !!encodeQR(state);
|
||||||
|
const canShare = typeof navigator !== 'undefined' && 'share' in navigator;
|
||||||
|
|
||||||
|
const handleDownload = (fmt: 'png' | 'svg') => {
|
||||||
|
if (!isActive) return;
|
||||||
|
downloadQR(state, isDark, fmt);
|
||||||
|
show(`Downloaded as ${fmt.toUpperCase()}`, 'success');
|
||||||
|
push(state, encodeQR(state) ?? '');
|
||||||
|
setShowFmts(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCopy = async () => {
|
||||||
|
if (!isActive) return;
|
||||||
|
try {
|
||||||
|
await copyQR(state, isDark);
|
||||||
|
show('Copied to clipboard', 'success');
|
||||||
|
} catch {
|
||||||
|
show('Copy not supported in this browser', 'error');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleShare = async () => {
|
||||||
|
if (!isActive || !canShare) return;
|
||||||
|
try {
|
||||||
|
await navigator.share({ title: 'CQRC Platform QR Code', text: encodeQR(state) ?? '', url: 'https://qrcode.asabbagh.com' });
|
||||||
|
push(state, encodeQR(state) ?? '');
|
||||||
|
} catch { /* user cancelled */ }
|
||||||
|
};
|
||||||
|
|
||||||
|
const btnStyle = (active: boolean) => active
|
||||||
|
? { background: 'var(--bg-elevated)', border: '1px solid var(--border-sub)', color: 'var(--text-secondary)', cursor: 'pointer' }
|
||||||
|
: { background: 'var(--bg-card)', border: '1px solid var(--border)', color: 'var(--text-dim)', cursor: 'not-allowed' };
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex gap-2 relative">
|
||||||
|
{/* Main download */}
|
||||||
|
<button onClick={() => isActive && handleDownload('png')} disabled={!isActive}
|
||||||
|
className="flex-1 h-12 rounded-[11px] flex items-center justify-center text-[15px] font-medium transition-all duration-200"
|
||||||
|
style={isActive ? { background: '#7C6FE0', color: 'white', boxShadow: '0 4px 16px rgba(124,111,224,0.35)', cursor: 'pointer' }
|
||||||
|
: { background: 'var(--bg-elevated)', color: 'var(--text-placeholder)', cursor: 'not-allowed' }}>
|
||||||
|
Download PNG
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Format picker */}
|
||||||
|
<button onClick={() => isActive && setShowFmts(v => !v)} disabled={!isActive} title="More formats"
|
||||||
|
className="w-12 h-12 rounded-[11px] flex items-center justify-center transition-all duration-200"
|
||||||
|
style={btnStyle(isActive)}>
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
|
||||||
|
<path d="M8 3v10M3 8l5 5 5-5" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Copy */}
|
||||||
|
<button onClick={handleCopy} disabled={!isActive} title="Copy SVG"
|
||||||
|
className="w-12 h-12 rounded-[11px] flex items-center justify-center transition-all duration-200"
|
||||||
|
style={btnStyle(isActive)}>
|
||||||
|
<svg width="17" height="17" viewBox="0 0 17 17" fill="none">
|
||||||
|
<rect x="5.5" y="5.5" width="9" height="9" rx="2" stroke="currentColor" strokeWidth="1.5"/>
|
||||||
|
<path d="M11.5 5.5V3.5C11.5 2.395 10.605 1.5 9.5 1.5H3.5C2.395 1.5 1.5 2.395 1.5 3.5V9.5C1.5 10.605 2.395 11.5 3.5 11.5H5.5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Share (mobile) */}
|
||||||
|
{canShare && (
|
||||||
|
<button onClick={handleShare} disabled={!isActive} title="Share"
|
||||||
|
className="w-12 h-12 rounded-[11px] flex items-center justify-center transition-all duration-200"
|
||||||
|
style={btnStyle(isActive)}>
|
||||||
|
<svg width="17" height="17" viewBox="0 0 17 17" fill="none">
|
||||||
|
<path d="M9 2l4 4-4 4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
|
||||||
|
<path d="M13 6H6a3 3 0 0 0-3 3v4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Format dropdown */}
|
||||||
|
{showFmts && (
|
||||||
|
<div className="absolute top-14 left-0 z-20 rounded-[12px] overflow-hidden shadow-xl"
|
||||||
|
style={{ background: 'var(--bg-card)', border: '1px solid var(--border)', minWidth: '160px' }}>
|
||||||
|
{(['png', 'svg'] as const).map(fmt => (
|
||||||
|
<button key={fmt} onClick={() => handleDownload(fmt)}
|
||||||
|
className="w-full px-4 py-3 text-left text-[14px] font-medium flex items-center gap-2.5 transition-colors"
|
||||||
|
style={{ color: 'var(--text-primary)' }}
|
||||||
|
onMouseEnter={e => (e.currentTarget.style.background = 'var(--hover-bg)')}
|
||||||
|
onMouseLeave={e => (e.currentTarget.style.background = 'transparent')}>
|
||||||
|
<span className="text-[11px] font-bold px-1.5 py-0.5 rounded" style={{ background: 'rgba(124,111,224,0.15)', color: '#7C6FE0' }}>
|
||||||
|
{fmt.toUpperCase()}
|
||||||
|
</span>
|
||||||
|
{fmt === 'png' ? 'Raster image' : 'Vector (scalable)'}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user