react-id-card-generator 1.0.22 → 1.1.1

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.
Files changed (47) hide show
  1. package/README.md +33 -12
  2. package/dist/cjs/components/IDCardDesigner.js +170 -13
  3. package/dist/cjs/components/IDCardDesigner.js.map +1 -1
  4. package/dist/cjs/components/IDCardGenerator.js +11 -1
  5. package/dist/cjs/components/IDCardGenerator.js.map +1 -1
  6. package/dist/cjs/components/IDCardPreview.js +6 -0
  7. package/dist/cjs/components/IDCardPreview.js.map +1 -1
  8. package/dist/cjs/components/IDCardTable.js +74 -0
  9. package/dist/cjs/components/IDCardTable.js.map +1 -0
  10. package/dist/cjs/index.js +4 -0
  11. package/dist/cjs/index.js.map +1 -1
  12. package/dist/cjs/node_modules/qrious/dist/qrious.js +1 -1
  13. package/dist/cjs/storage/templateStorage.js +2 -2
  14. package/dist/cjs/storage/templateStorage.js.map +1 -1
  15. package/dist/cjs/styles.css +1 -1
  16. package/dist/cjs/utils/styleUtils.js +3 -0
  17. package/dist/cjs/utils/styleUtils.js.map +1 -1
  18. package/dist/cjs/utils/tableUtils.js +106 -0
  19. package/dist/cjs/utils/tableUtils.js.map +1 -0
  20. package/dist/cjs/utils/validators.js +33 -10
  21. package/dist/cjs/utils/validators.js.map +1 -1
  22. package/dist/esm/components/IDCardDesigner.js +170 -13
  23. package/dist/esm/components/IDCardDesigner.js.map +1 -1
  24. package/dist/esm/components/IDCardGenerator.js +11 -1
  25. package/dist/esm/components/IDCardGenerator.js.map +1 -1
  26. package/dist/esm/components/IDCardPreview.js +6 -0
  27. package/dist/esm/components/IDCardPreview.js.map +1 -1
  28. package/dist/esm/components/IDCardTable.js +72 -0
  29. package/dist/esm/components/IDCardTable.js.map +1 -0
  30. package/dist/esm/index.js +1 -0
  31. package/dist/esm/index.js.map +1 -1
  32. package/dist/esm/node_modules/qrious/dist/qrious.js +1 -1
  33. package/dist/esm/storage/templateStorage.js +2 -2
  34. package/dist/esm/storage/templateStorage.js.map +1 -1
  35. package/dist/esm/styles.css +1 -1
  36. package/dist/esm/utils/styleUtils.js +3 -0
  37. package/dist/esm/utils/styleUtils.js.map +1 -1
  38. package/dist/esm/utils/tableUtils.js +96 -0
  39. package/dist/esm/utils/tableUtils.js.map +1 -0
  40. package/dist/esm/utils/validators.js +33 -10
  41. package/dist/esm/utils/validators.js.map +1 -1
  42. package/dist/index.d.ts +43 -4
  43. package/dist/types/components/IDCardTable.d.ts +8 -0
  44. package/dist/types/index.d.ts +2 -1
  45. package/dist/types/types/index.d.ts +37 -2
  46. package/dist/types/utils/tableUtils.d.ts +10 -0
  47. package/package.json +2 -2
@@ -4,6 +4,7 @@ import { useIDCardTemplate } from '../hooks/useIDCardTemplate.js';
4
4
  import { deepCleanUndefined } from '../utils/deepCleanUndefined.js';
5
5
  import { IDCardPreview } from './IDCardPreview.js';
6
6
  import { generateId, imageFileToBase64 } from '../storage/templateStorage.js';
7
+ import { createSampleTableRows, createDefaultTableConfig, normalizeTableConfig, resizeTableColumns, coerceTableRows } from '../utils/tableUtils.js';
7
8
 
8
9
  // Available field types in the designer
