email-builder-pro 1.0.0
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.
- package/INTEGRATION.md +250 -0
- package/PUBLISH.md +116 -0
- package/README.md +158 -0
- package/app/globals.css +46 -0
- package/components/Canvas.tsx +78 -0
- package/components/ComponentPalette.tsx +297 -0
- package/components/ComponentRenderer.tsx +496 -0
- package/components/DraggableComponent.tsx +84 -0
- package/components/DroppableComponent.tsx +80 -0
- package/components/EmailBuilder.tsx +59 -0
- package/components/ImageUpload.tsx +186 -0
- package/components/NumberInput.tsx +73 -0
- package/components/PreviewPanel.tsx +125 -0
- package/components/PropertiesPanel.tsx +1386 -0
- package/components/TemplateManager.tsx +104 -0
- package/components/Toolbar.tsx +242 -0
- package/lib/store.ts +198 -0
- package/package.json +76 -0
- package/postcss.config.js +8 -0
- package/src/index.tsx +42 -0
- package/tailwind.config.js +29 -0
- package/types/email.ts +22 -0
|
@@ -0,0 +1,1386 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEmailBuilder } from '@/lib/store';
|
|
4
|
+
import { Trash2 } from 'lucide-react';
|
|
5
|
+
import NumberInput from './NumberInput';
|
|
6
|
+
import ImageUpload from './ImageUpload';
|
|
7
|
+
|
|
8
|
+
export default function PropertiesPanel() {
|
|
9
|
+
const {
|
|
10
|
+
components,
|
|
11
|
+
selectedComponent,
|
|
12
|
+
updateComponent,
|
|
13
|
+
removeComponent,
|
|
14
|
+
selectComponent,
|
|
15
|
+
} = useEmailBuilder();
|
|
16
|
+
|
|
17
|
+
const component = components.find((c) => c.id === selectedComponent);
|
|
18
|
+
|
|
19
|
+
if (!component) {
|
|
20
|
+
return (
|
|
21
|
+
<div className="p-4">
|
|
22
|
+
<h2 className="text-sm font-semibold text-gray-700 mb-4">Properties</h2>
|
|
23
|
+
<p className="text-sm text-gray-400">Select a component to edit its properties</p>
|
|
24
|
+
</div>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const handlePropChange = (key: string, value: any) => {
|
|
29
|
+
updateComponent(component.id, {
|
|
30
|
+
props: {
|
|
31
|
+
...component.props,
|
|
32
|
+
[key]: value,
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// Common styling properties component
|
|
38
|
+
const CommonStyles = () => (
|
|
39
|
+
<>
|
|
40
|
+
<div className="mb-4 pt-4 border-t border-gray-200">
|
|
41
|
+
<h3 className="text-xs font-semibold text-gray-600 mb-3 uppercase">Spacing</h3>
|
|
42
|
+
<div className="grid grid-cols-2 gap-2">
|
|
43
|
+
<NumberInput
|
|
44
|
+
label="Margin Top"
|
|
45
|
+
value={component.props.marginTop || 0}
|
|
46
|
+
onChange={(val) => handlePropChange('marginTop', val)}
|
|
47
|
+
min={0}
|
|
48
|
+
max={100}
|
|
49
|
+
unit="px"
|
|
50
|
+
/>
|
|
51
|
+
<NumberInput
|
|
52
|
+
label="Margin Bottom"
|
|
53
|
+
value={component.props.marginBottom || 0}
|
|
54
|
+
onChange={(val) => handlePropChange('marginBottom', val)}
|
|
55
|
+
min={0}
|
|
56
|
+
max={100}
|
|
57
|
+
unit="px"
|
|
58
|
+
/>
|
|
59
|
+
<NumberInput
|
|
60
|
+
label="Margin Left"
|
|
61
|
+
value={component.props.marginLeft || 0}
|
|
62
|
+
onChange={(val) => handlePropChange('marginLeft', val)}
|
|
63
|
+
min={0}
|
|
64
|
+
max={100}
|
|
65
|
+
unit="px"
|
|
66
|
+
/>
|
|
67
|
+
<NumberInput
|
|
68
|
+
label="Margin Right"
|
|
69
|
+
value={component.props.marginRight || 0}
|
|
70
|
+
onChange={(val) => handlePropChange('marginRight', val)}
|
|
71
|
+
min={0}
|
|
72
|
+
max={100}
|
|
73
|
+
unit="px"
|
|
74
|
+
/>
|
|
75
|
+
</div>
|
|
76
|
+
<NumberInput
|
|
77
|
+
label="Margin (All)"
|
|
78
|
+
value={component.props.margin || 0}
|
|
79
|
+
onChange={(val) => handlePropChange('margin', val)}
|
|
80
|
+
min={0}
|
|
81
|
+
max={100}
|
|
82
|
+
unit="px"
|
|
83
|
+
/>
|
|
84
|
+
</div>
|
|
85
|
+
</>
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const renderPropertyEditor = () => {
|
|
89
|
+
switch (component.type) {
|
|
90
|
+
case 'text':
|
|
91
|
+
return (
|
|
92
|
+
<>
|
|
93
|
+
<div className="mb-4">
|
|
94
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
95
|
+
Text Content
|
|
96
|
+
</label>
|
|
97
|
+
<textarea
|
|
98
|
+
value={component.props.text || ''}
|
|
99
|
+
onChange={(e) => handlePropChange('text', e.target.value)}
|
|
100
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm"
|
|
101
|
+
rows={4}
|
|
102
|
+
/>
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
<div className="mb-4 pt-4 border-t border-gray-200">
|
|
106
|
+
<h3 className="text-xs font-semibold text-gray-600 mb-3 uppercase">Typography</h3>
|
|
107
|
+
<NumberInput
|
|
108
|
+
label="Font Size"
|
|
109
|
+
value={component.props.fontSize || 16}
|
|
110
|
+
onChange={(val) => handlePropChange('fontSize', val)}
|
|
111
|
+
min={8}
|
|
112
|
+
max={72}
|
|
113
|
+
unit="px"
|
|
114
|
+
/>
|
|
115
|
+
<div className="mb-4">
|
|
116
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
117
|
+
Font Weight
|
|
118
|
+
</label>
|
|
119
|
+
<select
|
|
120
|
+
value={component.props.fontWeight || 'normal'}
|
|
121
|
+
onChange={(e) => handlePropChange('fontWeight', e.target.value)}
|
|
122
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm"
|
|
123
|
+
>
|
|
124
|
+
<option value="normal">Normal</option>
|
|
125
|
+
<option value="bold">Bold</option>
|
|
126
|
+
<option value="300">Light</option>
|
|
127
|
+
<option value="400">Regular</option>
|
|
128
|
+
<option value="500">Medium</option>
|
|
129
|
+
<option value="600">Semi Bold</option>
|
|
130
|
+
<option value="700">Bold</option>
|
|
131
|
+
</select>
|
|
132
|
+
</div>
|
|
133
|
+
<div className="mb-4">
|
|
134
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
135
|
+
Font Family
|
|
136
|
+
</label>
|
|
137
|
+
<select
|
|
138
|
+
value={component.props.fontFamily || 'Arial'}
|
|
139
|
+
onChange={(e) => handlePropChange('fontFamily', e.target.value)}
|
|
140
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm"
|
|
141
|
+
>
|
|
142
|
+
<option value="Arial">Arial</option>
|
|
143
|
+
<option value="Helvetica">Helvetica</option>
|
|
144
|
+
<option value="Georgia">Georgia</option>
|
|
145
|
+
<option value="Times New Roman">Times New Roman</option>
|
|
146
|
+
<option value="Courier New">Courier New</option>
|
|
147
|
+
<option value="Verdana">Verdana</option>
|
|
148
|
+
</select>
|
|
149
|
+
</div>
|
|
150
|
+
<div className="mb-4">
|
|
151
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
152
|
+
Line Height
|
|
153
|
+
</label>
|
|
154
|
+
<input
|
|
155
|
+
type="number"
|
|
156
|
+
step="0.1"
|
|
157
|
+
value={component.props.lineHeight || 1.5}
|
|
158
|
+
onChange={(e) => handlePropChange('lineHeight', parseFloat(e.target.value))}
|
|
159
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm"
|
|
160
|
+
/>
|
|
161
|
+
</div>
|
|
162
|
+
</div>
|
|
163
|
+
|
|
164
|
+
<div className="mb-4 pt-4 border-t border-gray-200">
|
|
165
|
+
<h3 className="text-xs font-semibold text-gray-600 mb-3 uppercase">Colors</h3>
|
|
166
|
+
<div className="mb-4">
|
|
167
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
168
|
+
Text Color
|
|
169
|
+
</label>
|
|
170
|
+
<input
|
|
171
|
+
type="color"
|
|
172
|
+
value={component.props.color || '#000000'}
|
|
173
|
+
onChange={(e) => handlePropChange('color', e.target.value)}
|
|
174
|
+
className="w-full h-10 border border-gray-300 rounded-md"
|
|
175
|
+
/>
|
|
176
|
+
</div>
|
|
177
|
+
<div className="mb-4">
|
|
178
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
179
|
+
Background Color
|
|
180
|
+
</label>
|
|
181
|
+
<input
|
|
182
|
+
type="color"
|
|
183
|
+
value={component.props.backgroundColor || 'transparent'}
|
|
184
|
+
onChange={(e) => handlePropChange('backgroundColor', e.target.value)}
|
|
185
|
+
className="w-full h-10 border border-gray-300 rounded-md"
|
|
186
|
+
/>
|
|
187
|
+
</div>
|
|
188
|
+
</div>
|
|
189
|
+
|
|
190
|
+
<div className="mb-4 pt-4 border-t border-gray-200">
|
|
191
|
+
<h3 className="text-xs font-semibold text-gray-600 mb-3 uppercase">Alignment</h3>
|
|
192
|
+
<div className="mb-4">
|
|
193
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
194
|
+
Text Align
|
|
195
|
+
</label>
|
|
196
|
+
<select
|
|
197
|
+
value={component.props.textAlign || 'left'}
|
|
198
|
+
onChange={(e) => handlePropChange('textAlign', e.target.value)}
|
|
199
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm"
|
|
200
|
+
>
|
|
201
|
+
<option value="left">Left</option>
|
|
202
|
+
<option value="center">Center</option>
|
|
203
|
+
<option value="right">Right</option>
|
|
204
|
+
<option value="justify">Justify</option>
|
|
205
|
+
</select>
|
|
206
|
+
</div>
|
|
207
|
+
</div>
|
|
208
|
+
|
|
209
|
+
<CommonStyles />
|
|
210
|
+
</>
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
case 'heading':
|
|
214
|
+
return (
|
|
215
|
+
<>
|
|
216
|
+
<div className="mb-4">
|
|
217
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
218
|
+
Heading Text
|
|
219
|
+
</label>
|
|
220
|
+
<input
|
|
221
|
+
type="text"
|
|
222
|
+
value={component.props.text || ''}
|
|
223
|
+
onChange={(e) => handlePropChange('text', e.target.value)}
|
|
224
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm"
|
|
225
|
+
/>
|
|
226
|
+
</div>
|
|
227
|
+
<div className="mb-4">
|
|
228
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
229
|
+
Heading Level
|
|
230
|
+
</label>
|
|
231
|
+
<select
|
|
232
|
+
value={component.props.level || 1}
|
|
233
|
+
onChange={(e) => handlePropChange('level', parseInt(e.target.value))}
|
|
234
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm"
|
|
235
|
+
>
|
|
236
|
+
<option value={1}>H1</option>
|
|
237
|
+
<option value={2}>H2</option>
|
|
238
|
+
<option value={3}>H3</option>
|
|
239
|
+
<option value={4}>H4</option>
|
|
240
|
+
<option value={5}>H5</option>
|
|
241
|
+
<option value={6}>H6</option>
|
|
242
|
+
</select>
|
|
243
|
+
</div>
|
|
244
|
+
|
|
245
|
+
<div className="mb-4 pt-4 border-t border-gray-200">
|
|
246
|
+
<h3 className="text-xs font-semibold text-gray-600 mb-3 uppercase">Typography</h3>
|
|
247
|
+
<NumberInput
|
|
248
|
+
label="Font Size"
|
|
249
|
+
value={component.props.fontSize || 24}
|
|
250
|
+
onChange={(val) => handlePropChange('fontSize', val)}
|
|
251
|
+
min={12}
|
|
252
|
+
max={72}
|
|
253
|
+
unit="px"
|
|
254
|
+
/>
|
|
255
|
+
<div className="mb-4">
|
|
256
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
257
|
+
Font Weight
|
|
258
|
+
</label>
|
|
259
|
+
<select
|
|
260
|
+
value={component.props.fontWeight || 'bold'}
|
|
261
|
+
onChange={(e) => handlePropChange('fontWeight', e.target.value)}
|
|
262
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm"
|
|
263
|
+
>
|
|
264
|
+
<option value="normal">Normal</option>
|
|
265
|
+
<option value="bold">Bold</option>
|
|
266
|
+
<option value="600">Semi Bold</option>
|
|
267
|
+
<option value="700">Bold</option>
|
|
268
|
+
<option value="800">Extra Bold</option>
|
|
269
|
+
</select>
|
|
270
|
+
</div>
|
|
271
|
+
</div>
|
|
272
|
+
|
|
273
|
+
<div className="mb-4 pt-4 border-t border-gray-200">
|
|
274
|
+
<h3 className="text-xs font-semibold text-gray-600 mb-3 uppercase">Colors</h3>
|
|
275
|
+
<div className="mb-4">
|
|
276
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
277
|
+
Text Color
|
|
278
|
+
</label>
|
|
279
|
+
<input
|
|
280
|
+
type="color"
|
|
281
|
+
value={component.props.color || '#000000'}
|
|
282
|
+
onChange={(e) => handlePropChange('color', e.target.value)}
|
|
283
|
+
className="w-full h-10 border border-gray-300 rounded-md"
|
|
284
|
+
/>
|
|
285
|
+
</div>
|
|
286
|
+
</div>
|
|
287
|
+
|
|
288
|
+
<div className="mb-4 pt-4 border-t border-gray-200">
|
|
289
|
+
<h3 className="text-xs font-semibold text-gray-600 mb-3 uppercase">Alignment</h3>
|
|
290
|
+
<div className="mb-4">
|
|
291
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
292
|
+
Text Align
|
|
293
|
+
</label>
|
|
294
|
+
<select
|
|
295
|
+
value={component.props.textAlign || 'left'}
|
|
296
|
+
onChange={(e) => handlePropChange('textAlign', e.target.value)}
|
|
297
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm"
|
|
298
|
+
>
|
|
299
|
+
<option value="left">Left</option>
|
|
300
|
+
<option value="center">Center</option>
|
|
301
|
+
<option value="right">Right</option>
|
|
302
|
+
</select>
|
|
303
|
+
</div>
|
|
304
|
+
</div>
|
|
305
|
+
|
|
306
|
+
<CommonStyles />
|
|
307
|
+
</>
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
case 'button':
|
|
311
|
+
return (
|
|
312
|
+
<>
|
|
313
|
+
<div className="mb-4">
|
|
314
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
315
|
+
Button Text
|
|
316
|
+
</label>
|
|
317
|
+
<input
|
|
318
|
+
type="text"
|
|
319
|
+
value={component.props.text || ''}
|
|
320
|
+
onChange={(e) => handlePropChange('text', e.target.value)}
|
|
321
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm"
|
|
322
|
+
/>
|
|
323
|
+
</div>
|
|
324
|
+
<div className="mb-4">
|
|
325
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
326
|
+
Link URL (href)
|
|
327
|
+
</label>
|
|
328
|
+
<input
|
|
329
|
+
type="text"
|
|
330
|
+
value={component.props.href || '#'}
|
|
331
|
+
onChange={(e) => handlePropChange('href', e.target.value)}
|
|
332
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm"
|
|
333
|
+
placeholder="https://example.com"
|
|
334
|
+
/>
|
|
335
|
+
</div>
|
|
336
|
+
|
|
337
|
+
<div className="mb-4 pt-4 border-t border-gray-200">
|
|
338
|
+
<h3 className="text-xs font-semibold text-gray-600 mb-3 uppercase">Colors</h3>
|
|
339
|
+
<div className="mb-4">
|
|
340
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
341
|
+
Background Color
|
|
342
|
+
</label>
|
|
343
|
+
<input
|
|
344
|
+
type="color"
|
|
345
|
+
value={component.props.backgroundColor || '#007bff'}
|
|
346
|
+
onChange={(e) => handlePropChange('backgroundColor', e.target.value)}
|
|
347
|
+
className="w-full h-10 border border-gray-300 rounded-md"
|
|
348
|
+
/>
|
|
349
|
+
</div>
|
|
350
|
+
<div className="mb-4">
|
|
351
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
352
|
+
Text Color
|
|
353
|
+
</label>
|
|
354
|
+
<input
|
|
355
|
+
type="color"
|
|
356
|
+
value={component.props.color || '#ffffff'}
|
|
357
|
+
onChange={(e) => handlePropChange('color', e.target.value)}
|
|
358
|
+
className="w-full h-10 border border-gray-300 rounded-md"
|
|
359
|
+
/>
|
|
360
|
+
</div>
|
|
361
|
+
</div>
|
|
362
|
+
|
|
363
|
+
<div className="mb-4 pt-4 border-t border-gray-200">
|
|
364
|
+
<h3 className="text-xs font-semibold text-gray-600 mb-3 uppercase">Typography</h3>
|
|
365
|
+
<NumberInput
|
|
366
|
+
label="Font Size"
|
|
367
|
+
value={component.props.fontSize || 14}
|
|
368
|
+
onChange={(val) => handlePropChange('fontSize', val)}
|
|
369
|
+
min={10}
|
|
370
|
+
max={24}
|
|
371
|
+
unit="px"
|
|
372
|
+
/>
|
|
373
|
+
<div className="mb-4">
|
|
374
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
375
|
+
Font Weight
|
|
376
|
+
</label>
|
|
377
|
+
<select
|
|
378
|
+
value={component.props.fontWeight || '600'}
|
|
379
|
+
onChange={(e) => handlePropChange('fontWeight', e.target.value)}
|
|
380
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm"
|
|
381
|
+
>
|
|
382
|
+
<option value="normal">Normal</option>
|
|
383
|
+
<option value="500">Medium</option>
|
|
384
|
+
<option value="600">Semi Bold</option>
|
|
385
|
+
<option value="700">Bold</option>
|
|
386
|
+
</select>
|
|
387
|
+
</div>
|
|
388
|
+
</div>
|
|
389
|
+
|
|
390
|
+
<div className="mb-4 pt-4 border-t border-gray-200">
|
|
391
|
+
<h3 className="text-xs font-semibold text-gray-600 mb-3 uppercase">Spacing & Size</h3>
|
|
392
|
+
<div className="grid grid-cols-2 gap-2 mb-4">
|
|
393
|
+
<NumberInput
|
|
394
|
+
label="Padding X"
|
|
395
|
+
value={component.props.paddingX || component.props.padding || 12}
|
|
396
|
+
onChange={(val) => handlePropChange('paddingX', val)}
|
|
397
|
+
min={0}
|
|
398
|
+
max={50}
|
|
399
|
+
unit="px"
|
|
400
|
+
/>
|
|
401
|
+
<NumberInput
|
|
402
|
+
label="Padding Y"
|
|
403
|
+
value={component.props.paddingY || component.props.padding || 12}
|
|
404
|
+
onChange={(val) => handlePropChange('paddingY', val)}
|
|
405
|
+
min={0}
|
|
406
|
+
max={50}
|
|
407
|
+
unit="px"
|
|
408
|
+
/>
|
|
409
|
+
</div>
|
|
410
|
+
<NumberInput
|
|
411
|
+
label="Border Radius"
|
|
412
|
+
value={component.props.borderRadius || 4}
|
|
413
|
+
onChange={(val) => handlePropChange('borderRadius', val)}
|
|
414
|
+
min={0}
|
|
415
|
+
max={50}
|
|
416
|
+
unit="px"
|
|
417
|
+
/>
|
|
418
|
+
</div>
|
|
419
|
+
|
|
420
|
+
<div className="mb-4 pt-4 border-t border-gray-200">
|
|
421
|
+
<h3 className="text-xs font-semibold text-gray-600 mb-3 uppercase">Border</h3>
|
|
422
|
+
<NumberInput
|
|
423
|
+
label="Border Width"
|
|
424
|
+
value={component.props.borderWidth || 0}
|
|
425
|
+
onChange={(val) => handlePropChange('borderWidth', val)}
|
|
426
|
+
min={0}
|
|
427
|
+
max={10}
|
|
428
|
+
unit="px"
|
|
429
|
+
/>
|
|
430
|
+
<div className="mb-4">
|
|
431
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
432
|
+
Border Color
|
|
433
|
+
</label>
|
|
434
|
+
<input
|
|
435
|
+
type="color"
|
|
436
|
+
value={component.props.borderColor || '#000000'}
|
|
437
|
+
onChange={(e) => handlePropChange('borderColor', e.target.value)}
|
|
438
|
+
className="w-full h-10 border border-gray-300 rounded-md"
|
|
439
|
+
/>
|
|
440
|
+
</div>
|
|
441
|
+
</div>
|
|
442
|
+
|
|
443
|
+
<CommonStyles />
|
|
444
|
+
</>
|
|
445
|
+
);
|
|
446
|
+
|
|
447
|
+
case 'image':
|
|
448
|
+
return (
|
|
449
|
+
<>
|
|
450
|
+
<ImageUpload
|
|
451
|
+
value={component.props.src || ''}
|
|
452
|
+
onChange={(url) => handlePropChange('src', url)}
|
|
453
|
+
onResize={(width, height) => {
|
|
454
|
+
handlePropChange('width', `${width}px`);
|
|
455
|
+
handlePropChange('height', `${height}px`);
|
|
456
|
+
}}
|
|
457
|
+
/>
|
|
458
|
+
<div className="mb-4">
|
|
459
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
460
|
+
Alt Text
|
|
461
|
+
</label>
|
|
462
|
+
<input
|
|
463
|
+
type="text"
|
|
464
|
+
value={component.props.alt || ''}
|
|
465
|
+
onChange={(e) => handlePropChange('alt', e.target.value)}
|
|
466
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm"
|
|
467
|
+
placeholder="Image description"
|
|
468
|
+
/>
|
|
469
|
+
</div>
|
|
470
|
+
|
|
471
|
+
<div className="mb-4 pt-4 border-t border-gray-200">
|
|
472
|
+
<h3 className="text-xs font-semibold text-gray-600 mb-3 uppercase">Size</h3>
|
|
473
|
+
<div className="mb-4">
|
|
474
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
475
|
+
Width
|
|
476
|
+
</label>
|
|
477
|
+
<input
|
|
478
|
+
type="text"
|
|
479
|
+
value={component.props.width || '100%'}
|
|
480
|
+
onChange={(e) => handlePropChange('width', e.target.value)}
|
|
481
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm"
|
|
482
|
+
placeholder="100% or 400px"
|
|
483
|
+
/>
|
|
484
|
+
</div>
|
|
485
|
+
<div className="mb-4">
|
|
486
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
487
|
+
Height
|
|
488
|
+
</label>
|
|
489
|
+
<input
|
|
490
|
+
type="text"
|
|
491
|
+
value={component.props.height || 'auto'}
|
|
492
|
+
onChange={(e) => handlePropChange('height', e.target.value)}
|
|
493
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm"
|
|
494
|
+
placeholder="auto or 300px"
|
|
495
|
+
/>
|
|
496
|
+
</div>
|
|
497
|
+
</div>
|
|
498
|
+
|
|
499
|
+
<div className="mb-4 pt-4 border-t border-gray-200">
|
|
500
|
+
<h3 className="text-xs font-semibold text-gray-600 mb-3 uppercase">Alignment</h3>
|
|
501
|
+
<div className="mb-4">
|
|
502
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
503
|
+
Align
|
|
504
|
+
</label>
|
|
505
|
+
<select
|
|
506
|
+
value={component.props.align || 'left'}
|
|
507
|
+
onChange={(e) => handlePropChange('align', e.target.value)}
|
|
508
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm"
|
|
509
|
+
>
|
|
510
|
+
<option value="left">Left</option>
|
|
511
|
+
<option value="center">Center</option>
|
|
512
|
+
<option value="right">Right</option>
|
|
513
|
+
</select>
|
|
514
|
+
</div>
|
|
515
|
+
</div>
|
|
516
|
+
|
|
517
|
+
<CommonStyles />
|
|
518
|
+
</>
|
|
519
|
+
);
|
|
520
|
+
|
|
521
|
+
case 'container':
|
|
522
|
+
return (
|
|
523
|
+
<>
|
|
524
|
+
<div className="mb-4 pt-4 border-t border-gray-200">
|
|
525
|
+
<h3 className="text-xs font-semibold text-gray-600 mb-3 uppercase">Background</h3>
|
|
526
|
+
<div className="mb-4">
|
|
527
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
528
|
+
Background Color
|
|
529
|
+
</label>
|
|
530
|
+
<input
|
|
531
|
+
type="color"
|
|
532
|
+
value={component.props.backgroundColor || '#ffffff'}
|
|
533
|
+
onChange={(e) => handlePropChange('backgroundColor', e.target.value)}
|
|
534
|
+
className="w-full h-10 border border-gray-300 rounded-md"
|
|
535
|
+
/>
|
|
536
|
+
</div>
|
|
537
|
+
</div>
|
|
538
|
+
|
|
539
|
+
<div className="mb-4 pt-4 border-t border-gray-200">
|
|
540
|
+
<h3 className="text-xs font-semibold text-gray-600 mb-3 uppercase">Spacing</h3>
|
|
541
|
+
<div className="grid grid-cols-2 gap-2 mb-4">
|
|
542
|
+
<NumberInput
|
|
543
|
+
label="Padding Top"
|
|
544
|
+
value={component.props.paddingTop || component.props.padding || 20}
|
|
545
|
+
onChange={(val) => handlePropChange('paddingTop', val)}
|
|
546
|
+
min={0}
|
|
547
|
+
max={100}
|
|
548
|
+
unit="px"
|
|
549
|
+
/>
|
|
550
|
+
<NumberInput
|
|
551
|
+
label="Padding Bottom"
|
|
552
|
+
value={component.props.paddingBottom || component.props.padding || 20}
|
|
553
|
+
onChange={(val) => handlePropChange('paddingBottom', val)}
|
|
554
|
+
min={0}
|
|
555
|
+
max={100}
|
|
556
|
+
unit="px"
|
|
557
|
+
/>
|
|
558
|
+
<NumberInput
|
|
559
|
+
label="Padding Left"
|
|
560
|
+
value={component.props.paddingLeft || component.props.padding || 20}
|
|
561
|
+
onChange={(val) => handlePropChange('paddingLeft', val)}
|
|
562
|
+
min={0}
|
|
563
|
+
max={100}
|
|
564
|
+
unit="px"
|
|
565
|
+
/>
|
|
566
|
+
<NumberInput
|
|
567
|
+
label="Padding Right"
|
|
568
|
+
value={component.props.paddingRight || component.props.padding || 20}
|
|
569
|
+
onChange={(val) => handlePropChange('paddingRight', val)}
|
|
570
|
+
min={0}
|
|
571
|
+
max={100}
|
|
572
|
+
unit="px"
|
|
573
|
+
/>
|
|
574
|
+
</div>
|
|
575
|
+
<NumberInput
|
|
576
|
+
label="Padding (All)"
|
|
577
|
+
value={component.props.padding || 20}
|
|
578
|
+
onChange={(val) => handlePropChange('padding', val)}
|
|
579
|
+
min={0}
|
|
580
|
+
max={100}
|
|
581
|
+
unit="px"
|
|
582
|
+
/>
|
|
583
|
+
</div>
|
|
584
|
+
|
|
585
|
+
<div className="mb-4 pt-4 border-t border-gray-200">
|
|
586
|
+
<h3 className="text-xs font-semibold text-gray-600 mb-3 uppercase">Border</h3>
|
|
587
|
+
<NumberInput
|
|
588
|
+
label="Border Width"
|
|
589
|
+
value={component.props.borderWidth || 0}
|
|
590
|
+
onChange={(val) => handlePropChange('borderWidth', val)}
|
|
591
|
+
min={0}
|
|
592
|
+
max={10}
|
|
593
|
+
unit="px"
|
|
594
|
+
/>
|
|
595
|
+
<div className="mb-4">
|
|
596
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
597
|
+
Border Color
|
|
598
|
+
</label>
|
|
599
|
+
<input
|
|
600
|
+
type="color"
|
|
601
|
+
value={component.props.borderColor || '#e0e0e0'}
|
|
602
|
+
onChange={(e) => handlePropChange('borderColor', e.target.value)}
|
|
603
|
+
className="w-full h-10 border border-gray-300 rounded-md"
|
|
604
|
+
/>
|
|
605
|
+
</div>
|
|
606
|
+
<NumberInput
|
|
607
|
+
label="Border Radius"
|
|
608
|
+
value={component.props.borderRadius || 0}
|
|
609
|
+
onChange={(val) => handlePropChange('borderRadius', val)}
|
|
610
|
+
min={0}
|
|
611
|
+
max={50}
|
|
612
|
+
unit="px"
|
|
613
|
+
/>
|
|
614
|
+
</div>
|
|
615
|
+
|
|
616
|
+
<CommonStyles />
|
|
617
|
+
</>
|
|
618
|
+
);
|
|
619
|
+
|
|
620
|
+
case 'divider':
|
|
621
|
+
return (
|
|
622
|
+
<>
|
|
623
|
+
<div className="mb-4 pt-4 border-t border-gray-200">
|
|
624
|
+
<h3 className="text-xs font-semibold text-gray-600 mb-3 uppercase">Style</h3>
|
|
625
|
+
<div className="mb-4">
|
|
626
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
627
|
+
Color
|
|
628
|
+
</label>
|
|
629
|
+
<input
|
|
630
|
+
type="color"
|
|
631
|
+
value={component.props.color || '#e0e0e0'}
|
|
632
|
+
onChange={(e) => handlePropChange('color', e.target.value)}
|
|
633
|
+
className="w-full h-10 border border-gray-300 rounded-md"
|
|
634
|
+
/>
|
|
635
|
+
</div>
|
|
636
|
+
<NumberInput
|
|
637
|
+
label="Height/Thickness"
|
|
638
|
+
value={component.props.height || 1}
|
|
639
|
+
onChange={(val) => handlePropChange('height', val)}
|
|
640
|
+
min={1}
|
|
641
|
+
max={20}
|
|
642
|
+
unit="px"
|
|
643
|
+
/>
|
|
644
|
+
</div>
|
|
645
|
+
|
|
646
|
+
<CommonStyles />
|
|
647
|
+
</>
|
|
648
|
+
);
|
|
649
|
+
|
|
650
|
+
case 'spacer':
|
|
651
|
+
return (
|
|
652
|
+
<>
|
|
653
|
+
<NumberInput
|
|
654
|
+
label="Height"
|
|
655
|
+
value={component.props.height || 20}
|
|
656
|
+
onChange={(val) => handlePropChange('height', val)}
|
|
657
|
+
min={0}
|
|
658
|
+
max={200}
|
|
659
|
+
unit="px"
|
|
660
|
+
/>
|
|
661
|
+
</>
|
|
662
|
+
);
|
|
663
|
+
|
|
664
|
+
case 'row':
|
|
665
|
+
return (
|
|
666
|
+
<>
|
|
667
|
+
<div className="mb-4 pt-4 border-t border-gray-200">
|
|
668
|
+
<h3 className="text-xs font-semibold text-gray-600 mb-3 uppercase">Layout</h3>
|
|
669
|
+
<NumberInput
|
|
670
|
+
label="Gap"
|
|
671
|
+
value={component.props.gap || 10}
|
|
672
|
+
onChange={(val) => handlePropChange('gap', val)}
|
|
673
|
+
min={0}
|
|
674
|
+
max={50}
|
|
675
|
+
unit="px"
|
|
676
|
+
/>
|
|
677
|
+
<div className="mb-4">
|
|
678
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
679
|
+
Justify Content
|
|
680
|
+
</label>
|
|
681
|
+
<select
|
|
682
|
+
value={component.props.justifyContent || 'flex-start'}
|
|
683
|
+
onChange={(e) => handlePropChange('justifyContent', e.target.value)}
|
|
684
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm"
|
|
685
|
+
>
|
|
686
|
+
<option value="flex-start">Start</option>
|
|
687
|
+
<option value="center">Center</option>
|
|
688
|
+
<option value="flex-end">End</option>
|
|
689
|
+
<option value="space-between">Space Between</option>
|
|
690
|
+
<option value="space-around">Space Around</option>
|
|
691
|
+
</select>
|
|
692
|
+
</div>
|
|
693
|
+
</div>
|
|
694
|
+
|
|
695
|
+
<CommonStyles />
|
|
696
|
+
</>
|
|
697
|
+
);
|
|
698
|
+
|
|
699
|
+
case 'column':
|
|
700
|
+
return (
|
|
701
|
+
<>
|
|
702
|
+
<div className="mb-4 pt-4 border-t border-gray-200">
|
|
703
|
+
<h3 className="text-xs font-semibold text-gray-600 mb-3 uppercase">Size</h3>
|
|
704
|
+
<div className="mb-4">
|
|
705
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
706
|
+
Width
|
|
707
|
+
</label>
|
|
708
|
+
<input
|
|
709
|
+
type="text"
|
|
710
|
+
value={component.props.width || '50%'}
|
|
711
|
+
onChange={(e) => handlePropChange('width', e.target.value)}
|
|
712
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm"
|
|
713
|
+
placeholder="50% or 300px"
|
|
714
|
+
/>
|
|
715
|
+
</div>
|
|
716
|
+
</div>
|
|
717
|
+
|
|
718
|
+
<CommonStyles />
|
|
719
|
+
</>
|
|
720
|
+
);
|
|
721
|
+
|
|
722
|
+
case 'header':
|
|
723
|
+
return (
|
|
724
|
+
<>
|
|
725
|
+
<div className="mb-4 pt-4 border-t border-gray-200">
|
|
726
|
+
<h3 className="text-xs font-semibold text-gray-600 mb-3 uppercase">Logo</h3>
|
|
727
|
+
<div className="mb-4">
|
|
728
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
729
|
+
Show Logo
|
|
730
|
+
</label>
|
|
731
|
+
<input
|
|
732
|
+
type="checkbox"
|
|
733
|
+
checked={component.props.showLogo !== false}
|
|
734
|
+
onChange={(e) => handlePropChange('showLogo', e.target.checked)}
|
|
735
|
+
className="w-4 h-4"
|
|
736
|
+
/>
|
|
737
|
+
</div>
|
|
738
|
+
{component.props.showLogo !== false && (
|
|
739
|
+
<>
|
|
740
|
+
<div className="mb-4">
|
|
741
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
742
|
+
Logo URL
|
|
743
|
+
</label>
|
|
744
|
+
<input
|
|
745
|
+
type="text"
|
|
746
|
+
value={component.props.logoUrl || ''}
|
|
747
|
+
onChange={(e) => handlePropChange('logoUrl', e.target.value)}
|
|
748
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm"
|
|
749
|
+
placeholder="https://example.com/logo.png"
|
|
750
|
+
/>
|
|
751
|
+
</div>
|
|
752
|
+
<div className="mb-4">
|
|
753
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
754
|
+
Logo Alt Text
|
|
755
|
+
</label>
|
|
756
|
+
<input
|
|
757
|
+
type="text"
|
|
758
|
+
value={component.props.logoAlt || ''}
|
|
759
|
+
onChange={(e) => handlePropChange('logoAlt', e.target.value)}
|
|
760
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm"
|
|
761
|
+
/>
|
|
762
|
+
</div>
|
|
763
|
+
<NumberInput
|
|
764
|
+
label="Logo Height"
|
|
765
|
+
value={component.props.logoHeight || 40}
|
|
766
|
+
onChange={(val) => handlePropChange('logoHeight', val)}
|
|
767
|
+
min={20}
|
|
768
|
+
max={100}
|
|
769
|
+
unit="px"
|
|
770
|
+
/>
|
|
771
|
+
</>
|
|
772
|
+
)}
|
|
773
|
+
</div>
|
|
774
|
+
|
|
775
|
+
<div className="mb-4 pt-4 border-t border-gray-200">
|
|
776
|
+
<h3 className="text-xs font-semibold text-gray-600 mb-3 uppercase">Background</h3>
|
|
777
|
+
<div className="mb-4">
|
|
778
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
779
|
+
Background Color
|
|
780
|
+
</label>
|
|
781
|
+
<input
|
|
782
|
+
type="color"
|
|
783
|
+
value={component.props.backgroundColor || '#ffffff'}
|
|
784
|
+
onChange={(e) => handlePropChange('backgroundColor', e.target.value)}
|
|
785
|
+
className="w-full h-10 border border-gray-300 rounded-md"
|
|
786
|
+
/>
|
|
787
|
+
</div>
|
|
788
|
+
</div>
|
|
789
|
+
|
|
790
|
+
<div className="mb-4 pt-4 border-t border-gray-200">
|
|
791
|
+
<h3 className="text-xs font-semibold text-gray-600 mb-3 uppercase">Border</h3>
|
|
792
|
+
<div className="mb-4">
|
|
793
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
794
|
+
Show Bottom Border
|
|
795
|
+
</label>
|
|
796
|
+
<input
|
|
797
|
+
type="checkbox"
|
|
798
|
+
checked={component.props.borderBottom || false}
|
|
799
|
+
onChange={(e) => handlePropChange('borderBottom', e.target.checked)}
|
|
800
|
+
className="w-4 h-4"
|
|
801
|
+
/>
|
|
802
|
+
</div>
|
|
803
|
+
{component.props.borderBottom && (
|
|
804
|
+
<div className="mb-4">
|
|
805
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
806
|
+
Border Color
|
|
807
|
+
</label>
|
|
808
|
+
<input
|
|
809
|
+
type="color"
|
|
810
|
+
value={component.props.borderColor || '#e0e0e0'}
|
|
811
|
+
onChange={(e) => handlePropChange('borderColor', e.target.value)}
|
|
812
|
+
className="w-full h-10 border border-gray-300 rounded-md"
|
|
813
|
+
/>
|
|
814
|
+
</div>
|
|
815
|
+
)}
|
|
816
|
+
</div>
|
|
817
|
+
|
|
818
|
+
<div className="mb-4 pt-4 border-t border-gray-200">
|
|
819
|
+
<h3 className="text-xs font-semibold text-gray-600 mb-3 uppercase">Spacing</h3>
|
|
820
|
+
<NumberInput
|
|
821
|
+
label="Padding"
|
|
822
|
+
value={component.props.padding || 20}
|
|
823
|
+
onChange={(val) => handlePropChange('padding', val)}
|
|
824
|
+
min={0}
|
|
825
|
+
max={100}
|
|
826
|
+
unit="px"
|
|
827
|
+
/>
|
|
828
|
+
</div>
|
|
829
|
+
|
|
830
|
+
<CommonStyles />
|
|
831
|
+
</>
|
|
832
|
+
);
|
|
833
|
+
|
|
834
|
+
case 'footer':
|
|
835
|
+
return (
|
|
836
|
+
<>
|
|
837
|
+
<div className="mb-4 pt-4 border-t border-gray-200">
|
|
838
|
+
<h3 className="text-xs font-semibold text-gray-600 mb-3 uppercase">Content</h3>
|
|
839
|
+
<div className="mb-4">
|
|
840
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
841
|
+
Copyright Text
|
|
842
|
+
</label>
|
|
843
|
+
<input
|
|
844
|
+
type="text"
|
|
845
|
+
value={component.props.copyright || ''}
|
|
846
|
+
onChange={(e) => handlePropChange('copyright', e.target.value)}
|
|
847
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm"
|
|
848
|
+
placeholder="© 2024 Company Name. All rights reserved."
|
|
849
|
+
/>
|
|
850
|
+
</div>
|
|
851
|
+
<div className="mb-4">
|
|
852
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
853
|
+
Show Social Links
|
|
854
|
+
</label>
|
|
855
|
+
<input
|
|
856
|
+
type="checkbox"
|
|
857
|
+
checked={component.props.showSocialLinks !== false}
|
|
858
|
+
onChange={(e) => handlePropChange('showSocialLinks', e.target.checked)}
|
|
859
|
+
className="w-4 h-4"
|
|
860
|
+
/>
|
|
861
|
+
</div>
|
|
862
|
+
</div>
|
|
863
|
+
|
|
864
|
+
<div className="mb-4 pt-4 border-t border-gray-200">
|
|
865
|
+
<h3 className="text-xs font-semibold text-gray-600 mb-3 uppercase">Background</h3>
|
|
866
|
+
<div className="mb-4">
|
|
867
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
868
|
+
Background Color
|
|
869
|
+
</label>
|
|
870
|
+
<input
|
|
871
|
+
type="color"
|
|
872
|
+
value={component.props.backgroundColor || '#f5f5f5'}
|
|
873
|
+
onChange={(e) => handlePropChange('backgroundColor', e.target.value)}
|
|
874
|
+
className="w-full h-10 border border-gray-300 rounded-md"
|
|
875
|
+
/>
|
|
876
|
+
</div>
|
|
877
|
+
</div>
|
|
878
|
+
|
|
879
|
+
<div className="mb-4 pt-4 border-t border-gray-200">
|
|
880
|
+
<h3 className="text-xs font-semibold text-gray-600 mb-3 uppercase">Typography</h3>
|
|
881
|
+
<NumberInput
|
|
882
|
+
label="Font Size"
|
|
883
|
+
value={component.props.fontSize || 12}
|
|
884
|
+
onChange={(val) => handlePropChange('fontSize', val)}
|
|
885
|
+
min={8}
|
|
886
|
+
max={24}
|
|
887
|
+
unit="px"
|
|
888
|
+
/>
|
|
889
|
+
<div className="mb-4">
|
|
890
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
891
|
+
Text Color
|
|
892
|
+
</label>
|
|
893
|
+
<input
|
|
894
|
+
type="color"
|
|
895
|
+
value={component.props.color || '#666666'}
|
|
896
|
+
onChange={(e) => handlePropChange('color', e.target.value)}
|
|
897
|
+
className="w-full h-10 border border-gray-300 rounded-md"
|
|
898
|
+
/>
|
|
899
|
+
</div>
|
|
900
|
+
<div className="mb-4">
|
|
901
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
902
|
+
Text Align
|
|
903
|
+
</label>
|
|
904
|
+
<select
|
|
905
|
+
value={component.props.textAlign || 'center'}
|
|
906
|
+
onChange={(e) => handlePropChange('textAlign', e.target.value)}
|
|
907
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm"
|
|
908
|
+
>
|
|
909
|
+
<option value="left">Left</option>
|
|
910
|
+
<option value="center">Center</option>
|
|
911
|
+
<option value="right">Right</option>
|
|
912
|
+
</select>
|
|
913
|
+
</div>
|
|
914
|
+
</div>
|
|
915
|
+
|
|
916
|
+
<div className="mb-4 pt-4 border-t border-gray-200">
|
|
917
|
+
<h3 className="text-xs font-semibold text-gray-600 mb-3 uppercase">Border</h3>
|
|
918
|
+
<div className="mb-4">
|
|
919
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
920
|
+
Show Top Border
|
|
921
|
+
</label>
|
|
922
|
+
<input
|
|
923
|
+
type="checkbox"
|
|
924
|
+
checked={component.props.borderTop || false}
|
|
925
|
+
onChange={(e) => handlePropChange('borderTop', e.target.checked)}
|
|
926
|
+
className="w-4 h-4"
|
|
927
|
+
/>
|
|
928
|
+
</div>
|
|
929
|
+
</div>
|
|
930
|
+
|
|
931
|
+
<CommonStyles />
|
|
932
|
+
</>
|
|
933
|
+
);
|
|
934
|
+
|
|
935
|
+
case 'section':
|
|
936
|
+
return (
|
|
937
|
+
<>
|
|
938
|
+
<div className="mb-4 pt-4 border-t border-gray-200">
|
|
939
|
+
<h3 className="text-xs font-semibold text-gray-600 mb-3 uppercase">Background</h3>
|
|
940
|
+
<div className="mb-4">
|
|
941
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
942
|
+
Background Color
|
|
943
|
+
</label>
|
|
944
|
+
<input
|
|
945
|
+
type="color"
|
|
946
|
+
value={component.props.backgroundColor || '#ffffff'}
|
|
947
|
+
onChange={(e) => handlePropChange('backgroundColor', e.target.value)}
|
|
948
|
+
className="w-full h-10 border border-gray-300 rounded-md"
|
|
949
|
+
/>
|
|
950
|
+
</div>
|
|
951
|
+
</div>
|
|
952
|
+
|
|
953
|
+
<div className="mb-4 pt-4 border-t border-gray-200">
|
|
954
|
+
<h3 className="text-xs font-semibold text-gray-600 mb-3 uppercase">Spacing</h3>
|
|
955
|
+
<NumberInput
|
|
956
|
+
label="Padding"
|
|
957
|
+
value={component.props.padding || 20}
|
|
958
|
+
onChange={(val) => handlePropChange('padding', val)}
|
|
959
|
+
min={0}
|
|
960
|
+
max={100}
|
|
961
|
+
unit="px"
|
|
962
|
+
/>
|
|
963
|
+
</div>
|
|
964
|
+
|
|
965
|
+
<CommonStyles />
|
|
966
|
+
</>
|
|
967
|
+
);
|
|
968
|
+
|
|
969
|
+
case 'link':
|
|
970
|
+
return (
|
|
971
|
+
<>
|
|
972
|
+
<div className="mb-4">
|
|
973
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
974
|
+
Link Text
|
|
975
|
+
</label>
|
|
976
|
+
<input
|
|
977
|
+
type="text"
|
|
978
|
+
value={component.props.text || ''}
|
|
979
|
+
onChange={(e) => handlePropChange('text', e.target.value)}
|
|
980
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm"
|
|
981
|
+
/>
|
|
982
|
+
</div>
|
|
983
|
+
<div className="mb-4">
|
|
984
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
985
|
+
URL (href)
|
|
986
|
+
</label>
|
|
987
|
+
<input
|
|
988
|
+
type="text"
|
|
989
|
+
value={component.props.href || '#'}
|
|
990
|
+
onChange={(e) => handlePropChange('href', e.target.value)}
|
|
991
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm"
|
|
992
|
+
placeholder="https://example.com"
|
|
993
|
+
/>
|
|
994
|
+
</div>
|
|
995
|
+
<div className="mb-4 pt-4 border-t border-gray-200">
|
|
996
|
+
<h3 className="text-xs font-semibold text-gray-600 mb-3 uppercase">Style</h3>
|
|
997
|
+
<NumberInput
|
|
998
|
+
label="Font Size"
|
|
999
|
+
value={component.props.fontSize || 16}
|
|
1000
|
+
onChange={(val) => handlePropChange('fontSize', val)}
|
|
1001
|
+
min={10}
|
|
1002
|
+
max={24}
|
|
1003
|
+
unit="px"
|
|
1004
|
+
/>
|
|
1005
|
+
<div className="mb-4">
|
|
1006
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
1007
|
+
Color
|
|
1008
|
+
</label>
|
|
1009
|
+
<input
|
|
1010
|
+
type="color"
|
|
1011
|
+
value={component.props.color || '#007bff'}
|
|
1012
|
+
onChange={(e) => handlePropChange('color', e.target.value)}
|
|
1013
|
+
className="w-full h-10 border border-gray-300 rounded-md"
|
|
1014
|
+
/>
|
|
1015
|
+
</div>
|
|
1016
|
+
<div className="mb-4">
|
|
1017
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
1018
|
+
Underline
|
|
1019
|
+
</label>
|
|
1020
|
+
<input
|
|
1021
|
+
type="checkbox"
|
|
1022
|
+
checked={component.props.underline !== false}
|
|
1023
|
+
onChange={(e) => handlePropChange('underline', e.target.checked)}
|
|
1024
|
+
className="w-4 h-4"
|
|
1025
|
+
/>
|
|
1026
|
+
</div>
|
|
1027
|
+
</div>
|
|
1028
|
+
|
|
1029
|
+
<CommonStyles />
|
|
1030
|
+
</>
|
|
1031
|
+
);
|
|
1032
|
+
|
|
1033
|
+
case 'list':
|
|
1034
|
+
return (
|
|
1035
|
+
<>
|
|
1036
|
+
<div className="mb-4">
|
|
1037
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
1038
|
+
List Type
|
|
1039
|
+
</label>
|
|
1040
|
+
<select
|
|
1041
|
+
value={component.props.listType || 'unordered'}
|
|
1042
|
+
onChange={(e) => handlePropChange('listType', e.target.value)}
|
|
1043
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm"
|
|
1044
|
+
>
|
|
1045
|
+
<option value="unordered">Unordered (Bullets)</option>
|
|
1046
|
+
<option value="ordered">Ordered (Numbers)</option>
|
|
1047
|
+
</select>
|
|
1048
|
+
</div>
|
|
1049
|
+
<div className="mb-4">
|
|
1050
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
1051
|
+
List Items (one per line)
|
|
1052
|
+
</label>
|
|
1053
|
+
<textarea
|
|
1054
|
+
value={(component.props.items || []).join('\n')}
|
|
1055
|
+
onChange={(e) => {
|
|
1056
|
+
const items = e.target.value.split('\n').filter(item => item.trim());
|
|
1057
|
+
handlePropChange('items', items);
|
|
1058
|
+
}}
|
|
1059
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm"
|
|
1060
|
+
rows={5}
|
|
1061
|
+
placeholder="Item 1 Item 2 Item 3"
|
|
1062
|
+
/>
|
|
1063
|
+
</div>
|
|
1064
|
+
<div className="mb-4 pt-4 border-t border-gray-200">
|
|
1065
|
+
<h3 className="text-xs font-semibold text-gray-600 mb-3 uppercase">Typography</h3>
|
|
1066
|
+
<NumberInput
|
|
1067
|
+
label="Font Size"
|
|
1068
|
+
value={component.props.fontSize || 16}
|
|
1069
|
+
onChange={(val) => handlePropChange('fontSize', val)}
|
|
1070
|
+
min={10}
|
|
1071
|
+
max={24}
|
|
1072
|
+
unit="px"
|
|
1073
|
+
/>
|
|
1074
|
+
<div className="mb-4">
|
|
1075
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
1076
|
+
Color
|
|
1077
|
+
</label>
|
|
1078
|
+
<input
|
|
1079
|
+
type="color"
|
|
1080
|
+
value={component.props.color || '#000000'}
|
|
1081
|
+
onChange={(e) => handlePropChange('color', e.target.value)}
|
|
1082
|
+
className="w-full h-10 border border-gray-300 rounded-md"
|
|
1083
|
+
/>
|
|
1084
|
+
</div>
|
|
1085
|
+
</div>
|
|
1086
|
+
|
|
1087
|
+
<CommonStyles />
|
|
1088
|
+
</>
|
|
1089
|
+
);
|
|
1090
|
+
|
|
1091
|
+
case 'table':
|
|
1092
|
+
return (
|
|
1093
|
+
<>
|
|
1094
|
+
<div className="mb-4">
|
|
1095
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
1096
|
+
Headers (comma separated)
|
|
1097
|
+
</label>
|
|
1098
|
+
<input
|
|
1099
|
+
type="text"
|
|
1100
|
+
value={(component.props.headers || []).join(', ')}
|
|
1101
|
+
onChange={(e) => {
|
|
1102
|
+
const headers = e.target.value.split(',').map(h => h.trim()).filter(h => h);
|
|
1103
|
+
handlePropChange('headers', headers);
|
|
1104
|
+
}}
|
|
1105
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm"
|
|
1106
|
+
placeholder="Header 1, Header 2, Header 3"
|
|
1107
|
+
/>
|
|
1108
|
+
</div>
|
|
1109
|
+
<div className="mb-4">
|
|
1110
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
1111
|
+
Rows (comma separated, one row per line)
|
|
1112
|
+
</label>
|
|
1113
|
+
<textarea
|
|
1114
|
+
value={(component.props.rows || []).map((row: string[]) => row.join(', ')).join('\n')}
|
|
1115
|
+
onChange={(e) => {
|
|
1116
|
+
const rows = e.target.value.split('\n')
|
|
1117
|
+
.filter(row => row.trim())
|
|
1118
|
+
.map(row => row.split(',').map(cell => cell.trim()).filter(cell => cell));
|
|
1119
|
+
handlePropChange('rows', rows);
|
|
1120
|
+
}}
|
|
1121
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm"
|
|
1122
|
+
rows={5}
|
|
1123
|
+
placeholder="Cell 1, Cell 2, Cell 3 Cell 4, Cell 5, Cell 6"
|
|
1124
|
+
/>
|
|
1125
|
+
</div>
|
|
1126
|
+
<div className="mb-4 pt-4 border-t border-gray-200">
|
|
1127
|
+
<h3 className="text-xs font-semibold text-gray-600 mb-3 uppercase">Style</h3>
|
|
1128
|
+
<NumberInput
|
|
1129
|
+
label="Border Width"
|
|
1130
|
+
value={component.props.borderWidth || 1}
|
|
1131
|
+
onChange={(val) => handlePropChange('borderWidth', val)}
|
|
1132
|
+
min={0}
|
|
1133
|
+
max={5}
|
|
1134
|
+
unit="px"
|
|
1135
|
+
/>
|
|
1136
|
+
<div className="mb-4">
|
|
1137
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
1138
|
+
Border Color
|
|
1139
|
+
</label>
|
|
1140
|
+
<input
|
|
1141
|
+
type="color"
|
|
1142
|
+
value={component.props.borderColor || '#e0e0e0'}
|
|
1143
|
+
onChange={(e) => handlePropChange('borderColor', e.target.value)}
|
|
1144
|
+
className="w-full h-10 border border-gray-300 rounded-md"
|
|
1145
|
+
/>
|
|
1146
|
+
</div>
|
|
1147
|
+
<NumberInput
|
|
1148
|
+
label="Cell Padding"
|
|
1149
|
+
value={component.props.cellPadding || 10}
|
|
1150
|
+
onChange={(val) => handlePropChange('cellPadding', val)}
|
|
1151
|
+
min={0}
|
|
1152
|
+
max={30}
|
|
1153
|
+
unit="px"
|
|
1154
|
+
/>
|
|
1155
|
+
<div className="mb-4">
|
|
1156
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
1157
|
+
Header Background Color
|
|
1158
|
+
</label>
|
|
1159
|
+
<input
|
|
1160
|
+
type="color"
|
|
1161
|
+
value={component.props.headerBackgroundColor || '#f5f5f5'}
|
|
1162
|
+
onChange={(e) => handlePropChange('headerBackgroundColor', e.target.value)}
|
|
1163
|
+
className="w-full h-10 border border-gray-300 rounded-md"
|
|
1164
|
+
/>
|
|
1165
|
+
</div>
|
|
1166
|
+
</div>
|
|
1167
|
+
|
|
1168
|
+
<CommonStyles />
|
|
1169
|
+
</>
|
|
1170
|
+
);
|
|
1171
|
+
|
|
1172
|
+
case 'social-links':
|
|
1173
|
+
return (
|
|
1174
|
+
<>
|
|
1175
|
+
<div className="mb-4 pt-4 border-t border-gray-200">
|
|
1176
|
+
<h3 className="text-xs font-semibold text-gray-600 mb-3 uppercase">Social Media Links</h3>
|
|
1177
|
+
<div className="mb-4">
|
|
1178
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
1179
|
+
Facebook URL
|
|
1180
|
+
</label>
|
|
1181
|
+
<input
|
|
1182
|
+
type="text"
|
|
1183
|
+
value={component.props.facebook || ''}
|
|
1184
|
+
onChange={(e) => handlePropChange('facebook', e.target.value)}
|
|
1185
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm"
|
|
1186
|
+
placeholder="https://facebook.com/yourpage"
|
|
1187
|
+
/>
|
|
1188
|
+
</div>
|
|
1189
|
+
<div className="mb-4">
|
|
1190
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
1191
|
+
Twitter URL
|
|
1192
|
+
</label>
|
|
1193
|
+
<input
|
|
1194
|
+
type="text"
|
|
1195
|
+
value={component.props.twitter || ''}
|
|
1196
|
+
onChange={(e) => handlePropChange('twitter', e.target.value)}
|
|
1197
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm"
|
|
1198
|
+
placeholder="https://twitter.com/yourhandle"
|
|
1199
|
+
/>
|
|
1200
|
+
</div>
|
|
1201
|
+
<div className="mb-4">
|
|
1202
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
1203
|
+
Instagram URL
|
|
1204
|
+
</label>
|
|
1205
|
+
<input
|
|
1206
|
+
type="text"
|
|
1207
|
+
value={component.props.instagram || ''}
|
|
1208
|
+
onChange={(e) => handlePropChange('instagram', e.target.value)}
|
|
1209
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm"
|
|
1210
|
+
placeholder="https://instagram.com/yourhandle"
|
|
1211
|
+
/>
|
|
1212
|
+
</div>
|
|
1213
|
+
<div className="mb-4">
|
|
1214
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
1215
|
+
LinkedIn URL
|
|
1216
|
+
</label>
|
|
1217
|
+
<input
|
|
1218
|
+
type="text"
|
|
1219
|
+
value={component.props.linkedin || ''}
|
|
1220
|
+
onChange={(e) => handlePropChange('linkedin', e.target.value)}
|
|
1221
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm"
|
|
1222
|
+
placeholder="https://linkedin.com/company/yourcompany"
|
|
1223
|
+
/>
|
|
1224
|
+
</div>
|
|
1225
|
+
<div className="mb-4">
|
|
1226
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
1227
|
+
YouTube URL
|
|
1228
|
+
</label>
|
|
1229
|
+
<input
|
|
1230
|
+
type="text"
|
|
1231
|
+
value={component.props.youtube || ''}
|
|
1232
|
+
onChange={(e) => handlePropChange('youtube', e.target.value)}
|
|
1233
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm"
|
|
1234
|
+
placeholder="https://youtube.com/yourchannel"
|
|
1235
|
+
/>
|
|
1236
|
+
</div>
|
|
1237
|
+
</div>
|
|
1238
|
+
<div className="mb-4 pt-4 border-t border-gray-200">
|
|
1239
|
+
<h3 className="text-xs font-semibold text-gray-600 mb-3 uppercase">Style</h3>
|
|
1240
|
+
<NumberInput
|
|
1241
|
+
label="Icon Size"
|
|
1242
|
+
value={component.props.iconSize || 24}
|
|
1243
|
+
onChange={(val) => handlePropChange('iconSize', val)}
|
|
1244
|
+
min={16}
|
|
1245
|
+
max={48}
|
|
1246
|
+
unit="px"
|
|
1247
|
+
/>
|
|
1248
|
+
<NumberInput
|
|
1249
|
+
label="Gap"
|
|
1250
|
+
value={component.props.gap || 10}
|
|
1251
|
+
onChange={(val) => handlePropChange('gap', val)}
|
|
1252
|
+
min={0}
|
|
1253
|
+
max={30}
|
|
1254
|
+
unit="px"
|
|
1255
|
+
/>
|
|
1256
|
+
<div className="mb-4">
|
|
1257
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
1258
|
+
Alignment
|
|
1259
|
+
</label>
|
|
1260
|
+
<select
|
|
1261
|
+
value={component.props.align || 'center'}
|
|
1262
|
+
onChange={(e) => handlePropChange('align', e.target.value)}
|
|
1263
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm"
|
|
1264
|
+
>
|
|
1265
|
+
<option value="flex-start">Left</option>
|
|
1266
|
+
<option value="center">Center</option>
|
|
1267
|
+
<option value="flex-end">Right</option>
|
|
1268
|
+
</select>
|
|
1269
|
+
</div>
|
|
1270
|
+
</div>
|
|
1271
|
+
|
|
1272
|
+
<CommonStyles />
|
|
1273
|
+
</>
|
|
1274
|
+
);
|
|
1275
|
+
|
|
1276
|
+
case 'quote':
|
|
1277
|
+
return (
|
|
1278
|
+
<>
|
|
1279
|
+
<div className="mb-4">
|
|
1280
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
1281
|
+
Quote Text
|
|
1282
|
+
</label>
|
|
1283
|
+
<textarea
|
|
1284
|
+
value={component.props.text || ''}
|
|
1285
|
+
onChange={(e) => handlePropChange('text', e.target.value)}
|
|
1286
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm"
|
|
1287
|
+
rows={3}
|
|
1288
|
+
/>
|
|
1289
|
+
</div>
|
|
1290
|
+
<div className="mb-4">
|
|
1291
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
1292
|
+
Author (optional)
|
|
1293
|
+
</label>
|
|
1294
|
+
<input
|
|
1295
|
+
type="text"
|
|
1296
|
+
value={component.props.author || ''}
|
|
1297
|
+
onChange={(e) => handlePropChange('author', e.target.value)}
|
|
1298
|
+
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm"
|
|
1299
|
+
placeholder="Author Name"
|
|
1300
|
+
/>
|
|
1301
|
+
</div>
|
|
1302
|
+
<div className="mb-4 pt-4 border-t border-gray-200">
|
|
1303
|
+
<h3 className="text-xs font-semibold text-gray-600 mb-3 uppercase">Typography</h3>
|
|
1304
|
+
<NumberInput
|
|
1305
|
+
label="Font Size"
|
|
1306
|
+
value={component.props.fontSize || 18}
|
|
1307
|
+
onChange={(val) => handlePropChange('fontSize', val)}
|
|
1308
|
+
min={12}
|
|
1309
|
+
max={32}
|
|
1310
|
+
unit="px"
|
|
1311
|
+
/>
|
|
1312
|
+
<div className="mb-4">
|
|
1313
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
1314
|
+
Color
|
|
1315
|
+
</label>
|
|
1316
|
+
<input
|
|
1317
|
+
type="color"
|
|
1318
|
+
value={component.props.color || '#666666'}
|
|
1319
|
+
onChange={(e) => handlePropChange('color', e.target.value)}
|
|
1320
|
+
className="w-full h-10 border border-gray-300 rounded-md"
|
|
1321
|
+
/>
|
|
1322
|
+
</div>
|
|
1323
|
+
</div>
|
|
1324
|
+
<div className="mb-4 pt-4 border-t border-gray-200">
|
|
1325
|
+
<h3 className="text-xs font-semibold text-gray-600 mb-3 uppercase">Border</h3>
|
|
1326
|
+
<div className="mb-4">
|
|
1327
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
1328
|
+
Show Left Border
|
|
1329
|
+
</label>
|
|
1330
|
+
<input
|
|
1331
|
+
type="checkbox"
|
|
1332
|
+
checked={component.props.borderLeft !== false}
|
|
1333
|
+
onChange={(e) => handlePropChange('borderLeft', e.target.checked)}
|
|
1334
|
+
className="w-4 h-4"
|
|
1335
|
+
/>
|
|
1336
|
+
</div>
|
|
1337
|
+
{component.props.borderLeft !== false && (
|
|
1338
|
+
<div className="mb-4">
|
|
1339
|
+
<label className="block text-xs font-medium text-gray-700 mb-1">
|
|
1340
|
+
Border Color
|
|
1341
|
+
</label>
|
|
1342
|
+
<input
|
|
1343
|
+
type="color"
|
|
1344
|
+
value={component.props.borderColor || '#007bff'}
|
|
1345
|
+
onChange={(e) => handlePropChange('borderColor', e.target.value)}
|
|
1346
|
+
className="w-full h-10 border border-gray-300 rounded-md"
|
|
1347
|
+
/>
|
|
1348
|
+
</div>
|
|
1349
|
+
)}
|
|
1350
|
+
</div>
|
|
1351
|
+
|
|
1352
|
+
<CommonStyles />
|
|
1353
|
+
</>
|
|
1354
|
+
);
|
|
1355
|
+
|
|
1356
|
+
default:
|
|
1357
|
+
return <p className="text-sm text-gray-400">No properties available</p>;
|
|
1358
|
+
}
|
|
1359
|
+
};
|
|
1360
|
+
|
|
1361
|
+
return (
|
|
1362
|
+
<div className="p-4 h-full overflow-y-auto">
|
|
1363
|
+
<div className="flex items-center justify-between mb-4 sticky top-0 bg-white pb-2 border-b border-gray-200">
|
|
1364
|
+
<h2 className="text-sm font-semibold text-gray-700">Properties</h2>
|
|
1365
|
+
<button
|
|
1366
|
+
onClick={() => {
|
|
1367
|
+
removeComponent(component.id);
|
|
1368
|
+
selectComponent(null);
|
|
1369
|
+
}}
|
|
1370
|
+
className="p-1.5 text-red-600 hover:bg-red-50 rounded-md transition-colors"
|
|
1371
|
+
>
|
|
1372
|
+
<Trash2 size={16} />
|
|
1373
|
+
</button>
|
|
1374
|
+
</div>
|
|
1375
|
+
|
|
1376
|
+
<div className="mb-4 pb-4 border-b border-gray-200">
|
|
1377
|
+
<p className="text-xs text-gray-500 mb-1">Component Type</p>
|
|
1378
|
+
<p className="text-sm font-medium text-gray-700 capitalize">
|
|
1379
|
+
{component.type}
|
|
1380
|
+
</p>
|
|
1381
|
+
</div>
|
|
1382
|
+
|
|
1383
|
+
<div>{renderPropertyEditor()}</div>
|
|
1384
|
+
</div>
|
|
1385
|
+
);
|
|
1386
|
+
}
|