UNPKG

qrcode-studio

Version:

A comprehensive Capacitor plugin for QR code and barcode scanning/generation. Supports 22+ QR data types and 14+ barcode formats (EAN, UPC, Code 128, etc.), with customizable designs, analytics, and React components. Works seamlessly across web, iOS, and

171 lines 18.7 kB
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime"; import React, { useState, useCallback, useRef } from 'react'; import { QRScanner } from '../QRScanner'; import { QRGenerator } from '../QRGenerator'; import { BarcodeScanner } from '../BarcodeScanner'; import { QRCodeStudio } from '../../index'; import { QRType } from '../../definitions'; import { qrFormFields, qrTypeInfo } from '../../utils/qr-forms'; import { ArrayFieldEditor, renderImageItem, renderLinkItem, renderMenuCategory } from '../shared/ArrayFieldEditor'; // import './QRStudio.css'; // CSS should be imported by the consuming app export const QRStudio = ({ config = {}, theme = {}, features = { scanner: true, generator: true, landingPages: true, analytics: true, export: true, sharing: true, history: true, favorites: true, templates: true, }, onSave, onScan, analytics: _analytics, className = '', style, }) => { const [activeTab, setActiveTab] = useState('generate'); const [mode, setMode] = useState('qr'); const [selectedType, setSelectedType] = useState(config.defaultType || QRType.WEBSITE); const [selectedBarcodeFormat, setSelectedBarcodeFormat] = useState('CODE_128'); const [formData, setFormData] = useState({}); const [, setGeneratedQR] = useState(null); const [generatedBarcode, setGeneratedBarcode] = useState(null); const [history, setHistory] = useState([]); const [showCustomization, setShowCustomization] = useState(false); const [design, setDesign] = useState(config.defaultDesign || {}); const formRef = useRef(null); // Load history on mount React.useEffect(() => { if (features.history) { loadHistory(); } }, [features.history]); // Apply theme React.useEffect(() => { if (theme.variables) { Object.entries(theme.variables).forEach(([key, value]) => { document.documentElement.style.setProperty(`--qr-${key}`, value); }); } if (theme.primary) { document.documentElement.style.setProperty('--qr-primary', theme.primary); } if (theme.secondary) { document.documentElement.style.setProperty('--qr-secondary', theme.secondary); } if (theme.mode) { document.documentElement.setAttribute('data-theme', theme.mode); } }, [theme]); const loadHistory = async () => { try { const result = await QRCodeStudio.getHistory({ limit: 20 }); setHistory(result.items); } catch (error) { console.error('Failed to load history:', error); } }; const handleScanResult = useCallback((result) => { onScan === null || onScan === void 0 ? void 0 : onScan(result); if (features.history) { loadHistory(); } }, [onScan, features.history]); const handleGenerate = useCallback((result) => { setGeneratedQR(result); onSave === null || onSave === void 0 ? void 0 : onSave(result); if (features.history) { loadHistory(); } }, [onSave, features.history]); const handleGenerateBarcode = useCallback(async () => { try { const result = await QRCodeStudio.generateBarcode({ format: selectedBarcodeFormat, data: formData.barcodeData || '', width: 300, height: 100, displayText: true, }); setGeneratedBarcode(result); } catch (error) { console.error('Failed to generate barcode:', error); } }, [selectedBarcodeFormat, formData.barcodeData]); const handleTypeChange = (type) => { setSelectedType(type); setFormData({}); setGeneratedQR(null); }; const handleFormChange = (field, value) => { setFormData(prev => (Object.assign(Object.assign({}, prev), { [field]: value }))); }; const handleDesignChange = (field, value) => { setDesign(prev => (Object.assign(Object.assign({}, prev), { [field]: value }))); }; const getQRData = () => { // Transform form data based on type // Clean up empty fields and ensure proper structure const cleanData = Object.assign({}, formData); // Remove empty string values Object.keys(cleanData).forEach(key => { if (cleanData[key] === '') { delete cleanData[key]; } }); // Ensure arrays are properly initialized if (selectedType === QRType.IMAGES && !cleanData.images) { cleanData.images = []; } if (selectedType === QRType.LINKS_LIST && !cleanData.links) { cleanData.links = []; } if (selectedType === QRType.MENU && !cleanData.categories) { cleanData.categories = []; } return cleanData; }; const renderTypeSelector = () => { const allowedTypes = config.allowedTypes || Object.values(QRType); return (_jsx("div", { className: "qr-type-grid", children: allowedTypes.map(type => { const info = qrTypeInfo[type]; return (_jsxs("button", { className: `qr-type-button ${selectedType === type ? 'active' : ''}`, onClick: () => handleTypeChange(type), children: [_jsx("span", { className: "type-icon", children: info.icon }), _jsx("span", { className: "type-name", children: type.replace(/_/g, ' ') }), _jsx("span", { className: "type-description", children: info.description })] }, type)); }) })); }; const renderForm = () => { const fields = qrFormFields[selectedType]; return (_jsx("form", { ref: formRef, className: "qr-form", onSubmit: (e) => e.preventDefault(), children: fields.map(field => { var _a; return (_jsxs("div", { className: "form-field", children: [_jsxs("label", { htmlFor: field.name, children: [field.label, field.required && _jsx("span", { className: "required", children: "*" })] }), field.type === 'textarea' ? (_jsx("textarea", { id: field.name, name: field.name, placeholder: field.placeholder, required: field.required, maxLength: field.maxLength, value: formData[field.name] || '', onChange: (e) => handleFormChange(field.name, e.target.value) })) : field.type === 'select' ? (_jsxs("select", { id: field.name, name: field.name, required: field.required, value: formData[field.name] || '', onChange: (e) => handleFormChange(field.name, e.target.value), children: [_jsxs("option", { value: "", children: ["Select ", field.label] }), (_a = field.options) === null || _a === void 0 ? void 0 : _a.map(opt => (_jsx("option", { value: opt.value, children: opt.label }, opt.value)))] })) : field.type === 'checkbox' ? (_jsx("input", { type: "checkbox", id: field.name, name: field.name, checked: formData[field.name] || false, onChange: (e) => handleFormChange(field.name, e.target.checked) })) : field.type === 'array' ? (_jsxs("div", { className: "array-field", children: [field.name === 'images' && (_jsx(ArrayFieldEditor, { fieldName: field.name, label: field.label, value: formData[field.name] || [], onChange: (value) => handleFormChange(field.name, value), itemTemplate: { url: '', caption: '' }, renderItem: renderImageItem })), field.name === 'links' && (_jsx(ArrayFieldEditor, { fieldName: field.name, label: field.label, value: formData[field.name] || [], onChange: (value) => handleFormChange(field.name, value), itemTemplate: { title: '', url: '', icon: '' }, renderItem: renderLinkItem })), field.name === 'categories' && (_jsx(ArrayFieldEditor, { fieldName: field.name, label: field.label, value: formData[field.name] || [], onChange: (value) => handleFormChange(field.name, value), itemTemplate: { name: '', items: [] }, renderItem: renderMenuCategory }))] })) : (_jsx("input", { type: field.type || 'text', id: field.name, name: field.name, placeholder: field.placeholder, required: field.required, pattern: field.pattern, maxLength: field.maxLength, min: field.min, max: field.max, value: formData[field.name] || '', onChange: (e) => handleFormChange(field.name, e.target.value) })), field.helper && (_jsx("span", { className: "field-helper", children: field.helper }))] }, field.name)); }) })); }; const renderCustomization = () => { var _a, _b, _c; return (_jsxs("div", { className: "qr-customization", children: [_jsx("h3", { children: "Customize Design" }), _jsxs("div", { className: "design-section", children: [_jsx("h4", { children: "Colors" }), _jsxs("div", { className: "color-inputs", children: [_jsxs("div", { className: "color-field", children: [_jsx("label", { children: "Foreground" }), _jsx("input", { type: "color", value: ((_a = design.colors) === null || _a === void 0 ? void 0 : _a.dark) || '#000000', onChange: (e) => handleDesignChange('colors', Object.assign(Object.assign({}, design.colors), { dark: e.target.value })) })] }), _jsxs("div", { className: "color-field", children: [_jsx("label", { children: "Background" }), _jsx("input", { type: "color", value: ((_b = design.colors) === null || _b === void 0 ? void 0 : _b.light) || '#FFFFFF', onChange: (e) => handleDesignChange('colors', Object.assign(Object.assign({}, design.colors), { light: e.target.value })) })] })] })] }), _jsxs("div", { className: "design-section", children: [_jsx("h4", { children: "Logo" }), _jsx("input", { type: "url", placeholder: "Logo URL", value: ((_c = design.logo) === null || _c === void 0 ? void 0 : _c.src) || '', onChange: (e) => handleDesignChange('logo', e.target.value ? Object.assign(Object.assign({}, design.logo), { src: e.target.value }) : undefined) })] }), _jsxs("div", { className: "design-section", children: [_jsx("h4", { children: "Style" }), _jsxs("select", { value: design.dotsStyle || 'square', onChange: (e) => handleDesignChange('dotsStyle', e.target.value), children: [_jsx("option", { value: "square", children: "Square" }), _jsx("option", { value: "rounded", children: "Rounded" }), _jsx("option", { value: "dots", children: "Dots" }), _jsx("option", { value: "classy", children: "Classy" }), _jsx("option", { value: "classy-rounded", children: "Classy Rounded" }), _jsx("option", { value: "extra-rounded", children: "Extra Rounded" })] })] })] })); }; const renderHistory = () => (_jsxs("div", { className: "qr-history", children: [_jsx("h3", { children: "Recent QR Codes" }), history.length === 0 ? (_jsx("p", { className: "empty-state", children: "No QR codes generated yet" })) : (_jsx("div", { className: "history-list", children: history.map(item => { var _a; return (_jsxs("div", { className: "history-item", children: [_jsx("div", { className: "history-preview", children: ((_a = item.qrCode) === null || _a === void 0 ? void 0 : _a.dataUrl) && (_jsx("img", { src: item.qrCode.dataUrl, alt: "QR Code" })) }), _jsxs("div", { className: "history-info", children: [_jsx("span", { className: "history-type", children: item.type }), _jsx("span", { className: "history-date", children: new Date(item.createdAt).toLocaleDateString() }), item.scanCount !== undefined && (_jsxs("span", { className: "history-scans", children: [item.scanCount, " scans"] }))] }), _jsx("div", { className: "history-actions", children: _jsx("button", { onClick: () => { setSelectedType(item.type); setFormData(item.data); setActiveTab('generate'); }, children: "Use Again" }) })] }, item.id)); }) }))] })); return (_jsxs("div", { className: `qr-studio-container ${className}`, style: style, children: [_jsxs("div", { className: "qr-studio-header", children: [_jsx("h1", { children: "QR Code Studio" }), _jsxs("div", { className: "studio-tabs", children: [features.scanner && (_jsx("button", { className: `tab ${activeTab === 'scan' ? 'active' : ''}`, onClick: () => setActiveTab('scan'), children: "Scan" })), features.generator && (_jsx("button", { className: `tab ${activeTab === 'generate' ? 'active' : ''}`, onClick: () => setActiveTab('generate'), children: "Generate" })), features.history && (_jsx("button", { className: `tab ${activeTab === 'history' ? 'active' : ''}`, onClick: () => setActiveTab('history'), children: "History" }))] })] }), _jsxs("div", { className: "qr-studio-content", children: [activeTab === 'scan' && features.scanner && (_jsxs("div", { className: "scan-tab", children: [_jsxs("div", { className: "scan-mode-selector", children: [_jsx("button", { className: `mode-btn ${mode === 'qr' ? 'active' : ''}`, onClick: () => setMode('qr'), children: "QR Code" }), _jsx("button", { className: `mode-btn ${mode === 'barcode' ? 'active' : ''}`, onClick: () => setMode('barcode'), children: "Barcode" })] }), mode === 'qr' ? (_jsx(QRScanner, { onScan: handleScanResult })) : (_jsx(BarcodeScanner, { onScan: handleScanResult, showFormatSelector: true, showProductInfo: true }))] })), activeTab === 'generate' && features.generator && (_jsxs("div", { className: "generate-tab", children: [_jsxs("div", { className: "generate-mode-selector", children: [_jsx("button", { className: `mode-btn ${mode === 'qr' ? 'active' : ''}`, onClick: () => setMode('qr'), children: "QR Code" }), _jsx("button", { className: `mode-btn ${mode === 'barcode' ? 'active' : ''}`, onClick: () => setMode('barcode'), children: "Barcode" })] }), mode === 'qr' ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "generate-sidebar", children: [_jsx("h2", { children: "Select QR Code Type" }), renderTypeSelector()] }), _jsxs("div", { className: "generate-main", children: [_jsxs("div", { className: "form-section", children: [_jsx("h2", { children: "Enter Information" }), renderForm(), _jsxs("button", { className: "customize-toggle", onClick: () => setShowCustomization(!showCustomization), children: [showCustomization ? 'Hide' : 'Show', " Customization"] }), showCustomization && renderCustomization()] }), _jsxs("div", { className: "preview-section", children: [_jsx("h2", { children: "Preview" }), Object.keys(formData).length > 0 && (_jsx(QRGenerator, { type: selectedType, data: getQRData(), design: design, size: 300, onGenerate: (result) => { handleGenerate(result); setGeneratedQR(result); }, showDownload: features.export, showShare: features.sharing }))] })] })] })) : (_jsxs("div", { className: "barcode-generate-main", children: [_jsxs("div", { className: "barcode-form-section", children: [_jsx("h2", { children: "Barcode Configuration" }), _jsxs("div", { className: "barcode-format-selector", children: [_jsx("label", { children: "Barcode Format" }), _jsxs("select", { value: selectedBarcodeFormat, onChange: (e) => setSelectedBarcodeFormat(e.target.value), children: [_jsxs("optgroup", { label: "Product Codes", children: [_jsx("option", { value: "EAN_13", children: "EAN-13" }), _jsx("option", { value: "EAN_8", children: "EAN-8" }), _jsx("option", { value: "UPC_A", children: "UPC-A" }), _jsx("option", { value: "UPC_E", children: "UPC-E" })] }), _jsxs("optgroup", { label: "Industrial Codes", children: [_jsx("option", { value: "CODE_128", children: "Code 128" }), _jsx("option", { value: "CODE_39", children: "Code 39" }), _jsx("option", { value: "CODE_93", children: "Code 93" }), _jsx("option", { value: "CODABAR", children: "Codabar" }), _jsx("option", { value: "ITF", children: "ITF" }), _jsx("option", { value: "ITF_14", children: "ITF-14" })] }), _jsxs("optgroup", { label: "Specialty", children: [_jsx("option", { value: "MSI", children: "MSI" }), _jsx("option", { value: "PHARMACODE", children: "Pharmacode" })] })] })] }), _jsxs("div", { className: "barcode-data-input", children: [_jsx("label", { children: "Barcode Data" }), _jsx("input", { type: "text", value: formData.barcodeData || '', onChange: (e) => setFormData(Object.assign(Object.assign({}, formData), { barcodeData: e.target.value })), placeholder: "Enter barcode data" })] }), _jsx("button", { className: "generate-barcode-btn", onClick: handleGenerateBarcode, disabled: !formData.barcodeData, children: "Generate Barcode" })] }), _jsxs("div", { className: "barcode-preview-section", children: [_jsx("h2", { children: "Preview" }), generatedBarcode && (_jsxs("div", { className: "barcode-result", children: [generatedBarcode.svg && (_jsx("div", { className: "barcode-svg", dangerouslySetInnerHTML: { __html: generatedBarcode.svg } })), generatedBarcode.dataUrl && !generatedBarcode.svg && (_jsx("img", { src: generatedBarcode.dataUrl, alt: "Generated Barcode" })), _jsxs("div", { className: "barcode-info", children: [_jsxs("p", { children: ["Format: ", generatedBarcode.format] }), _jsxs("p", { children: ["Data: ", generatedBarcode.data] })] }), features.export && (_jsxs("div", { className: "barcode-actions", children: [_jsx("button", { onClick: () => { const link = document.createElement('a'); link.download = `barcode-${generatedBarcode.format}.png`; link.href = generatedBarcode.dataUrl || ''; link.click(); }, children: "Download PNG" }), generatedBarcode.svg && (_jsx("button", { onClick: () => { const blob = new Blob([generatedBarcode.svg], { type: 'image/svg+xml' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.download = `barcode-${generatedBarcode.format}.svg`; link.href = url; link.click(); URL.revokeObjectURL(url); }, children: "Download SVG" }))] }))] }))] })] }))] })), activeTab === 'history' && features.history && renderHistory()] })] })); }; export default QRStudio; //# sourceMappingURL=QRStudio.js.map