9
10
  const defaultFieldTypes = [
@@ -11,7 +12,18 @@ const defaultFieldTypes = [
11
12
  { type: 'label', label: 'Static Label', icon: '🏷️' },
12
13
  { type: 'image', label: 'Image/Photo', icon: '🖼️' },
13
14
  { type: 'qrcode', label: 'QR Code', icon: '📱' },
15
+ { type: 'table', label: 'Table', icon: '▦' },
14
16
  ];
17
+ const getPhotoRadiusSliderValue = (borderRadius) => {
18
+ if (typeof borderRadius !== 'string') {
19
+ return 0;
20
+ }
21
+ const parsed = Number.parseFloat(borderRadius);
22
+ if (!Number.isFinite(parsed)) {
23
+ return 0;
24
+ }
25
+ return Math.max(0, Math.min(200, Math.round(parsed)));
26
+ };
15
27
  /**
16
28
  * IDCardDesigner - Visual drag-and-drop template builder
17
29
  *
@@ -55,6 +67,7 @@ const IDCardDesigner = ({ initialTemplate, onSave, onCancel, className = '', sty
55
67
  bloodgroup: 'O+',
56
68
  phoneno: '9876543210',
57
69
  address: 'Sample Address',
70
+ results: createSampleTableRows(createDefaultTableConfig()),
58
71
  });
59
72
  // Get selected field
60
73
  const selectedField = template.fields.find((f) => f.id === selectedFieldId);
@@ -82,9 +95,10 @@ const IDCardDesigner = ({ initialTemplate, onSave, onCancel, className = '', sty
82
95
  }, [selectedField, addField, setSelectedFieldId, template.cardSize.width, template.cardSize.height]);
83
96
  // Handle adding new field
84
97
  const handleAddField = useCallback((type) => {
98
+ const tableConfig = type === 'table' ? createDefaultTableConfig() : undefined;
85
99
  const newField = {
86
100
  id: generateId('field'),
87
- fieldKey: `field_${Date.now()}`,
101
+ fieldKey: type === 'table' ? 'results' : `field_${Date.now()}`,
88
102
  label: type === 'label' ? 'Label' : '',
89
103
  type,
90
104
  side: activeSide,
@@ -98,6 +112,7 @@ const IDCardDesigner = ({ initialTemplate, onSave, onCancel, className = '', sty
98
112
  },
99
113
  zIndex: template.fields.length + 1,
100
114
  staticText: type === 'label' ? 'Static Label' : '',
115
+ ...(tableConfig ? { table: tableConfig } : {}),
101
116
  ...(type === 'qrcode' ? { qrFields: [] } : {}),
102
117
  };
103
118
  // Adjust default sizes based on type
@@ -107,9 +122,24 @@ const IDCardDesigner = ({ initialTemplate, onSave, onCancel, className = '', sty
107
122
  else if (type === 'qrcode') {
108
123
  newField.position = { x: 50, y: 50, width: 80, height: 80 };
109
124
  }
125
+ else if (type === 'table') {
126
+ const availableTableWidth = Math.max(20, template.cardSize.width - 40);
127
+ newField.position = {
128
+ x: 20,
129
+ y: 40,
130
+ width: Math.min(availableTableWidth, 300),
131
+ height: 120,
132
+ };
133
+ }
110
134
  addField(newField);
135
+ if (type === 'table' && tableConfig) {
136
+ setSampleData((prev) => ({
137
+ ...prev,
138
+ [newField.fieldKey]: prev[newField.fieldKey] || createSampleTableRows(tableConfig),
139
+ }));
140
+ }
111
141
  setSelectedFieldId(newField.id);
112
- }, [activeSide, addField, template.fields.length]);
142
+ }, [activeSide, addField, template.cardSize.width, template.fields.length]);
113
143
  // Handle background upload
114
144
  const handleBackgroundUpload = useCallback(async (e) => {
115
145
  const file = e.target.files?.[0];
@@ -127,8 +157,8 @@ const IDCardDesigner = ({ initialTemplate, onSave, onCancel, className = '', sty
127
157
  const handleOrientationChange = useCallback((orientation) => {
128
158
  setOrientation(orientation);
129
159
  // Update card size based on orientation
130
- const baseWidth = 340;
131
- const baseHeight = 215;
160
+ const baseWidth = 640;
161
+ const baseHeight = 415;
132
162
  if (orientation === 'portrait') {
133
163
  setCardSize({ width: baseHeight, height: baseWidth, unit: 'px' });
134
164
  }
@@ -251,6 +281,19 @@ const IDCardDesigner = ({ initialTemplate, onSave, onCancel, className = '', sty
251
281
  });
252
282
  }
253
283
  else {
284
+ if (property === 'fieldKey' && selectedField.type === 'table' && typeof value === 'string') {
285
+ updateField(selectedField.id, { fieldKey: value });
286
+ setSampleData((prev) => {
287
+ if (!value || value === selectedField.fieldKey || prev[value] !== undefined) {
288
+ return prev;
289
+ }
290
+ const next = { ...prev };
291
+ next[value] = prev[selectedField.fieldKey] || createSampleTableRows(selectedField.table);
292
+ delete next[selectedField.fieldKey];
293
+ return next;
294
+ });
295
+ return;
296
+ }
254
297
  // Only allow qrFields property on QR code fields
255
298
  if (property === 'qrFields') {
256
299
  if (selectedField.type === 'qrcode') {
@@ -262,6 +305,73 @@ const IDCardDesigner = ({ initialTemplate, onSave, onCancel, className = '', sty
262
305
  }
263
306
  }
264
307
  }, [selectedField, updateField]);
308
+ const handleTableConfigUpdate = useCallback((updates) => {
309
+ if (!selectedField || selectedField.type !== 'table')
310
+ return;
311
+ const table = normalizeTableConfig(selectedField.table);
312
+ updateField(selectedField.id, {
313
+ table: {
314
+ ...table,
315
+ ...updates,
316
+ style: {
317
+ ...table.style,
318
+ ...updates.style,
319
+ },
320
+ },
321
+ });
322
+ }, [selectedField, updateField]);
323
+ const handleTableStyleUpdate = useCallback((styleKey, value) => {
324
+ if (!selectedField || selectedField.type !== 'table')
325
+ return;
326
+ const table = normalizeTableConfig(selectedField.table);
327
+ handleTableConfigUpdate({
328
+ style: {
329
+ ...table.style,
330
+ [styleKey]: value,
331
+ },
332
+ });
333
+ }, [selectedField, handleTableConfigUpdate]);
334
+ const handleTableColumnUpdate = useCallback((columnIndex, updates) => {
335
+ if (!selectedField || selectedField.type !== 'table')
336
+ return;
337
+ const table = normalizeTableConfig(selectedField.table);
338
+ const columns = table.columns.map((column, index) => index === columnIndex ? { ...column, ...updates } : column);
339
+ handleTableConfigUpdate({ columns });
340
+ }, [selectedField, handleTableConfigUpdate]);
341
+ const handleTableColumnCountUpdate = useCallback((count) => {
342
+ if (!selectedField || selectedField.type !== 'table')
343
+ return;
344
+ const table = normalizeTableConfig(selectedField.table);
345
+ handleTableConfigUpdate({
346
+ columns: resizeTableColumns(table.columns, count),
347
+ });
348
+ }, [selectedField, handleTableConfigUpdate]);
349
+ const handleTableSampleCellUpdate = useCallback((field, table, rowIndex, columnKey, value) => {
350
+ setSampleData((prev) => {
351
+ const existingRows = coerceTableRows(prev[field.fieldKey]);
352
+ const rowCount = Math.max(table.rowCount, existingRows.length, rowIndex + 1);
353
+ const rows = Array.from({ length: rowCount }, (_, index) => ({
354
+ ...(existingRows[index] || {}),
355
+ }));
356
+ rows[rowIndex] = {
357
+ ...rows[rowIndex],
358
+ [columnKey]: value,
359
+ };
360
+ return {
361
+ ...prev,
362
+ [field.fieldKey]: rows,
363
+ };
364
+ });
365
+ }, []);
366
+ const handleAddTableSampleRow = useCallback((field) => {
367
+ setSampleData((prev) => {
368
+ const rows = coerceTableRows(prev[field.fieldKey]);
369
+ return {
370
+ ...prev,
371
+ [field.fieldKey]: [...rows, {}],
372
+ };
373
+ });
374
+ }, []);
265
375
  // Handle save
266
376
  const handleSave = useCallback(() => {
267
377
  const updatedTemplate = {
@@ -281,11 +391,15 @@ const IDCardDesigner = ({ initialTemplate, onSave, onCancel, className = '', sty
281
391
  }, [selectedFieldId, removeField]);
282
392
  // Keyboard shortcuts
283
393
  useEffect(() => {
394
+ const handleLineHeightInputFocus = () => {
395
+ };
396
+ const handleLineHeightInputBlur = () => {
397
+ };
284
398
  // Listen for focus/blur on line height input
285
399
  const lhInput = document.getElementById('line-height-input');
286
400
  if (lhInput) {
287
- lhInput.addEventListener('focus', () => { });
288
- lhInput.addEventListener('blur', () => { });
401
+ lhInput.addEventListener('focus', handleLineHeightInputFocus);
402
+ lhInput.addEventListener('blur', handleLineHeightInputBlur);
289
403
  }
290
404
  const handleKeyDown = (e) => {
291
405
  if (selectedFieldId) {
@@ -335,30 +449,73 @@ const IDCardDesigner = ({ initialTemplate, onSave, onCancel, className = '', sty
335
449
  }
336
450
  };
337
451
  document.addEventListener('keydown', handleKeyDown);
338
- return () => document.removeEventListener('keydown', handleKeyDown);
452
+ return () => {
453
+ document.removeEventListener('keydown', handleKeyDown);
454
+ if (lhInput) {
455
+ lhInput.removeEventListener('focus', handleLineHeightInputFocus);
456
+ lhInput.removeEventListener('blur', handleLineHeightInputBlur);
457
+ }
458
+ };
339
459
  }, [selectedFieldId, handleDeleteField, template.fields, template.cardSize, updateField]);
340
- 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
460
+ 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", { type: "button", onClick: onCancel, className: "btn btn-secondary", children: "Cancel" })), jsx("button", { type: "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", { type: "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", { type: "button", className: `btn ${template.orientation === 'landscape' ? 'active' : ''}`, onClick: () => handleOrientationChange('landscape'), children: "Landscape" }), jsx("button", { type: "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", { type: "button", className: `btn ${template.sides === 'single' ? 'active' : ''}`, onClick: () => handleSidesChange('single'), children: "Single" }), jsx("button", { type: "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", { type: "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", { type: "button", className: `tab ${activeSide === 'front' ? 'active' : ''}`, onClick: () => setActiveSide('front'), children: "Front" }), template.sides === 'double' && (jsx("button", { type: "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
341
461
  .filter((f) => f.side === activeSide)
342
- .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) => {
462
+ .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", { type: "button", className: "delete-btn", onClick: (e) => {
343
463
  e.stopPropagation();
344
464
  removeField(field.id);
345
465
  if (field.id === selectedFieldId)
346
466
  setSelectedFieldId(null);
347
- }, 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 || '12px', 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 Text Transform" }), jsxs("select", { value: selectedField.style?.labelTextTransform || 'none', onChange: (e) => handleFieldPropertyUpdate('style.labelTextTransform', 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: "Label Color" }), jsx("input", { type: "color", value: selectedField.style?.labelColor || '#666666', onChange: (e) => handleFieldPropertyUpdate('style.labelColor', e.target.value) })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Label Text Align" }), jsxs("select", { value: selectedField.style?.labelTextAlign || 'left', onChange: (e) => handleFieldPropertyUpdate('style.labelTextAlign', e.target.value), children: [jsx("option", { value: "left", children: "Left" }), jsx("option", { value: "center", children: "Center" }), jsx("option", { value: "right", children: "Right" })] })] })] }), 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: "Text Font Size" }), jsx("input", { type: "text", value: selectedField.style?.textFontSize || selectedField.style?.fontSize || '12px', onChange: (e) => handleFieldPropertyUpdate('style.textFontSize', e.target.value) })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Text Font Weight" }), jsxs("select", { value: selectedField.style?.textFontWeight || selectedField.style?.fontWeight || 'normal', onChange: (e) => handleFieldPropertyUpdate('style.textFontWeight', 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?.textColor || selectedField.style?.color || '#000000', onChange: (e) => handleFieldPropertyUpdate('style.textColor', e.target.value) })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Text Align" }), jsxs("select", { value: selectedField.style?.textTextAlign || selectedField.style?.textAlign || 'left', onChange: (e) => handleFieldPropertyUpdate('style.textTextAlign', 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: "Line Height" }), jsx("input", { id: "line-height-input", type: "number", min: 0.1, step: 0.1, value: selectedField.style?.lineHeight ? Number(selectedField.style.lineHeight) : 1, onChange: (e) => handleFieldPropertyUpdate('style.lineHeight', e.target.value), style: { width: 80 }, onFocus: () => setLineHeightInputFocused(true), onBlur: () => setLineHeightInputFocused(false) })] }), 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: "Text Vertical Align" }), jsxs("select", { value: selectedField.style?.textVerticalAlign || 'top', onChange: (e) => handleFieldPropertyUpdate('style.textVerticalAlign', e.target.value), children: [jsx("option", { value: "top", children: "Top" }), jsx("option", { value: "middle", children: "Middle" }), jsx("option", { value: "bottom", children: "Bottom" })] })] })] }), jsxs("div", { style: { border: '1px solid #eee', borderRadius: 4, marginBottom: 12, padding: 8 }, children: [jsx("strong", { style: { fontSize: '1em', display: 'block', marginBottom: 6 }, children: "Container Style" }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Vertical Align (Container)" }), 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
348
- .filter(f => f.type !== 'qrcode' && f.type !== 'label')
467
+ }, 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: selectedField.type === 'table' ? 'Data Source Key' : 'Field Key' }), jsx("input", { type: "text", value: selectedField.fieldKey, onChange: (e) => handleFieldPropertyUpdate('fieldKey', e.target.value) })] }), selectedField.type !== 'label' && selectedField.type !== 'table' && (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 === 'table' && (() => {
468
+ const table = normalizeTableConfig(selectedField.table);
469
+ const sampleRows = coerceTableRows(sampleData[selectedField.fieldKey]);
470
+ const editableRows = Array.from({ length: Math.max(table.rowCount, sampleRows.length) }, (_, index) => sampleRows[index] || {});
471
+ return (jsxs(Fragment, { children: [jsxs("div", { className: "table-editor-section", children: [jsx("strong", { children: "Table Layout" }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Body Rows" }), jsx("input", { type: "number", min: 1, value: table.rowCount, onChange: (e) => handleTableConfigUpdate({
472
+ rowCount: Math.max(1, Number(e.target.value) || 1),
473
+ }) })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Columns" }), jsx("input", { type: "number", min: 1, value: table.columns.length, onChange: (e) => handleTableColumnCountUpdate(Number(e.target.value) || 1) })] }), jsxs("div", { className: "property-group checkbox-group", children: [jsx("input", { id: "table-show-header", type: "checkbox", checked: table.showHeader !== false, onChange: (e) => handleTableConfigUpdate({ showHeader: e.target.checked }) }), jsx("label", { htmlFor: "table-show-header", children: "Show Header" })] }), jsxs("div", { className: "property-group checkbox-group", children: [jsx("input", { id: "table-zebra-rows", type: "checkbox", checked: Boolean(table.zebraRows), onChange: (e) => handleTableConfigUpdate({ zebraRows: e.target.checked }) }), jsx("label", { htmlFor: "table-zebra-rows", children: "Zebra Rows" })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Row Height" }), jsx("input", { type: "number", min: 1, placeholder: "Auto", value: table.rowHeight || '', onChange: (e) => handleTableConfigUpdate({
474
+ rowHeight: e.target.value ? Math.max(1, Number(e.target.value)) : undefined,
475
+ }) })] })] }), jsxs("div", { className: "table-editor-section", children: [jsx("strong", { children: "Columns" }), table.columns.map((column, index) => (jsxs("div", { className: "table-column-editor", children: [jsxs("div", { className: "property-group", children: [jsxs("label", { children: ["Header ", index + 1] }), jsx("input", { type: "text", value: column.header, onChange: (e) => handleTableColumnUpdate(index, { header: e.target.value }) })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Field Key" }), jsx("input", { type: "text", value: column.fieldKey, onChange: (e) => {
476
+ const nextKey = e.target.value;
477
+ const previousKey = column.fieldKey;
478
+ handleTableColumnUpdate(index, { fieldKey: nextKey });
479
+ if (nextKey && nextKey !== previousKey) {
480
+ setSampleData((prev) => {
481
+ const rows = coerceTableRows(prev[selectedField.fieldKey]);
482
+ return {
483
+ ...prev,
484
+ [selectedField.fieldKey]: rows.map((row) => {
485
+ if (row[nextKey] !== undefined)
486
+ return row;
487
+ return {
488
+ ...row,
489
+ [nextKey]: row[previousKey],
490
+ };
491
+ }),
492
+ };
493
+ });
494
+ }
495
+ } })] }), jsxs("div", { className: "table-column-inline", children: [jsxs("div", { className: "property-group", children: [jsx("label", { children: "Width" }), jsx("input", { type: "number", min: 0.1, step: 0.1, value: column.width || 1, onChange: (e) => handleTableColumnUpdate(index, {
496
+ width: Math.max(0.1, Number(e.target.value) || 1),
497
+ }) })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Align" }), jsxs("select", { value: column.align || 'left', onChange: (e) => handleTableColumnUpdate(index, {
498
+ align: e.target.value,
499
+ }), children: [jsx("option", { value: "left", children: "Left" }), jsx("option", { value: "center", children: "Center" }), jsx("option", { value: "right", children: "Right" })] })] })] })] }, column.id)))] }), jsxs("div", { className: "table-editor-section", children: [jsx("strong", { children: "Table Style" }), jsxs("div", { className: "table-column-inline", children: [jsxs("div", { className: "property-group", children: [jsx("label", { children: "Border Width" }), jsx("input", { type: "number", min: 0, value: table.style?.borderWidth ?? 1, onChange: (e) => handleTableStyleUpdate('borderWidth', Math.max(0, Number(e.target.value) || 0)) })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Cell Padding" }), jsx("input", { type: "text", value: table.style?.cellPadding || '2px 4px', onChange: (e) => handleTableStyleUpdate('cellPadding', e.target.value) })] })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Border Color" }), jsx("input", { type: "color", value: table.style?.borderColor || '#000000', onChange: (e) => handleTableStyleUpdate('borderColor', e.target.value) })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Header Background" }), jsx("input", { type: "color", value: table.style?.headerBackgroundColor || '#f0f0f0', onChange: (e) => handleTableStyleUpdate('headerBackgroundColor', e.target.value) })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Header Text Color" }), jsx("input", { type: "color", value: table.style?.headerTextColor || '#000000', onChange: (e) => handleTableStyleUpdate('headerTextColor', e.target.value) })] }), jsxs("div", { className: "table-column-inline", children: [jsxs("div", { className: "property-group", children: [jsx("label", { children: "Header Font Size" }), jsx("input", { type: "text", value: table.style?.headerFontSize || '10px', onChange: (e) => handleTableStyleUpdate('headerFontSize', e.target.value) })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Header Weight" }), jsxs("select", { value: table.style?.headerFontWeight || 'bold', onChange: (e) => handleTableStyleUpdate('headerFontWeight', e.target.value), children: [jsx("option", { value: "normal", children: "Normal" }), jsx("option", { value: "bold", children: "Bold" }), jsx("option", { value: "400", children: "400" }), jsx("option", { value: "500", children: "500" }), jsx("option", { value: "600", children: "600" }), jsx("option", { value: "700", children: "700" })] })] })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Body Text Color" }), jsx("input", { type: "color", value: table.style?.bodyTextColor || '#000000', onChange: (e) => handleTableStyleUpdate('bodyTextColor', e.target.value) })] }), jsxs("div", { className: "table-column-inline", children: [jsxs("div", { className: "property-group", children: [jsx("label", { children: "Body Font Size" }), jsx("input", { type: "text", value: table.style?.bodyFontSize || '10px', onChange: (e) => handleTableStyleUpdate('bodyFontSize', e.target.value) })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Body Weight" }), jsxs("select", { value: table.style?.bodyFontWeight || 'normal', onChange: (e) => handleTableStyleUpdate('bodyFontWeight', e.target.value), children: [jsx("option", { value: "normal", children: "Normal" }), jsx("option", { value: "bold", children: "Bold" }), jsx("option", { value: "400", children: "400" }), jsx("option", { value: "500", children: "500" }), jsx("option", { value: "600", children: "600" }), jsx("option", { value: "700", children: "700" })] })] })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Zebra Background" }), jsx("input", { type: "color", value: table.style?.zebraBackgroundColor || '#f7f7f7', onChange: (e) => handleTableStyleUpdate('zebraBackgroundColor', e.target.value) })] })] }), jsxs("div", { className: "table-editor-section", children: [jsx("strong", { children: "Sample Rows" }), jsxs("small", { className: "hint", children: ["Preview data for ", selectedField.fieldKey] }), jsxs("div", { className: "table-sample-grid", style: {
500
+ gridTemplateColumns: `repeat(${table.columns.length}, minmax(90px, 1fr))`,
501
+ }, children: [table.columns.map((column) => (jsx("div", { className: "table-sample-header", children: column.header }, `sample-header-${column.id}`))), editableRows.map((row, rowIndex) => table.columns.map((column) => (jsx("input", { type: "text", value: String(row[column.fieldKey] ?? ''), onChange: (e) => handleTableSampleCellUpdate(selectedField, table, rowIndex, column.fieldKey, e.target.value) }, `sample-${rowIndex}-${column.id}`))))] }), jsx("button", { className: "btn btn-outline btn-sm", type: "button", onClick: () => handleAddTableSampleRow(selectedField), children: "+ Add Sample Row" })] })] }));
502
+ })(), selectedField.type === 'text' && (jsxs(Fragment, { children: [jsx("button", { type: "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 || '12px', 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 Text Transform" }), jsxs("select", { value: selectedField.style?.labelTextTransform || 'none', onChange: (e) => handleFieldPropertyUpdate('style.labelTextTransform', 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: "Label Color" }), jsx("input", { type: "color", value: selectedField.style?.labelColor || '#666666', onChange: (e) => handleFieldPropertyUpdate('style.labelColor', e.target.value) })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Label Text Align" }), jsxs("select", { value: selectedField.style?.labelTextAlign || 'left', onChange: (e) => handleFieldPropertyUpdate('style.labelTextAlign', e.target.value), children: [jsx("option", { value: "left", children: "Left" }), jsx("option", { value: "center", children: "Center" }), jsx("option", { value: "right", children: "Right" })] })] })] }), 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: "Text Font Size" }), jsx("input", { type: "text", value: selectedField.style?.textFontSize || selectedField.style?.fontSize || '12px', onChange: (e) => handleFieldPropertyUpdate('style.textFontSize', e.target.value) })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Text Font Weight" }), jsxs("select", { value: selectedField.style?.textFontWeight || selectedField.style?.fontWeight || 'normal', onChange: (e) => handleFieldPropertyUpdate('style.textFontWeight', 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?.textColor || selectedField.style?.color || '#000000', onChange: (e) => handleFieldPropertyUpdate('style.textColor', e.target.value) })] }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Text Align" }), jsxs("select", { value: selectedField.style?.textTextAlign || selectedField.style?.textAlign || 'left', onChange: (e) => handleFieldPropertyUpdate('style.textTextAlign', 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: "Line Height" }), jsx("input", { id: "line-height-input", type: "number", min: 0.1, step: 0.1, value: selectedField.style?.lineHeight ? Number(selectedField.style.lineHeight) : 1, onChange: (e) => handleFieldPropertyUpdate('style.lineHeight', e.target.value), style: { width: 80 }, onFocus: () => setLineHeightInputFocused(true), onBlur: () => setLineHeightInputFocused(false) })] }), 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: "Text Vertical Align" }), jsxs("select", { value: selectedField.style?.textVerticalAlign || 'top', onChange: (e) => handleFieldPropertyUpdate('style.textVerticalAlign', e.target.value), children: [jsx("option", { value: "top", children: "Top" }), jsx("option", { value: "middle", children: "Middle" }), jsx("option", { value: "bottom", children: "Bottom" })] })] })] }), jsxs("div", { style: { border: '1px solid #eee', borderRadius: 4, marginBottom: 12, padding: 8 }, children: [jsx("strong", { style: { fontSize: '1em', display: 'block', marginBottom: 6 }, children: "Container Style" }), jsxs("div", { className: "property-group", children: [jsx("label", { children: "Vertical Align (Container)" }), 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
503
+ .filter(f => f.type !== 'qrcode' && f.type !== 'label' && f.type !== 'table')
349
504
  .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) => {
350
505
  const currentFields = selectedField.qrFields || [];
351
506
  const newFields = e.target.checked
352
507
  ? [...currentFields, field.fieldKey]
353
508
  : currentFields.filter(f => f !== field.fieldKey);
354
509
  handleFieldPropertyUpdate('qrFields', newFields);
355
- }, style: { marginRight: '6px' } }), field.label || field.fieldKey] }, field.fieldKey))) })] })), 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" })] })] }), selectedField.type === 'label' && (jsxs("div", { className: "property-group", children: [jsx("label", { children: "Text Transform" }), jsxs("select", { value: selectedField.style?.labelTextTransform || 'none', onChange: (e) => handleFieldPropertyUpdate('style.labelTextTransform', 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" })] })] }))] }), selectedField.type === 'qrcode' ? (jsxs("div", { className: "property-group", children: [jsx("label", { children: "Background Color" }), jsx("div", { style: {
510
+ }, style: { marginRight: '6px' } }), field.label || field.fieldKey] }, field.fieldKey))) })] })), selectedField.type === 'image' && (jsxs("div", { className: "property-group", children: [jsx("label", { htmlFor: `photo-radius-input-${selectedField.id}`, children: "Photo Radius" }), jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 8 }, children: [jsx("input", { type: "range", min: "0", max: "200", value: getPhotoRadiusSliderValue(selectedField.style?.borderRadius), onChange: (e) => handleFieldPropertyUpdate('style.borderRadius', `${e.target.value}px`), "aria-label": "Photo radius slider", style: { flex: 1 } }), jsx("input", { id: `photo-radius-input-${selectedField.id}`, type: "text", value: selectedField.style?.borderRadius || '0px', onChange: (e) => handleFieldPropertyUpdate('style.borderRadius', e.target.value.trim() || '0px'), style: { width: 72 } })] }), jsxs("div", { style: { display: 'flex', gap: 8, marginTop: 8 }, children: [jsx("button", { type: "button", className: "btn btn-outline btn-sm", onClick: () => handleFieldPropertyUpdate('style.borderRadius', '0px'), children: "Square" }), jsx("button", { type: "button", className: "btn btn-outline btn-sm", onClick: () => handleFieldPropertyUpdate('style.borderRadius', '50%'), children: "Circle" })] })] })), selectedField.type !== 'text' && selectedField.type !== 'table' && 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" })] })] }), selectedField.type === 'label' && (jsxs("div", { className: "property-group", children: [jsx("label", { children: "Text Transform" }), jsxs("select", { value: selectedField.style?.labelTextTransform || 'none', onChange: (e) => handleFieldPropertyUpdate('style.labelTextTransform', 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" })] })] }))] }), selectedField.type === 'qrcode' ? (jsxs("div", { className: "property-group", children: [jsx("label", { children: "Background Color" }), jsx("div", { style: {
356
511
  padding: '8px 10px',
357
512
  border: '1px solid #ddd',
358
513
  borderRadius: '4px',
359
514
  backgroundColor: '#f8f8f8',
360
515
  color: '#666',
361
- }, children: "Transparent" })] })) : (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))) })] }))] })] })] }));
516
+ }, children: "Transparent" })] })) : (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", { type: "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)
517
+ .filter(([, value]) => !Array.isArray(value))
518
+ .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", { type: "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))) })] }))] })] })] }));
362
519
  };
363
520
 
364
521
  export { IDCardDesigner };