react-id-card-generator 1.0.7 → 1.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -54,6 +54,17 @@ const IDCardPreview = ({ template, data, side, scale = 1, showGrid = false, onFi
54
54
  const posStyle = styleUtils.positionToStyle(field.position);
55
55
  const fieldStyle = styleUtils.fieldStyleToCSS(field.style);
56
56
  const isSelected = field.id === selectedFieldId;
57
+ // Determine vertical alignment and label position for text fields
58
+ let verticalJustify = 'flex-start';
59
+ let flexDirection = 'column';
60
+ if (field.type === 'text') {
61
+ if (field.style?.verticalAlign === 'middle')
62
+ verticalJustify = 'center';
63
+ else if (field.style?.verticalAlign === 'bottom')
64
+ verticalJustify = 'flex-end';
65
+ if (field.style?.labelPosition === 'left')
66
+ flexDirection = 'row';
67
+ }
57
68
  const combinedStyle = {
58
69
  ...posStyle,
59
70
  ...fieldStyle,
@@ -63,6 +74,9 @@ const IDCardPreview = ({ template, data, side, scale = 1, showGrid = false, onFi
63
74
  cursor: editable ? 'move' : 'default',
64
75
  outline: isSelected ? '2px solid #2196F3' : 'none',
65
76
  outlineOffset: '1px',
77
+ display: field.type === 'text' ? 'flex' : undefined,
78
+ flexDirection: field.type === 'text' ? flexDirection : undefined,
79
+ justifyContent: field.type === 'text' ? verticalJustify : undefined,
66
80
  };
67
81
  const handleClick = (e) => {
68
82
  e.stopPropagation();
@@ -71,8 +85,21 @@ const IDCardPreview = ({ template, data, side, scale = 1, showGrid = false, onFi
71
85
  const value = data[field.fieldKey];
72
86
  const content = (() => {
73
87
  switch (field.type) {
74
- case 'text':
75
- return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [field.label && (jsxRuntime.jsx("span", { style: { fontSize: '0.75em', display: 'block', opacity: 0.7 }, children: field.label })), jsxRuntime.jsx("span", { children: String(value || field.placeholder || field.fieldKey) })] }));
88
+ case 'text': {
89
+ // Determine label vertical alignment
90
+ let labelAlignSelf = 'flex-start';
91
+ if (field.style?.labelVerticalAlign === 'middle')
92
+ labelAlignSelf = 'center';
93
+ else if (field.style?.labelVerticalAlign === 'bottom')
94
+ labelAlignSelf = 'flex-end';
95
+ const labelFontSize = field.style?.labelFontSize || '0.75em';
96
+ const labelFontWeight = field.style?.labelFontWeight || 'normal';
97
+ const labelColor = field.style?.labelColor || '#666666';
98
+ const labelStyle = field.style?.labelPosition === 'left'
99
+ ? { fontSize: labelFontSize, fontWeight: labelFontWeight, color: labelColor, opacity: 0.7, marginRight: 8, alignSelf: labelAlignSelf, whiteSpace: 'nowrap' }
100
+ : { fontSize: labelFontSize, fontWeight: labelFontWeight, color: labelColor, display: 'block', opacity: 0.7, alignSelf: labelAlignSelf };
101
+ return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [field.label && (jsxRuntime.jsx("span", { style: labelStyle, children: field.label })), jsxRuntime.jsx("span", { children: String(value || field.placeholder || field.fieldKey) })] }));
102
+ }
76
103
  case 'label':
77
104
  return jsxRuntime.jsx("span", { children: field.staticText || field.label || 'Label' });
78
105
  case 'image':
@@ -93,10 +120,15 @@ const IDCardPreview = ({ template, data, side, scale = 1, showGrid = false, onFi
93
120
  flexDirection: 'column',
94
121
  }, children: [jsxRuntime.jsx("span", { children: "\uD83D\uDCF7" }), jsxRuntime.jsx("span", { children: field.label || 'Photo' })] }));
95
122
  case 'qrcode':
123
+ // Use the field's rendered width/height (in px) for QR code size
124
+ const qrWidth = Math.max(32, Math.round(field.position.width));
125
+ const qrHeight = Math.max(32, Math.round(field.position.height));
126
+ // Always generate QR code at the field's current size for smooth scaling
127
+ const qrSize = Math.max(qrWidth, qrHeight);
96
128
  const qrDataUrl = field.qrFields
97
- ? qrUtils.generateQrCodeFromFields(data, field.qrFields, 200)
129
+ ? qrUtils.generateQrCodeFromFields(data, field.qrFields, qrSize)
98
130
  : '';
99
- return qrDataUrl ? (jsxRuntime.jsx("img", { src: qrDataUrl, alt: "QR Code", style: { width: '100%', height: '100%', objectFit: 'contain' } })) : (jsxRuntime.jsx("div", { style: {
131
+ return qrDataUrl ? (jsxRuntime.jsx("img", { src: qrDataUrl, alt: "QR Code", style: { width: '100%', height: '100%', objectFit: 'contain', display: 'block', pointerEvents: 'none' } })) : (jsxRuntime.jsx("div", { style: {
100
132
  width: '100%',
101
133
  height: '100%',
102
134
  backgroundColor: '#f0f0f0',
@@ -1 +1 @@
1
- {"version":3,"file":"IDCardPreview.js","sources":["../../../src/components/IDCardPreview.tsx"],"sourcesContent":["import React, { useMemo } from 'react';\r\nimport type { IDCardPreviewProps, FieldMapping } from '../types';\r\nimport { fieldStyleToCSS, positionToStyle } from '../utils/styleUtils';\r\nimport { generateQrCodeFromFields } from '../core/qrUtils';\r\n\r\n/**\r\n * IDCardPreview - Interactive card preview component\r\n * \r\n * Renders an ID card with:\r\n * - Field selection and highlighting\r\n * - Resize handles for selected fields\r\n * - Grid overlay for alignment\r\n * - Dynamic field rendering based on type\r\n * \r\n * Used inside the designer for visual editing\r\n */\r\nexport const IDCardPreview: React.FC<IDCardPreviewProps> = ({\r\n template,\r\n data,\r\n side,\r\n scale = 1,\r\n showGrid = false,\r\n onFieldSelect,\r\n selectedFieldId,\r\n editable = true,\r\n onFieldUpdate,\r\n}: IDCardPreviewProps) => {\r\n // Get only fields that belong to the current side\r\n const fieldsToRender = useMemo(() => {\r\n return template.fields.filter((field: FieldMapping) => field.side === side);\r\n }, [template.fields, side]);\r\n\r\n // Get background for current side\r\n const backgroundImage = side === 'front' ? template.backgrounds.front : template.backgrounds.back;\r\n\r\n // Render resize handles for selected field\r\n const renderResizeHandles = (field: FieldMapping) => {\r\n if (field.id !== selectedFieldId || !editable) return null;\r\n\r\n const handles = ['n', 's', 'e', 'w', 'ne', 'nw', 'se', 'sw'];\r\n const handleStyle: React.CSSProperties = {\r\n position: 'absolute',\r\n width: '8px',\r\n height: '8px',\r\n backgroundColor: '#2196F3',\r\n border: '1px solid #fff',\r\n borderRadius: '2px',\r\n zIndex: 1000,\r\n };\r\n\r\n const positions: Record<string, React.CSSProperties> = {\r\n n: { top: '-4px', left: '50%', transform: 'translateX(-50%)', cursor: 'n-resize' },\r\n s: { bottom: '-4px', left: '50%', transform: 'translateX(-50%)', cursor: 's-resize' },\r\n e: { right: '-4px', top: '50%', transform: 'translateY(-50%)', cursor: 'e-resize' },\r\n w: { left: '-4px', top: '50%', transform: 'translateY(-50%)', cursor: 'w-resize' },\r\n ne: { top: '-4px', right: '-4px', cursor: 'ne-resize' },\r\n nw: { top: '-4px', left: '-4px', cursor: 'nw-resize' },\r\n se: { bottom: '-4px', right: '-4px', cursor: 'se-resize' },\r\n sw: { bottom: '-4px', left: '-4px', cursor: 'sw-resize' },\r\n };\r\n\r\n return handles.map((handle) => (\r\n <div\r\n key={handle}\r\n className={`resize-handle resize-handle-${handle}`}\r\n style={{ ...handleStyle, ...positions[handle] }}\r\n data-handle={handle}\r\n />\r\n ));\r\n };\r\n\r\n // Render a single field\r\n const renderField = (field: FieldMapping) => {\r\n const posStyle = positionToStyle(field.position);\r\n const fieldStyle = fieldStyleToCSS(field.style);\r\n const isSelected = field.id === selectedFieldId;\r\n\r\n const combinedStyle: React.CSSProperties = {\r\n ...posStyle,\r\n ...fieldStyle,\r\n zIndex: field.zIndex || 1,\r\n boxSizing: 'border-box',\r\n overflow: 'hidden',\r\n cursor: editable ? 'move' : 'default',\r\n outline: isSelected ? '2px solid #2196F3' : 'none',\r\n outlineOffset: '1px',\r\n };\r\n\r\n const handleClick = (e: React.MouseEvent) => {\r\n e.stopPropagation();\r\n onFieldSelect?.(field);\r\n };\r\n\r\n const value = data[field.fieldKey];\r\n\r\n const content = (() => {\r\n switch (field.type) {\r\n case 'text':\r\n return (\r\n <>\r\n {field.label && (\r\n <span style={{ fontSize: '0.75em', display: 'block', opacity: 0.7 }}>\r\n {field.label}\r\n </span>\r\n )}\r\n <span>{String(value || field.placeholder || field.fieldKey)}</span>\r\n </>\r\n );\r\n\r\n case 'label':\r\n return <span>{field.staticText || field.label || 'Label'}</span>;\r\n\r\n case 'image':\r\n return value ? (\r\n <img\r\n src={String(value)}\r\n alt={field.label || 'Image'}\r\n style={{\r\n width: '100%',\r\n height: '100%',\r\n objectFit: 'cover',\r\n objectPosition: 'center top',\r\n }}\r\n />\r\n ) : (\r\n <div\r\n style={{\r\n width: '100%',\r\n height: '100%',\r\n backgroundColor: '#e0e0e0',\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n fontSize: '10px',\r\n color: '#666',\r\n flexDirection: 'column',\r\n }}\r\n >\r\n <span>📷</span>\r\n <span>{field.label || 'Photo'}</span>\r\n </div>\r\n );\r\n\r\n case 'qrcode':\r\n const qrDataUrl = field.qrFields\r\n ? generateQrCodeFromFields(data, field.qrFields, 200)\r\n : '';\r\n\r\n return qrDataUrl ? (\r\n <img\r\n src={qrDataUrl}\r\n alt=\"QR Code\"\r\n style={{ width: '100%', height: '100%', objectFit: 'contain' }}\r\n />\r\n ) : (\r\n <div\r\n style={{\r\n width: '100%',\r\n height: '100%',\r\n backgroundColor: '#f0f0f0',\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n fontSize: '10px',\r\n color: '#666',\r\n border: '1px dashed #ccc',\r\n }}\r\n >\r\n QR Code\r\n </div>\r\n );\r\n\r\n default:\r\n return null;\r\n }\r\n })();\r\n\r\n return (\r\n <div\r\n key={field.id}\r\n id={`preview-${field.id}`}\r\n style={combinedStyle}\r\n onClick={handleClick}\r\n className={`preview-field preview-field-${field.type} ${isSelected ? 'selected' : ''}`}\r\n >\r\n {content}\r\n {renderResizeHandles(field)}\r\n </div>\r\n );\r\n };\r\n\r\n // Card container style\r\n const cardStyle: React.CSSProperties = {\r\n width: template.cardSize.width,\r\n height: template.cardSize.height,\r\n position: 'relative',\r\n overflow: 'hidden',\r\n backgroundColor: backgroundImage ? 'transparent' : '#f5f5f5',\r\n border: '1px solid #ddd',\r\n transform: `scale(${scale})`,\r\n transformOrigin: 'top left',\r\n };\r\n\r\n // Grid overlay style\r\n const gridStyle: React.CSSProperties = {\r\n position: 'absolute',\r\n top: 0,\r\n left: 0,\r\n width: '100%',\r\n height: '100%',\r\n backgroundImage: showGrid\r\n ? 'linear-gradient(to right, rgba(0,0,0,0.05) 1px, transparent 1px), linear-gradient(to bottom, rgba(0,0,0,0.05) 1px, transparent 1px)'\r\n : 'none',\r\n backgroundSize: '10px 10px',\r\n pointerEvents: 'none',\r\n zIndex: 999,\r\n };\r\n\r\n const handleContainerClick = () => {\r\n onFieldSelect?.(null);\r\n };\r\n\r\n return (\r\n <div\r\n className=\"idcard-preview-container\"\r\n style={cardStyle}\r\n onClick={handleContainerClick}\r\n >\r\n {/* Background */}\r\n {backgroundImage && (\r\n <img\r\n src={backgroundImage}\r\n alt={`${side} background`}\r\n style={{\r\n position: 'absolute',\r\n top: 0,\r\n left: 0,\r\n width: '100%',\r\n height: '100%',\r\n objectFit: 'cover',\r\n zIndex: 0,\r\n pointerEvents: 'none',\r\n }}\r\n />\r\n )}\r\n\r\n {/* Fields */}\r\n {fieldsToRender.map(renderField)}\r\n\r\n {/* Grid overlay */}\r\n {showGrid && <div style={gridStyle} />}\r\n\r\n {/* Empty state */}\r\n {!backgroundImage && fieldsToRender.length === 0 && (\r\n <div\r\n style={{\r\n position: 'absolute',\r\n top: '50%',\r\n left: '50%',\r\n transform: 'translate(-50%, -50%)',\r\n textAlign: 'center',\r\n color: '#999',\r\n fontSize: '14px',\r\n }}\r\n >\r\n <p>Upload a background image</p>\r\n <p>and add fields to get started</p>\r\n </div>\r\n )}\r\n </div>\r\n );\r\n};\r\n\r\n"],"names":["useMemo","_jsx","positionToStyle","fieldStyleToCSS","_jsxs","generateQrCodeFromFields"],"mappings":"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"}
1
+ {"version":3,"file":"IDCardPreview.js","sources":["../../../src/components/IDCardPreview.tsx"],"sourcesContent":["import React, { useMemo } from 'react';\r\nimport type { IDCardPreviewProps, FieldMapping } from '../types';\r\nimport { fieldStyleToCSS, positionToStyle } from '../utils/styleUtils';\r\nimport { generateQrCodeFromFields } from '../core/qrUtils';\r\n\r\n/**\r\n * IDCardPreview - Interactive card preview component\r\n * \r\n * Renders an ID card with:\r\n * - Field selection and highlighting\r\n * - Resize handles for selected fields\r\n * - Grid overlay for alignment\r\n * - Dynamic field rendering based on type\r\n * \r\n * Used inside the designer for visual editing\r\n */\r\nexport const IDCardPreview: React.FC<IDCardPreviewProps> = ({\r\n template,\r\n data,\r\n side,\r\n scale = 1,\r\n showGrid = false,\r\n onFieldSelect,\r\n selectedFieldId,\r\n editable = true,\r\n onFieldUpdate,\r\n}: IDCardPreviewProps) => {\r\n // Get only fields that belong to the current side\r\n const fieldsToRender = useMemo(() => {\r\n return template.fields.filter((field: FieldMapping) => field.side === side);\r\n }, [template.fields, side]);\r\n\r\n // Get background for current side\r\n const backgroundImage = side === 'front' ? template.backgrounds.front : template.backgrounds.back;\r\n\r\n // Render resize handles for selected field\r\n const renderResizeHandles = (field: FieldMapping) => {\r\n if (field.id !== selectedFieldId || !editable) return null;\r\n\r\n const handles = ['n', 's', 'e', 'w', 'ne', 'nw', 'se', 'sw'];\r\n const handleStyle: React.CSSProperties = {\r\n position: 'absolute',\r\n width: '8px',\r\n height: '8px',\r\n backgroundColor: '#2196F3',\r\n border: '1px solid #fff',\r\n borderRadius: '2px',\r\n zIndex: 1000,\r\n };\r\n\r\n const positions: Record<string, React.CSSProperties> = {\r\n n: { top: '-4px', left: '50%', transform: 'translateX(-50%)', cursor: 'n-resize' },\r\n s: { bottom: '-4px', left: '50%', transform: 'translateX(-50%)', cursor: 's-resize' },\r\n e: { right: '-4px', top: '50%', transform: 'translateY(-50%)', cursor: 'e-resize' },\r\n w: { left: '-4px', top: '50%', transform: 'translateY(-50%)', cursor: 'w-resize' },\r\n ne: { top: '-4px', right: '-4px', cursor: 'ne-resize' },\r\n nw: { top: '-4px', left: '-4px', cursor: 'nw-resize' },\r\n se: { bottom: '-4px', right: '-4px', cursor: 'se-resize' },\r\n sw: { bottom: '-4px', left: '-4px', cursor: 'sw-resize' },\r\n };\r\n\r\n return handles.map((handle) => (\r\n <div\r\n key={handle}\r\n className={`resize-handle resize-handle-${handle}`}\r\n style={{ ...handleStyle, ...positions[handle] }}\r\n data-handle={handle}\r\n />\r\n ));\r\n };\r\n\r\n // Render a single field\r\n const renderField = (field: FieldMapping) => {\r\n const posStyle = positionToStyle(field.position);\r\n const fieldStyle = fieldStyleToCSS(field.style);\r\n const isSelected = field.id === selectedFieldId;\r\n\r\n // Determine vertical alignment and label position for text fields\r\n let verticalJustify: 'flex-start' | 'center' | 'flex-end' = 'flex-start';\r\n let flexDirection: 'column' | 'row' = 'column';\r\n if (field.type === 'text') {\r\n if (field.style?.verticalAlign === 'middle') verticalJustify = 'center';\r\n else if (field.style?.verticalAlign === 'bottom') verticalJustify = 'flex-end';\r\n if (field.style?.labelPosition === 'left') flexDirection = 'row';\r\n }\r\n\r\n const combinedStyle: React.CSSProperties = {\r\n ...posStyle,\r\n ...fieldStyle,\r\n zIndex: field.zIndex || 1,\r\n boxSizing: 'border-box',\r\n overflow: 'hidden',\r\n cursor: editable ? 'move' : 'default',\r\n outline: isSelected ? '2px solid #2196F3' : 'none',\r\n outlineOffset: '1px',\r\n display: field.type === 'text' ? 'flex' : undefined,\r\n flexDirection: field.type === 'text' ? flexDirection : undefined,\r\n justifyContent: field.type === 'text' ? verticalJustify : undefined,\r\n };\r\n\r\n const handleClick = (e: React.MouseEvent) => {\r\n e.stopPropagation();\r\n onFieldSelect?.(field);\r\n };\r\n\r\n const value = data[field.fieldKey];\r\n\r\n const content = (() => {\r\n switch (field.type) {\r\n case 'text': {\r\n // Determine label vertical alignment\r\n let labelAlignSelf: React.CSSProperties['alignSelf'] = 'flex-start';\r\n if (field.style?.labelVerticalAlign === 'middle') labelAlignSelf = 'center';\r\n else if (field.style?.labelVerticalAlign === 'bottom') labelAlignSelf = 'flex-end';\r\n const labelFontSize = field.style?.labelFontSize || '0.75em';\r\n const labelFontWeight = field.style?.labelFontWeight || 'normal';\r\n const labelColor = field.style?.labelColor || '#666666';\r\n const labelStyle: React.CSSProperties = field.style?.labelPosition === 'left'\r\n ? { fontSize: labelFontSize, fontWeight: labelFontWeight, color: labelColor, opacity: 0.7, marginRight: 8, alignSelf: labelAlignSelf, whiteSpace: 'nowrap' }\r\n : { fontSize: labelFontSize, fontWeight: labelFontWeight, color: labelColor, display: 'block', opacity: 0.7, alignSelf: labelAlignSelf };\r\n return (\r\n <>\r\n {field.label && (\r\n <span style={labelStyle}>\r\n {field.label}\r\n </span>\r\n )}\r\n <span>{String(value || field.placeholder || field.fieldKey)}</span>\r\n </>\r\n );\r\n }\r\n\r\n case 'label':\r\n return <span>{field.staticText || field.label || 'Label'}</span>;\r\n\r\n case 'image':\r\n return value ? (\r\n <img\r\n src={String(value)}\r\n alt={field.label || 'Image'}\r\n style={{\r\n width: '100%',\r\n height: '100%',\r\n objectFit: 'cover',\r\n objectPosition: 'center top',\r\n }}\r\n />\r\n ) : (\r\n <div\r\n style={{\r\n width: '100%',\r\n height: '100%',\r\n backgroundColor: '#e0e0e0',\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n fontSize: '10px',\r\n color: '#666',\r\n flexDirection: 'column',\r\n }}\r\n >\r\n <span>📷</span>\r\n <span>{field.label || 'Photo'}</span>\r\n </div>\r\n );\r\n\r\n case 'qrcode':\r\n // Use the field's rendered width/height (in px) for QR code size\r\n const qrWidth = Math.max(32, Math.round(field.position.width));\r\n const qrHeight = Math.max(32, Math.round(field.position.height));\r\n // Always generate QR code at the field's current size for smooth scaling\r\n const qrSize = Math.max(qrWidth, qrHeight);\r\n const qrDataUrl = field.qrFields\r\n ? generateQrCodeFromFields(data, field.qrFields, qrSize)\r\n : '';\r\n\r\n return qrDataUrl ? (\r\n <img\r\n src={qrDataUrl}\r\n alt=\"QR Code\"\r\n style={{ width: '100%', height: '100%', objectFit: 'contain', display: 'block', pointerEvents: 'none' }}\r\n />\r\n ) : (\r\n <div\r\n style={{\r\n width: '100%',\r\n height: '100%',\r\n backgroundColor: '#f0f0f0',\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n fontSize: '10px',\r\n color: '#666',\r\n border: '1px dashed #ccc',\r\n }}\r\n >\r\n QR Code\r\n </div>\r\n );\r\n\r\n default:\r\n return null;\r\n }\r\n })();\r\n\r\n return (\r\n <div\r\n key={field.id}\r\n id={`preview-${field.id}`}\r\n style={combinedStyle}\r\n onClick={handleClick}\r\n className={`preview-field preview-field-${field.type} ${isSelected ? 'selected' : ''}`}\r\n >\r\n {content}\r\n {renderResizeHandles(field)}\r\n </div>\r\n );\r\n };\r\n\r\n // Card container style\r\n const cardStyle: React.CSSProperties = {\r\n width: template.cardSize.width,\r\n height: template.cardSize.height,\r\n position: 'relative',\r\n overflow: 'hidden',\r\n backgroundColor: backgroundImage ? 'transparent' : '#f5f5f5',\r\n border: '1px solid #ddd',\r\n transform: `scale(${scale})`,\r\n transformOrigin: 'top left',\r\n };\r\n\r\n // Grid overlay style\r\n const gridStyle: React.CSSProperties = {\r\n position: 'absolute',\r\n top: 0,\r\n left: 0,\r\n width: '100%',\r\n height: '100%',\r\n backgroundImage: showGrid\r\n ? 'linear-gradient(to right, rgba(0,0,0,0.05) 1px, transparent 1px), linear-gradient(to bottom, rgba(0,0,0,0.05) 1px, transparent 1px)'\r\n : 'none',\r\n backgroundSize: '10px 10px',\r\n pointerEvents: 'none',\r\n zIndex: 999,\r\n };\r\n\r\n const handleContainerClick = () => {\r\n onFieldSelect?.(null);\r\n };\r\n\r\n return (\r\n <div\r\n className=\"idcard-preview-container\"\r\n style={cardStyle}\r\n onClick={handleContainerClick}\r\n >\r\n {/* Background */}\r\n {backgroundImage && (\r\n <img\r\n src={backgroundImage}\r\n alt={`${side} background`}\r\n style={{\r\n position: 'absolute',\r\n top: 0,\r\n left: 0,\r\n width: '100%',\r\n height: '100%',\r\n objectFit: 'cover',\r\n zIndex: 0,\r\n pointerEvents: 'none',\r\n }}\r\n />\r\n )}\r\n\r\n {/* Fields */}\r\n {fieldsToRender.map(renderField)}\r\n\r\n {/* Grid overlay */}\r\n {showGrid && <div style={gridStyle} />}\r\n\r\n {/* Empty state */}\r\n {!backgroundImage && fieldsToRender.length === 0 && (\r\n <div\r\n style={{\r\n position: 'absolute',\r\n top: '50%',\r\n left: '50%',\r\n transform: 'translate(-50%, -50%)',\r\n textAlign: 'center',\r\n color: '#999',\r\n fontSize: '14px',\r\n }}\r\n >\r\n <p>Upload a background image</p>\r\n <p>and add fields to get started</p>\r\n </div>\r\n )}\r\n </div>\r\n );\r\n};\r\n\r\n"],"names":["useMemo","_jsx","positionToStyle","fieldStyleToCSS","_jsxs","_Fragment","generateQrCodeFromFields"],"mappings":"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"}
@@ -27,7 +27,7 @@ function toReactStyle(styleObj) {
27
27
  function fieldStyleToCSS(style) {
28
28
  if (!style)
29
29
  return {};
30
- const css = {
30
+ return {
31
31
  fontSize: style.fontSize,
32
32
  fontWeight: style.fontWeight,
33
33
  fontFamily: style.fontFamily,
@@ -40,24 +40,8 @@ function fieldStyleToCSS(style) {
40
40
  border: style.border,
41
41
  lineHeight: style.lineHeight,
42
42
  letterSpacing: style.letterSpacing,
43
+ // verticalAlign is handled in the preview component for flex
43
44
  };
44
- // Add vertical alignment for text fields
45
- if (style.verticalAlign) {
46
- if (style.verticalAlign === 'top') {
47
- css.display = 'flex';
48
- css.alignItems = 'flex-start';
49
- }
50
- else if (style.verticalAlign === 'middle') {
51
- css.display = 'flex';
52
- css.alignItems = 'center';
53
- }
54
- else if (style.verticalAlign === 'bottom') {
55
- css.display = 'flex';
56
- css.alignItems = 'flex-end';
57
- }
58
- css.height = '100%';
59
- }
60
- return css;
61
45
  }
62
46
  /**
63
47
  * Merge multiple style objects
@@ -1 +1 @@
1
- {"version":3,"file":"styleUtils.js","sources":["../../../src/utils/styleUtils.ts"],"sourcesContent":["import type React from 'react';\r\nimport type { FieldStyle } from '../types';\r\n\r\n/**\r\n * Convert a style object with kebab-case keys to React camelCase\r\n * Useful when importing styles from external sources\r\n */\r\nexport function toReactStyle(\r\n styleObj: Record<string, unknown> | undefined\r\n): React.CSSProperties {\r\n if (!styleObj) return {};\r\n\r\n const out: Record<string, unknown> = {};\r\n for (const key in styleObj) {\r\n // Skip non-style properties\r\n if (key === 'frontOrBack' || key === 'category' || key === 'element') {\r\n continue;\r\n }\r\n // Convert kebab-case to camelCase (e.g., 'font-size' -> 'fontSize')\r\n const reactKey = key.replace(/-([a-z])/g, (g) => g[1].toUpperCase());\r\n out[reactKey] = styleObj[key];\r\n }\r\n return out as React.CSSProperties;\r\n}\r\n\r\n/**\r\n * Convert FieldStyle to React CSS properties\r\n * @param style - The field style object\r\n * @returns React-compatible CSS properties object\r\n */\r\nexport function fieldStyleToCSS(style: FieldStyle | undefined): React.CSSProperties {\r\n if (!style) return {};\r\n const css: React.CSSProperties = {\r\n fontSize: style.fontSize,\r\n fontWeight: style.fontWeight,\r\n fontFamily: style.fontFamily,\r\n color: style.color,\r\n textAlign: style.textAlign,\r\n textTransform: style.textTransform,\r\n backgroundColor: style.backgroundColor,\r\n borderRadius: style.borderRadius,\r\n padding: style.padding,\r\n border: style.border,\r\n lineHeight: style.lineHeight,\r\n letterSpacing: style.letterSpacing,\r\n };\r\n // Add vertical alignment for text fields\r\n if (style.verticalAlign) {\r\n if (style.verticalAlign === 'top') {\r\n css.display = 'flex';\r\n css.alignItems = 'flex-start';\r\n } else if (style.verticalAlign === 'middle') {\r\n css.display = 'flex';\r\n css.alignItems = 'center';\r\n } else if (style.verticalAlign === 'bottom') {\r\n css.display = 'flex';\r\n css.alignItems = 'flex-end';\r\n }\r\n css.height = '100%';\r\n }\r\n return css;\r\n}\r\n\r\n/**\r\n * Merge multiple style objects\r\n */\r\nexport function mergeStyles(\r\n ...styles: (React.CSSProperties | undefined)[]\r\n): React.CSSProperties {\r\n return Object.assign({}, ...styles.filter(Boolean));\r\n}\r\n\r\n/**\r\n * Generate position styles from field position\r\n */\r\nexport function positionToStyle(position: {\r\n x: number;\r\n y: number;\r\n width: number;\r\n height: number;\r\n}): React.CSSProperties {\r\n return {\r\n position: 'absolute',\r\n left: `${position.x}px`,\r\n top: `${position.y}px`,\r\n width: `${position.width}px`,\r\n height: `${position.height}px`,\r\n };\r\n}\r\n\r\n/**\r\n * Default styles for different field types\r\n */\r\nexport const defaultFieldStyles: Record<string, FieldStyle> = {\r\n text: {\r\n fontSize: '12px',\r\n fontWeight: 'normal',\r\n color: '#000000',\r\n textAlign: 'left',\r\n },\r\n label: {\r\n fontSize: '10px',\r\n fontWeight: 'bold',\r\n color: '#333333',\r\n textAlign: 'left',\r\n textTransform: 'uppercase',\r\n },\r\n image: {},\r\n qrcode: {\r\n backgroundColor: '#ffffff',\r\n },\r\n};\r\n\r\n/**\r\n * CSS class name generator\r\n */\r\nexport function cn(...classes: (string | undefined | null | false)[]): string {\r\n return classes.filter(Boolean).join(' ');\r\n}\r\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"styleUtils.js","sources":["../../../src/utils/styleUtils.ts"],"sourcesContent":["import type React from 'react';\r\nimport type { FieldStyle } from '../types';\r\n\r\n/**\r\n * Convert a style object with kebab-case keys to React camelCase\r\n * Useful when importing styles from external sources\r\n */\r\nexport function toReactStyle(\r\n styleObj: Record<string, unknown> | undefined\r\n): React.CSSProperties {\r\n if (!styleObj) return {};\r\n\r\n const out: Record<string, unknown> = {};\r\n for (const key in styleObj) {\r\n // Skip non-style properties\r\n if (key === 'frontOrBack' || key === 'category' || key === 'element') {\r\n continue;\r\n }\r\n // Convert kebab-case to camelCase (e.g., 'font-size' -> 'fontSize')\r\n const reactKey = key.replace(/-([a-z])/g, (g) => g[1].toUpperCase());\r\n out[reactKey] = styleObj[key];\r\n }\r\n return out as React.CSSProperties;\r\n}\r\n\r\n/**\r\n * Convert FieldStyle to React CSS properties\r\n * @param style - The field style object\r\n * @returns React-compatible CSS properties object\r\n */\r\nexport function fieldStyleToCSS(style: FieldStyle | undefined): React.CSSProperties {\r\n if (!style) return {};\r\n return {\r\n fontSize: style.fontSize,\r\n fontWeight: style.fontWeight,\r\n fontFamily: style.fontFamily,\r\n color: style.color,\r\n textAlign: style.textAlign,\r\n textTransform: style.textTransform,\r\n backgroundColor: style.backgroundColor,\r\n borderRadius: style.borderRadius,\r\n padding: style.padding,\r\n border: style.border,\r\n lineHeight: style.lineHeight,\r\n letterSpacing: style.letterSpacing,\r\n // verticalAlign is handled in the preview component for flex\r\n };\r\n}\r\n\r\n/**\r\n * Merge multiple style objects\r\n */\r\nexport function mergeStyles(\r\n ...styles: (React.CSSProperties | undefined)[]\r\n): React.CSSProperties {\r\n return Object.assign({}, ...styles.filter(Boolean));\r\n}\r\n\r\n/**\r\n * Generate position styles from field position\r\n */\r\nexport function positionToStyle(position: {\r\n x: number;\r\n y: number;\r\n width: number;\r\n height: number;\r\n}): React.CSSProperties {\r\n return {\r\n position: 'absolute',\r\n left: `${position.x}px`,\r\n top: `${position.y}px`,\r\n width: `${position.width}px`,\r\n height: `${position.height}px`,\r\n };\r\n}\r\n\r\n/**\r\n * Default styles for different field types\r\n */\r\nexport const defaultFieldStyles: Record<string, FieldStyle> = {\r\n text: {\r\n fontSize: '12px',\r\n fontWeight: 'normal',\r\n color: '#000000',\r\n textAlign: 'left',\r\n },\r\n label: {\r\n fontSize: '10px',\r\n fontWeight: 'bold',\r\n color: '#333333',\r\n textAlign: 'left',\r\n textTransform: 'uppercase',\r\n },\r\n image: {},\r\n qrcode: {\r\n backgroundColor: '#ffffff',\r\n },\r\n};\r\n\r\n/**\r\n * CSS class name generator\r\n */\r\nexport function cn(...classes: (string | undefined | null | false)[]): string {\r\n return classes.filter(Boolean).join(' ');\r\n}\r\n"],"names":[],"mappings":";;AAGA;;;AAGG;AACG,SAAU,YAAY,CAC1B,QAA6C,EAAA;AAE7C,IAAA,IAAI,CAAC,QAAQ;AAAE,QAAA,OAAO,EAAE;IAExB,MAAM,GAAG,GAA4B,EAAE;AACvC,IAAA,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE;;AAE1B,QAAA,IAAI,GAAG,KAAK,aAAa,IAAI,GAAG,KAAK,UAAU,IAAI,GAAG,KAAK,SAAS,EAAE;YACpE;QACF;;QAEA,MAAM,QAAQ,GAAG,GAAG,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;QACpE,GAAG,CAAC,QAAQ,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC;IAC/B;AACA,IAAA,OAAO,GAA0B;AACnC;AAEA;;;;AAIG;AACG,SAAU,eAAe,CAAC,KAA6B,EAAA;AAC3D,IAAA,IAAI,CAAC,KAAK;AAAE,QAAA,OAAO,EAAE;IACrB,OAAO;QACL,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,UAAU,EAAE,KAAK,CAAC,UAAU;QAC5B,UAAU,EAAE,KAAK,CAAC,UAAU;QAC5B,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,aAAa,EAAE,KAAK,CAAC,aAAa;QAClC,eAAe,EAAE,KAAK,CAAC,eAAe;QACtC,YAAY,EAAE,KAAK,CAAC,YAAY;QAChC,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,UAAU,EAAE,KAAK,CAAC,UAAU;QAC5B,aAAa,EAAE,KAAK,CAAC,aAAa;;KAEnC;AACH;AAEA;;AAEG;AACG,SAAU,WAAW,CACzB,GAAG,MAA2C,EAAA;AAE9C,IAAA,OAAO,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;AACrD;AAEA;;AAEG;AACG,SAAU,eAAe,CAAC,QAK/B,EAAA;IACC,OAAO;AACL,QAAA,QAAQ,EAAE,UAAU;AACpB,QAAA,IAAI,EAAE,CAAA,EAAG,QAAQ,CAAC,CAAC,CAAA,EAAA,CAAI;AACvB,QAAA,GAAG,EAAE,CAAA,EAAG,QAAQ,CAAC,CAAC,CAAA,EAAA,CAAI;AACtB,QAAA,KAAK,EAAE,CAAA,EAAG,QAAQ,CAAC,KAAK,CAAA,EAAA,CAAI;AAC5B,QAAA,MAAM,EAAE,CAAA,EAAG,QAAQ,CAAC,MAAM,CAAA,EAAA,CAAI;KAC/B;AACH;AAEA;;AAEG;AACI,MAAM,kBAAkB,GAA+B;AAC5D,IAAA,IAAI,EAAE;AACJ,QAAA,QAAQ,EAAE,MAAM;AAChB,QAAA,UAAU,EAAE,QAAQ;AACpB,QAAA,KAAK,EAAE,SAAS;AAChB,QAAA,SAAS,EAAE,MAAM;AAClB,KAAA;AACD,IAAA,KAAK,EAAE;AACL,QAAA,QAAQ,EAAE,MAAM;AAChB,QAAA,UAAU,EAAE,MAAM;AAClB,QAAA,KAAK,EAAE,SAAS;AAChB,QAAA,SAAS,EAAE,MAAM;AACjB,QAAA,aAAa,EAAE,WAAW;AAC3B,KAAA;AACD,IAAA,KAAK,EAAE,EAAE;AACT,IAAA,MAAM,EAAE;AACN,QAAA,eAAe,EAAE,SAAS;AAC3B,KAAA;;AAGH;;AAEG;AACG,SAAU,EAAE,CAAC,GAAG,OAA8C,EAAA;IAClE,OAAO,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;AAC1C;;;;;;;;;"}
@@ -1,4 +1,4 @@
1
- import { jsxs, jsx } from 'react/jsx-runtime';
1
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
2
2
  import { useState, useRef, useCallback, useEffect } from 'react';
3
3
  import { useIDCardTemplate } from '../hooks/useIDCardTemplate.js';
4
4
  import { deepCleanUndefined } from '../utils/deepCleanUndefined.js';
@@ -61,6 +61,24 @@ const IDCardDesigner = ({ initialTemplate, onSave, onCancel, className = '', sty
61
61
  const handleFieldSelect = useCallback((field) => {
62
62
  setSelectedFieldId(field?.id || null);
63
63
  }, []);
64
+ // Duplicate selected text field
65
+ const handleDuplicateField = useCallback(() => {
66
+ if (!selectedField || selectedField.type !== 'text')
67
+ return;
68
+ const { id, fieldKey, position, ...rest } = selectedField;
69
+ const newField = {
70
+ ...rest,
71
+ id: generateId('field'),
72
+ fieldKey: `field_${Date.now()}`,
73
+ position: {
74
+ ...position,
75
+ x: Math.min(position.x + 20, template.cardSize.width - position.width),
76
+ y: Math.min(position.y + 20, template.cardSize.height - position.height),
77
+ },
78
+ };
79
+ addField(newField);
80
+ setSelectedFieldId(newField.id);
81
+ }, [selectedField, addField, setSelectedFieldId, template.cardSize.width, template.cardSize.height]);
64
82
  // Handle adding new field
65
83
  const handleAddField = useCallback((type) => {
66
84
  const newField = {
@@ -75,6 +93,7 @@ const IDCardDesigner = ({ initialTemplate, onSave, onCancel, className = '', sty
75
93
  fontWeight: type === 'label' ? 'bold' : 'normal',
76
94
  color: '#000000',
77
95
  textAlign: 'left',
96
+ backgroundColor: 'transparent',
78
97
  },
79
98
  zIndex: template.fields.length + 1,
80
99
  staticText: type === 'label' ? 'Static Label' : '',
@@ -162,6 +181,7 @@ const IDCardDesigner = ({ initialTemplate, onSave, onCancel, className = '', sty
162
181
  if (isDragging) {
163
182
  const deltaX = e.clientX - dragStartRef.current.x;
164
183
  const deltaY = e.clientY - dragStartRef.current.y;
184
+ // Unified drag logic for all field types
165
185
  const newX = Math.max(0, Math.min(template.cardSize.width - selectedField.position.width, dragStartRef.current.fieldX + deltaX));
166
186
  const newY = Math.max(0, Math.min(template.cardSize.height - selectedField.position.height, dragStartRef.current.fieldY + deltaY));
167
187
  updateField(selectedField.id, {
@@ -176,19 +196,50 @@ const IDCardDesigner = ({ initialTemplate, onSave, onCancel, className = '', sty
176
196
  let newHeight = height;
177
197
  let newX = fieldX;
178
198
  let newY = fieldY;
179
- if (handle.includes('e')) {
180
- newWidth = Math.max(20, width + deltaX);
181
- }
182
- if (handle.includes('w')) {
183
- newWidth = Math.max(20, width - deltaX);
184
- newX = Math.max(0, fieldX + deltaX);
185
- }
186
- if (handle.includes('s')) {
187
- newHeight = Math.max(20, height + deltaY);
199
+ // Enforce fixed aspect ratio for QR code fields
200
+ if (selectedField.type === 'qrcode') {
201
+ // Use the larger delta for both width and height
202
+ let delta = 0;
203
+ if (handle.includes('e') || handle.includes('w')) {
204
+ delta = deltaX;
205
+ }
206
+ else if (handle.includes('s') || handle.includes('n')) {
207
+ delta = deltaY;
208
+ }
209
+ if (handle.includes('e')) {
210
+ newWidth = Math.max(20, width + delta);
211
+ newHeight = newWidth;
212
+ }
213
+ if (handle.includes('w')) {
214
+ newWidth = Math.max(20, width - delta);
215
+ newHeight = newWidth;
216
+ newX = Math.max(0, fieldX + delta);
217
+ }
218
+ if (handle.includes('s')) {
219
+ newHeight = Math.max(20, height + delta);
220
+ newWidth = newHeight;
221
+ }
222
+ if (handle.includes('n')) {
223
+ newHeight = Math.max(20, height - delta);
224
+ newWidth = newHeight;
225
+ newY = Math.max(0, fieldY + delta);
226
+ }
188
227
  }
189
- if (handle.includes('n')) {
190
- newHeight = Math.max(20, height - deltaY);
191
- newY = Math.max(0, fieldY + deltaY);
228
+ else {
229
+ if (handle.includes('e')) {
230
+ newWidth = Math.max(20, width + deltaX);
231
+ }
232
+ if (handle.includes('w')) {
233
+ newWidth = Math.max(20, width - deltaX);
234
+ newX = Math.max(0, fieldX + deltaX);
235
+ }
236
+ if (handle.includes('s')) {
237
+ newHeight = Math.max(20, height + deltaY);
238
+ }
239
+ if (handle.includes('n')) {
240
+ newHeight = Math.max(20, height - deltaY);
241
+ newY = Math.max(0, fieldY + deltaY);
242
+ }
192
243
  }
193
244
  updateField(selectedField.id, {
194
245
  position: { x: newX, y: newY, width: newWidth, height: newHeight },
@@ -198,14 +249,19 @@ const IDCardDesigner = ({ initialTemplate, onSave, onCancel, className = '', sty
198
249
  const handleMouseUp = () => {
199
250
  setIsDragging(false);
200
251
  setIsResizing(false);
252
+ // Remove any accidental selection or pointer capture
253
+ window.getSelection?.()?.removeAllRanges?.();
254
+ if (previewContainerRef.current) {
255
+ previewContainerRef.current.releasePointerCapture?.(0);
256
+ }
201
257
  };
202
258
  if (isDragging || isResizing) {
203
259
  document.addEventListener('mousemove', handleMouseMove);
204
- document.addEventListener('mouseup', handleMouseUp);
260
+ window.addEventListener('mouseup', handleMouseUp);
205
261
  }
206
262
  return () => {
207
263
  document.removeEventListener('mousemove', handleMouseMove);
208
- document.removeEventListener('mouseup', handleMouseUp);
264
+ window.removeEventListener('mouseup', handleMouseUp);
209
265
  };
210
266
  }, [isDragging, isResizing, selectedField, updateField, template.cardSize]);
211
267
  // Handle field property update
@@ -256,16 +312,41 @@ const IDCardDesigner = ({ initialTemplate, onSave, onCancel, className = '', sty
256
312
  // Keyboard shortcuts
257
313
  useEffect(() => {
258
314
  const handleKeyDown = (e) => {
259
- if (e.key === 'Delete' && selectedFieldId) {
260
- handleDeleteField();
261
- }
262
- if (e.key === 'Escape') {
263
- setSelectedFieldId(null);
315
+ if (selectedFieldId) {
316
+ // Arrow key movement
317
+ const step = e.shiftKey ? 5 : 1;
318
+ let dx = 0, dy = 0;
319
+ if (e.key === 'ArrowLeft')
320
+ dx = -step;
321
+ if (e.key === 'ArrowRight')
322
+ dx = step;
323
+ if (e.key === 'ArrowUp')
324
+ dy = -step;
325
+ if (e.key === 'ArrowDown')
326
+ dy = step;
327
+ if (dx !== 0 || dy !== 0) {
328
+ const field = template.fields.find(f => f.id === selectedFieldId);
329
+ if (field) {
330
+ let newX = Math.max(0, Math.min(template.cardSize.width - field.position.width, field.position.x + dx));
331
+ let newY = Math.max(0, Math.min(template.cardSize.height - field.position.height, field.position.y + dy));
332
+ updateField(field.id, {
333
+ position: { ...field.position, x: newX, y: newY },
334
+ });
335
+ }
336
+ e.preventDefault();
337
+ }
338
+ // Delete and Escape
339
+ if (e.key === 'Delete') {
340
+ handleDeleteField();
341
+ }
342
+ if (e.key === 'Escape') {
343
+ setSelectedFieldId(null);
344
+ }
264
345
  }
265
346
  };
266
347
  document.addEventListener('keydown', handleKeyDown);
267
348
  return () => document.removeEventListener('keydown', handleKeyDown);
268
- }, [selectedFieldId, handleDeleteField]);
349
+ }, [selectedFieldId, handleDeleteField, template.fields, template.cardSize, updateField]);
269
350
  return (jsxs("div", { className: `idcard-designer ${className}`, style: style, children: [jsxs("div", { className: "designer-header", children: [jsx("input", { type: "text", value: templateName, onChange: (e) => setTemplateName(e.target.value), className: "template-name-input", placeholder: "Template Name" }), jsxs("div", { className: "header-actions", children: [onCancel && (jsx("button", { onClick: onCancel, className: "btn btn-secondary", children: "Cancel" })), jsx("button", { onClick: handleSave, className: "btn btn-primary", disabled: !isValid, children: "Save Template" })] })] }), jsxs("div", { className: "designer-body", children: [jsxs("div", { className: "designer-sidebar left-sidebar", children: [jsx("h3", { children: "Add Fields" }), jsx("div", { className: "field-types", children: defaultFieldTypes.map((fieldType) => (jsxs("button", { onClick: () => handleAddField(fieldType.type), className: "field-type-btn", children: [jsx("span", { className: "field-icon", children: fieldType.icon }), jsx("span", { className: "field-label", children: fieldType.label })] }, fieldType.type))) }), jsx("h3", { children: "Card Settings" }), jsxs("div", { className: "card-settings", children: [jsxs("div", { className: "setting-group", children: [jsx("label", { children: "Orientation" }), jsxs("div", { className: "btn-group", children: [jsx("button", { className: `btn ${template.orientation === 'landscape' ? 'active' : ''}`, onClick: () => handleOrientationChange('landscape'), children: "Landscape" }), jsx("button", { className: `btn ${template.orientation === 'portrait' ? 'active' : ''}`, onClick: () => handleOrientationChange('portrait'), children: "Portrait" })] })] }), jsxs("div", { className: "setting-group", children: [jsx("label", { children: "Card Sides" }), jsxs("div", { className: "btn-group", children: [jsx("button", { className: `btn ${template.sides === 'single' ? 'active' : ''}`, onClick: () => handleSidesChange('single'), children: "Single" }), jsx("button", { className: `btn ${template.sides === 'double' ? 'active' : ''}`, onClick: () => handleSidesChange('double'), children: "Double" })] })] }), jsxs("div", { className: "setting-group", children: [jsx("label", { children: "Card Size (px)" }), jsxs("div", { className: "size-inputs", children: [jsx("input", { type: "number", value: template.cardSize.width, onChange: (e) => setCardSize({ ...template.cardSize, width: Number(e.target.value) }), placeholder: "Width" }), jsx("span", { children: "\u00D7" }), jsx("input", { type: "number", value: template.cardSize.height, onChange: (e) => setCardSize({ ...template.cardSize, height: Number(e.target.value) }), placeholder: "Height" })] })] })] }), jsx("h3", { children: "Background" }), jsxs("div", { className: "background-upload", children: [jsx("input", { type: "file", accept: "image/*", onChange: handleBackgroundUpload, id: "bg-upload", className: "file-input" }), jsxs("label", { htmlFor: "bg-upload", className: "btn btn-outline", children: ["Upload ", activeSide, " Background"] }), template.backgrounds[activeSide] && (jsx("button", { className: "btn btn-danger btn-sm", onClick: () => setBackground(activeSide, ''), children: "Remove" }))] })] }), jsxs("div", { className: "designer-preview", children: [jsxs("div", { className: "side-tabs", children: [jsx("button", { className: `tab ${activeSide === 'front' ? 'active' : ''}`, onClick: () => setActiveSide('front'), children: "Front" }), template.sides === 'double' && (jsx("button", { className: `tab ${activeSide === 'back' ? 'active' : ''}`, onClick: () => setActiveSide('back'), children: "Back" })), jsxs("label", { className: "grid-toggle", children: [jsx("input", { type: "checkbox", checked: showGrid, onChange: (e) => setShowGrid(e.target.checked) }), "Show Grid"] })] }), jsx("div", { ref: previewContainerRef, className: "preview-wrapper", onMouseDown: handleMouseDown, children: jsx(IDCardPreview, { template: template, data: sampleData, side: activeSide, showGrid: showGrid, onFieldSelect: handleFieldSelect, selectedFieldId: selectedFieldId, editable: true }) }), jsxs("div", { className: "fields-list", children: [jsxs("h4", { children: ["Fields on ", activeSide, " side:"] }), template.fields
270
351
  .filter((f) => f.side === activeSide)
271
352
  .map((field) => (jsxs("div", { className: `field-item ${field.id === selectedFieldId ? 'selected' : ''}`, onClick: () => setSelectedFieldId(field.id), children: [jsx("span", { className: "field-type-icon", children: defaultFieldTypes.find((t) => t.type === field.type)?.icon }), jsx("span", { className: "field-key", children: field.fieldKey }), jsx("button", { className: "delete-btn", onClick: (e) => {
@@ -273,7 +354,7 @@ const IDCardDesigner = ({ initialTemplate, onSave, onCancel, className = '', sty
273
354
  removeField(field.id);
274
355
  if (field.id === selectedFieldId)
275
356
  setSelectedFieldId(null);
276
- }, children: "\u00D7" })] }, field.id)))] })] }), jsxs("div", { className: "designer-sidebar right-sidebar", children: [jsx("h3", { children: "Field Properties" }), selectedField ? (jsxs("div", { className: "field-properties", children: [jsxs("div", { className: "property-group", children: [jsx("label", { children: "Field Key" }), jsx("input", { type: "text", value: selectedField.fieldKey, onChange: (e) => handleFieldPropertyUpdate('fieldKey', e.target.value) })] }), selectedField.type !== 'label' && (jsxs("div", { className: "property-group", children: [jsx("label", { children: "Label" }), jsx("input", { type: "text", value: selectedField.label || '', onChange: (e) => handleFieldPropertyUpdate('label', e.target.value) })] })), selectedField.type === 'label' && (jsxs("div", { className: "property-group", children: [jsx("label", { children: "Static Text" }), jsx("input", { type: "text", value: selectedField.staticText || '', onChange: (e) => handleFieldPropertyUpdate('staticText', e.target.value) })] })), selectedField.type === 'qrcode' && (jsxs("div", { className: "property-group", children: [jsx("label", { children: "QR Code Data Fields" }), jsx("small", { className: "hint", children: "Select which fields to encode in the QR code:" }), jsx("div", { style: { marginTop: '8px', maxHeight: '150px', overflowY: 'auto', border: '1px solid #ddd', borderRadius: '4px', padding: '8px' }, children: template.fields
357
+ }, children: "\u00D7" })] }, field.id)))] })] }), jsxs("div", { className: "designer-sidebar right-sidebar", children: [jsx("h3", { children: "Field Properties" }), selectedField ? (jsxs("div", { className: "field-properties", children: [jsxs("div", { className: "property-group", children: [jsx("label", { children: "Field Key" }), jsx("input", { type: "text", value: selectedField.fieldKey, onChange: (e) => handleFieldPropertyUpdate('fieldKey', e.target.value) })] }), selectedField.type !== 'label' && (jsxs("div", { className: "property-group", children: [jsx("label", { children: "Label" }), jsx("input", { type: "text", value: selectedField.label || '', onChange: (e) => handleFieldPropertyUpdate('label', e.target.value) })] })), selectedField.type === 'text' && (jsxs(Fragment, { children: [jsx("button", { className: "btn btn-outline btn-sm", style: { marginBottom: 12 }, onClick: handleDuplicateField, children: "Duplicate Field" }), jsxs("div", { style: { border: '1px solid #eee', borderRadius: 4, marginBottom: 12, padding: 8 }, children: [jsx("strong", { style: { fontSize: '1em', display: 'block', marginBottom: 6 }, children: "Label Style" }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Label Position" }), jsxs("select", { value: selectedField.style?.labelPosition || 'top', onChange: (e) => handleFieldPropertyUpdate('style.labelPosition', e.target.value), children: [jsx("option", { value: "top", children: "Top" }), jsx("option", { value: "left", children: "Left (Side)" })] })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Label Vertical Align" }), jsxs("select", { value: selectedField.style?.labelVerticalAlign || 'top', onChange: (e) => handleFieldPropertyUpdate('style.labelVerticalAlign', e.target.value), children: [jsx("option", { value: "top", children: "Top" }), jsx("option", { value: "middle", children: "Middle" }), jsx("option", { value: "bottom", children: "Bottom" })] })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Label Font Size" }), jsx("input", { type: "text", value: selectedField.style?.labelFontSize || '0.75em', onChange: (e) => handleFieldPropertyUpdate('style.labelFontSize', e.target.value) })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Label Font Weight" }), jsxs("select", { value: selectedField.style?.labelFontWeight || 'normal', onChange: (e) => handleFieldPropertyUpdate('style.labelFontWeight', e.target.value), children: [jsx("option", { value: "normal", children: "Normal" }), jsx("option", { value: "bold", children: "Bold" }), jsx("option", { value: "100", children: "100" }), jsx("option", { value: "200", children: "200" }), jsx("option", { value: "300", children: "300" }), jsx("option", { value: "400", children: "400" }), jsx("option", { value: "500", children: "500" }), jsx("option", { value: "600", children: "600" }), jsx("option", { value: "700", children: "700" }), jsx("option", { value: "800", children: "800" }), jsx("option", { value: "900", children: "900" })] })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Label Color" }), jsx("input", { type: "color", value: selectedField.style?.labelColor || '#666666', onChange: (e) => handleFieldPropertyUpdate('style.labelColor', e.target.value) })] })] }), jsxs("div", { style: { border: '1px solid #eee', borderRadius: 4, marginBottom: 12, padding: 8 }, children: [jsx("strong", { style: { fontSize: '1em', display: 'block', marginBottom: 6 }, children: "Text Style" }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Font Size" }), jsx("input", { type: "text", value: selectedField.style?.fontSize || '12px', onChange: (e) => handleFieldPropertyUpdate('style.fontSize', e.target.value) })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Font Weight" }), jsxs("select", { value: selectedField.style?.fontWeight || 'normal', onChange: (e) => handleFieldPropertyUpdate('style.fontWeight', e.target.value), children: [jsx("option", { value: "normal", children: "Normal" }), jsx("option", { value: "bold", children: "Bold" }), jsx("option", { value: "100", children: "100" }), jsx("option", { value: "200", children: "200" }), jsx("option", { value: "300", children: "300" }), jsx("option", { value: "400", children: "400" }), jsx("option", { value: "500", children: "500" }), jsx("option", { value: "600", children: "600" }), jsx("option", { value: "700", children: "700" }), jsx("option", { value: "800", children: "800" }), jsx("option", { value: "900", children: "900" })] })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Text Color" }), jsx("input", { type: "color", value: selectedField.style?.color || '#000000', onChange: (e) => handleFieldPropertyUpdate('style.color', e.target.value) })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Text Align" }), jsxs("select", { value: selectedField.style?.textAlign || 'left', onChange: (e) => handleFieldPropertyUpdate('style.textAlign', e.target.value), children: [jsx("option", { value: "left", children: "Left" }), jsx("option", { value: "center", children: "Center" }), jsx("option", { value: "right", children: "Right" })] })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Text Transform" }), jsxs("select", { value: selectedField.style?.textTransform || 'none', onChange: (e) => handleFieldPropertyUpdate('style.textTransform', e.target.value), children: [jsx("option", { value: "none", children: "None" }), jsx("option", { value: "uppercase", children: "UPPERCASE" }), jsx("option", { value: "lowercase", children: "lowercase" }), jsx("option", { value: "capitalize", children: "Capitalize" })] })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Vertical Align" }), jsxs("select", { value: selectedField.style?.verticalAlign || 'top', onChange: (e) => handleFieldPropertyUpdate('style.verticalAlign', e.target.value), children: [jsx("option", { value: "top", children: "Top" }), jsx("option", { value: "middle", children: "Middle" }), jsx("option", { value: "bottom", children: "Bottom" })] })] })] })] })), selectedField.type === 'label' && (jsxs("div", { className: "property-group", children: [jsx("label", { children: "Static Text" }), jsx("input", { type: "text", value: selectedField.staticText || '', onChange: (e) => handleFieldPropertyUpdate('staticText', e.target.value) })] })), selectedField.type === 'qrcode' && (jsxs("div", { className: "property-group", children: [jsx("label", { children: "QR Code Data Fields" }), jsx("small", { className: "hint", children: "Select which fields to encode in the QR code:" }), jsx("div", { style: { marginTop: '8px', maxHeight: '150px', overflowY: 'auto', border: '1px solid #ddd', borderRadius: '4px', padding: '8px' }, children: template.fields
277
358
  .filter(f => f.type !== 'qrcode' && f.type !== 'label')
278
359
  .map((field) => (jsxs("label", { style: { display: 'block', marginBottom: '6px', cursor: 'pointer' }, children: [jsx("input", { type: "checkbox", checked: selectedField.qrFields?.includes(field.fieldKey) || false, onChange: (e) => {
279
360
  const currentFields = selectedField.qrFields || [];
@@ -281,7 +362,7 @@ const IDCardDesigner = ({ initialTemplate, onSave, onCancel, className = '', sty
281
362
  ? [...currentFields, field.fieldKey]
282
363
  : currentFields.filter(f => f !== field.fieldKey);
283
364
  handleFieldPropertyUpdate('qrFields', newFields);
284
- }, style: { marginRight: '6px' } }), field.label || field.fieldKey] }, field.fieldKey))) })] })), jsx("h4", { children: "Style" }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Font Size" }), jsx("input", { type: "text", value: selectedField.style?.fontSize || '12px', onChange: (e) => handleFieldPropertyUpdate('style.fontSize', e.target.value) })] }), selectedField.type === 'text' && (jsxs("div", { className: "property-group", children: [jsx("label", { children: "Vertical Align" }), jsxs("select", { value: selectedField.style?.verticalAlign || 'top', onChange: (e) => handleFieldPropertyUpdate('style.verticalAlign', e.target.value), children: [jsx("option", { value: "top", children: "Top" }), jsx("option", { value: "middle", children: "Middle" }), jsx("option", { value: "bottom", children: "Bottom" })] })] })), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Font Weight" }), jsxs("select", { value: selectedField.style?.fontWeight || 'normal', onChange: (e) => handleFieldPropertyUpdate('style.fontWeight', e.target.value), children: [jsx("option", { value: "normal", children: "Normal" }), jsx("option", { value: "bold", children: "Bold" }), jsx("option", { value: "100", children: "100" }), jsx("option", { value: "200", children: "200" }), jsx("option", { value: "300", children: "300" }), jsx("option", { value: "400", children: "400" }), jsx("option", { value: "500", children: "500" }), jsx("option", { value: "600", children: "600" }), jsx("option", { value: "700", children: "700" }), jsx("option", { value: "800", children: "800" }), jsx("option", { value: "900", children: "900" })] })] }), selectedField.type === 'image' && (jsxs("div", { className: "property-group", children: [jsx("label", { children: "Border Radius" }), jsx("input", { type: "text", placeholder: "e.g. 50% or 8px", value: selectedField.style?.borderRadius || '', onChange: (e) => handleFieldPropertyUpdate('style.borderRadius', e.target.value) }), jsxs("small", { className: "hint", children: ["Use ", jsx("b", { children: "50%" }), " for a circle, ", jsx("b", { children: "8px" }), " for rounded corners, etc."] })] })), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Text Color" }), jsx("input", { type: "color", value: selectedField.style?.color || '#000000', onChange: (e) => handleFieldPropertyUpdate('style.color', e.target.value) })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Text Align" }), jsxs("select", { value: selectedField.style?.textAlign || 'left', onChange: (e) => handleFieldPropertyUpdate('style.textAlign', e.target.value), children: [jsx("option", { value: "left", children: "Left" }), jsx("option", { value: "center", children: "Center" }), jsx("option", { value: "right", children: "Right" })] })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Text Transform" }), jsxs("select", { value: selectedField.style?.textTransform || 'none', onChange: (e) => handleFieldPropertyUpdate('style.textTransform', e.target.value), children: [jsx("option", { value: "none", children: "None" }), jsx("option", { value: "uppercase", children: "UPPERCASE" }), jsx("option", { value: "lowercase", children: "lowercase" }), jsx("option", { value: "capitalize", children: "Capitalize" })] })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Background Color" }), jsx("input", { type: "color", value: selectedField.style?.backgroundColor || '#ffffff', onChange: (e) => handleFieldPropertyUpdate('style.backgroundColor', e.target.value) })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Side" }), jsxs("select", { value: selectedField.side, onChange: (e) => handleFieldPropertyUpdate('side', e.target.value), children: [jsx("option", { value: "front", children: "Front" }), template.sides === 'double' && jsx("option", { value: "back", children: "Back" })] })] }), jsx("button", { className: "btn btn-danger", onClick: handleDeleteField, children: "Delete Field" })] })) : (jsx("p", { className: "no-selection", children: "Select a field to edit its properties" })), jsx("h3", { children: "Sample Data" }), jsxs("div", { className: "sample-data", children: [jsx("p", { className: "hint", children: "Edit sample data to preview:" }), Object.entries(sampleData).map(([key, value]) => (jsxs("div", { className: "property-group", children: [jsx("label", { children: key }), jsx("input", { type: "text", value: String(value), onChange: (e) => setSampleData((prev) => ({ ...prev, [key]: e.target.value })) })] }, key))), jsx("button", { className: "btn btn-outline btn-sm", onClick: () => setSampleData((prev) => ({ ...prev, [`custom_${Date.now()}`]: '' })), children: "+ Add Field" })] }), validationErrors.length > 0 && (jsxs("div", { className: "validation-errors", children: [jsx("h4", { children: "\u26A0\uFE0F Validation Issues" }), jsx("ul", { children: validationErrors.map((error, index) => (jsx("li", { children: error }, index))) })] }))] })] })] }));
365
+ }, style: { marginRight: '6px' } }), field.label || field.fieldKey] }, field.fieldKey))) })] })), selectedField.type === 'image' && (jsxs("div", { className: "property-group", children: [jsx("label", { children: "Border Radius" }), jsx("input", { type: "text", placeholder: "e.g. 50% or 8px", value: selectedField.style?.borderRadius || '', onChange: (e) => handleFieldPropertyUpdate('style.borderRadius', e.target.value) }), jsxs("small", { className: "hint", children: ["Use ", jsx("b", { children: "50%" }), " for a circle, ", jsx("b", { children: "8px" }), " for rounded corners, etc."] })] })), selectedField.type !== 'text' && jsxs(Fragment, { children: [jsx("h4", { children: "Style" }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Font Size" }), jsx("input", { type: "text", value: selectedField.style?.fontSize || '12px', onChange: (e) => handleFieldPropertyUpdate('style.fontSize', e.target.value) })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Font Weight" }), jsxs("select", { value: selectedField.style?.fontWeight || 'normal', onChange: (e) => handleFieldPropertyUpdate('style.fontWeight', e.target.value), children: [jsx("option", { value: "normal", children: "Normal" }), jsx("option", { value: "bold", children: "Bold" }), jsx("option", { value: "100", children: "100" }), jsx("option", { value: "200", children: "200" }), jsx("option", { value: "300", children: "300" }), jsx("option", { value: "400", children: "400" }), jsx("option", { value: "500", children: "500" }), jsx("option", { value: "600", children: "600" }), jsx("option", { value: "700", children: "700" }), jsx("option", { value: "800", children: "800" }), jsx("option", { value: "900", children: "900" })] })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Text Color" }), jsx("input", { type: "color", value: selectedField.style?.color || '#000000', onChange: (e) => handleFieldPropertyUpdate('style.color', e.target.value) })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Text Align" }), jsxs("select", { value: selectedField.style?.textAlign || 'left', onChange: (e) => handleFieldPropertyUpdate('style.textAlign', e.target.value), children: [jsx("option", { value: "left", children: "Left" }), jsx("option", { value: "center", children: "Center" }), jsx("option", { value: "right", children: "Right" })] })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Text Transform" }), jsxs("select", { value: selectedField.style?.textTransform || 'none', onChange: (e) => handleFieldPropertyUpdate('style.textTransform', e.target.value), children: [jsx("option", { value: "none", children: "None" }), jsx("option", { value: "uppercase", children: "UPPERCASE" }), jsx("option", { value: "lowercase", children: "lowercase" }), jsx("option", { value: "capitalize", children: "Capitalize" })] })] })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Background Color" }), jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 8 }, children: [jsx("input", { type: "checkbox", id: "transparent-bg-checkbox", checked: selectedField.style?.backgroundColor === 'transparent', onChange: e => handleFieldPropertyUpdate('style.backgroundColor', e.target.checked ? 'transparent' : '#ffffff') }), jsx("label", { htmlFor: "transparent-bg-checkbox", style: { margin: 0, fontWeight: 400, fontSize: '0.95em' }, children: "Transparent" }), selectedField.style?.backgroundColor !== 'transparent' && (jsx("input", { type: "color", value: selectedField.style?.backgroundColor || '#ffffff', onChange: e => handleFieldPropertyUpdate('style.backgroundColor', e.target.value) }))] })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Side" }), jsxs("select", { value: selectedField.side, onChange: (e) => handleFieldPropertyUpdate('side', e.target.value), children: [jsx("option", { value: "front", children: "Front" }), template.sides === 'double' && jsx("option", { value: "back", children: "Back" })] })] }), jsx("button", { className: "btn btn-danger", onClick: handleDeleteField, children: "Delete Field" })] })) : (jsx("p", { className: "no-selection", children: "Select a field to edit its properties" })), jsx("h3", { children: "Sample Data" }), jsxs("div", { className: "sample-data", children: [jsx("p", { className: "hint", children: "Edit sample data to preview:" }), Object.entries(sampleData).map(([key, value]) => (jsxs("div", { className: "property-group", children: [jsx("label", { children: key }), jsx("input", { type: "text", value: String(value), onChange: (e) => setSampleData((prev) => ({ ...prev, [key]: e.target.value })) })] }, key))), jsx("button", { className: "btn btn-outline btn-sm", onClick: () => setSampleData((prev) => ({ ...prev, [`custom_${Date.now()}`]: '' })), children: "+ Add Field" })] }), validationErrors.length > 0 && (jsxs("div", { className: "validation-errors", children: [jsx("h4", { children: "\u26A0\uFE0F Validation Issues" }), jsx("ul", { children: validationErrors.map((error, index) => (jsx("li", { children: error }, index))) })] }))] })] })] }));
285
366
  };
286
367
 
287
368
  export { IDCardDesigner };