@fleetbase/ember-ui 0.3.22 → 0.3.24

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 (46) hide show
  1. package/addon/components/layout/header/smart-nav-menu/dropdown.hbs +77 -68
  2. package/addon/components/layout/header/smart-nav-menu/dropdown.js +7 -54
  3. package/addon/components/template-builder/canvas.hbs +23 -0
  4. package/addon/components/template-builder/canvas.js +116 -0
  5. package/addon/components/template-builder/element-renderer.hbs +126 -0
  6. package/addon/components/template-builder/element-renderer.js +398 -0
  7. package/addon/components/template-builder/layers-panel.hbs +99 -0
  8. package/addon/components/template-builder/layers-panel.js +146 -0
  9. package/addon/components/template-builder/properties-panel/field.hbs +7 -0
  10. package/addon/components/template-builder/properties-panel/field.js +9 -0
  11. package/addon/components/template-builder/properties-panel/section.hbs +24 -0
  12. package/addon/components/template-builder/properties-panel/section.js +19 -0
  13. package/addon/components/template-builder/properties-panel.hbs +576 -0
  14. package/addon/components/template-builder/properties-panel.js +413 -0
  15. package/addon/components/template-builder/queries-panel.hbs +84 -0
  16. package/addon/components/template-builder/queries-panel.js +88 -0
  17. package/addon/components/template-builder/query-form.hbs +260 -0
  18. package/addon/components/template-builder/query-form.js +309 -0
  19. package/addon/components/template-builder/toolbar.hbs +134 -0
  20. package/addon/components/template-builder/toolbar.js +106 -0
  21. package/addon/components/template-builder/variable-picker.hbs +210 -0
  22. package/addon/components/template-builder/variable-picker.js +181 -0
  23. package/addon/components/template-builder.hbs +119 -0
  24. package/addon/components/template-builder.js +567 -0
  25. package/addon/helpers/string-starts-with.js +14 -0
  26. package/addon/services/template-builder.js +72 -0
  27. package/addon/styles/addon.css +1 -0
  28. package/addon/styles/components/badge.css +66 -12
  29. package/addon/styles/components/smart-nav-menu.css +35 -29
  30. package/addon/styles/components/template-builder.css +297 -0
  31. package/addon/utils/get-currency.js +1 -1
  32. package/app/components/template-builder/canvas.js +1 -0
  33. package/app/components/template-builder/element-renderer.js +1 -0
  34. package/app/components/template-builder/layers-panel.js +1 -0
  35. package/app/components/template-builder/properties-panel/field.js +1 -0
  36. package/app/components/template-builder/properties-panel/section.js +1 -0
  37. package/app/components/template-builder/properties-panel.js +1 -0
  38. package/app/components/template-builder/queries-panel.js +1 -0
  39. package/app/components/template-builder/query-form.js +1 -0
  40. package/app/components/template-builder/toolbar.js +1 -0
  41. package/app/components/template-builder/variable-picker.js +1 -0
  42. package/app/components/template-builder.js +1 -0
  43. package/app/helpers/string-starts-with.js +1 -0
  44. package/app/services/template-builder.js +1 -0
  45. package/package.json +3 -2
  46. package/tsconfig.declarations.json +8 -8
@@ -0,0 +1,19 @@
1
+ import Component from '@glimmer/component';
2
+ import { action } from '@ember/object';
3
+
4
+ /**
5
+ * Collapsible section wrapper for the properties panel.
6
+ *
7
+ * @argument {String} title - Section title
8
+ * @argument {String} icon - FontAwesome icon name
9
+ * @argument {Boolean} isOpen - Whether the section is expanded
10
+ * @argument {Function} onToggle - Called when the header is clicked
11
+ */
12
+ export default class TemplateBuilderPropertiesPanelSectionComponent extends Component {
13
+ @action
14
+ toggle() {
15
+ if (this.args.onToggle) {
16
+ this.args.onToggle();
17
+ }
18
+ }
19
+ }
@@ -0,0 +1,576 @@
1
+ {{! Template Builder Properties Panel }}
2
+ <div class="tb-properties-panel flex flex-col h-full overflow-y-auto" ...attributes>
3
+
4
+ {{#if this.hasSelection}}
5
+ {{! ============================================================ }}
6
+ {{! ELEMENT PROPERTIES }}
7
+ {{! ============================================================ }}
8
+
9
+ {{! Header }}
10
+ <div class="flex items-center justify-between px-3 py-2 border-b border-gray-200 dark:border-gray-700 sticky top-0 bg-white dark:bg-gray-900 z-10">
11
+ <div class="flex items-center space-x-2">
12
+ <FaIcon @icon={{this.elementType}} class="w-3.5 h-3.5 text-blue-500" />
13
+ <span class="text-xs font-semibold text-gray-700 dark:text-gray-300 capitalize">{{this.elementType}} Properties</span>
14
+ </div>
15
+ </div>
16
+
17
+ {{! POSITION SECTION }}
18
+ <TemplateBuilder::PropertiesPanel::Section
19
+ @title="Position"
20
+ @icon="arrows-up-down-left-right"
21
+ @isOpen={{this.isSectionOpen "position"}}
22
+ @onToggle={{fn this.toggleSection "position"}}
23
+ >
24
+ <div class="grid grid-cols-2 gap-2">
25
+ <TemplateBuilder::PropertiesPanel::Field @label="X">
26
+ <input type="number" class="tb-input" value={{this.element.x}} {{on "change" (fn this.updateNumericProp "x")}} />
27
+ </TemplateBuilder::PropertiesPanel::Field>
28
+ <TemplateBuilder::PropertiesPanel::Field @label="Y">
29
+ <input type="number" class="tb-input" value={{this.element.y}} {{on "change" (fn this.updateNumericProp "y")}} />
30
+ </TemplateBuilder::PropertiesPanel::Field>
31
+ <TemplateBuilder::PropertiesPanel::Field @label="Rotation">
32
+ <input type="number" class="tb-input" value={{this.element.rotation}} min="-360" max="360" {{on "change" (fn this.updateNumericProp "rotation")}} />
33
+ </TemplateBuilder::PropertiesPanel::Field>
34
+ <TemplateBuilder::PropertiesPanel::Field @label="Z-Index">
35
+ <input type="number" class="tb-input" value={{this.element.z_index}} min="1" {{on "change" (fn this.updateNumericProp "z_index")}} />
36
+ </TemplateBuilder::PropertiesPanel::Field>
37
+ <TemplateBuilder::PropertiesPanel::Field @label="Opacity" @span="2">
38
+ <input type="range" class="w-full" value={{this.element.opacity}} min="0" max="1" step="0.05" {{on "input" (fn this.updateNumericProp "opacity")}} />
39
+ </TemplateBuilder::PropertiesPanel::Field>
40
+ </div>
41
+ </TemplateBuilder::PropertiesPanel::Section>
42
+
43
+ {{! SIZE SECTION }}
44
+ <TemplateBuilder::PropertiesPanel::Section
45
+ @title="Size"
46
+ @icon="ruler-combined"
47
+ @isOpen={{this.isSectionOpen "size"}}
48
+ @onToggle={{fn this.toggleSection "size"}}
49
+ >
50
+ <div class="grid grid-cols-2 gap-2">
51
+ <TemplateBuilder::PropertiesPanel::Field @label="Width">
52
+ <input type="number" class="tb-input" value={{this.element.width}} min="1" {{on "change" (fn this.updateNumericProp "width")}} />
53
+ </TemplateBuilder::PropertiesPanel::Field>
54
+ <TemplateBuilder::PropertiesPanel::Field @label="Height">
55
+ <input type="number" class="tb-input" value={{this.element.height}} min="1" {{on "change" (fn this.updateNumericProp "height")}} />
56
+ </TemplateBuilder::PropertiesPanel::Field>
57
+ </div>
58
+ </TemplateBuilder::PropertiesPanel::Section>
59
+
60
+ {{! TEXT CONTENT SECTION (text elements only) }}
61
+ {{#if this.hasTextContent}}
62
+ <TemplateBuilder::PropertiesPanel::Section
63
+ @title="Content"
64
+ @icon="align-left"
65
+ @isOpen={{this.isSectionOpen "content"}}
66
+ @onToggle={{fn this.toggleSection "content"}}
67
+ >
68
+ <div class="space-y-2">
69
+ <div class="relative">
70
+ <textarea
71
+ class="tb-input w-full min-h-20 resize-y font-mono text-xs"
72
+ placeholder="Enter text or use {variable.name} syntax..."
73
+ {{on "input" (fn this.updateProp "content" value="target.value")}}
74
+ >{{this.element.content}}</textarea>
75
+ </div>
76
+ <button
77
+ type="button"
78
+ class="flex items-center space-x-1.5 text-xs text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300"
79
+ {{on "click" (fn this.openVariablePicker "content")}}
80
+ >
81
+ <FaIcon @icon="code" class="w-3 h-3" />
82
+ <span>Insert variable or formula</span>
83
+ </button>
84
+ </div>
85
+ </TemplateBuilder::PropertiesPanel::Section>
86
+ {{/if}}
87
+
88
+ {{! IMAGE SECTION }}
89
+ {{#if this.isImage}}
90
+ <TemplateBuilder::PropertiesPanel::Section
91
+ @title="Image"
92
+ @icon="image"
93
+ @isOpen={{this.isSectionOpen "image"}}
94
+ @onToggle={{fn this.toggleSection "image"}}
95
+ >
96
+ <div class="space-y-2">
97
+
98
+ {{! Source — upload or variable token }}
99
+ <TemplateBuilder::PropertiesPanel::Field @label="Image Source">
100
+ {{#if this.imageIsVariable}}
101
+ {{! Variable token display }}
102
+ <div class="flex items-center space-x-1">
103
+ <div class="flex-1 flex items-center space-x-1.5 px-2 py-1 rounded border border-blue-300 dark:border-blue-600 bg-blue-50 dark:bg-blue-900/20 min-w-0">
104
+ <FaIcon @icon="code" class="w-3 h-3 text-blue-500 shrink-0" />
105
+ <span class="text-xs text-blue-700 dark:text-blue-300 truncate font-mono">{{this.element.src}}</span>
106
+ </div>
107
+ <button type="button" class="tb-icon-btn" title="Change variable" {{on "click" (fn this.openVariablePicker "src")}}>
108
+ <FaIcon @icon="pen" class="w-3 h-3" />
109
+ </button>
110
+ <button type="button" class="tb-icon-btn" title="Clear" {{on "click" this.clearImageSrc}}>
111
+ <FaIcon @icon="xmark" class="w-3 h-3" />
112
+ </button>
113
+ </div>
114
+ {{else if this.imageIsUploaded}}
115
+ {{! Uploaded file display }}
116
+ <div class="flex items-center space-x-1">
117
+ <div class="flex-1 flex items-center space-x-1.5 px-2 py-1 rounded border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 min-w-0">
118
+ {{#if this.isUploadingImage}}
119
+ <span class="text-xs text-gray-500 dark:text-gray-400">Uploading...</span>
120
+ {{else}}
121
+ <FaIcon @icon="image" class="w-3 h-3 text-gray-400 shrink-0" />
122
+ <span class="text-xs text-gray-700 dark:text-gray-300 truncate">{{this.uploadedImageFilename}}</span>
123
+ {{/if}}
124
+ </div>
125
+ <FileUpload
126
+ @name="tb-image-{{this.element.uuid}}"
127
+ @accept="image/*"
128
+ @onFileAdded={{this.onImageFileAdded}}
129
+ >
130
+ <button type="button" class="tb-icon-btn" title="Replace image">
131
+ <FaIcon @icon="arrow-up-from-bracket" class="w-3 h-3" />
132
+ </button>
133
+ </FileUpload>
134
+ <button type="button" class="tb-icon-btn" title="Clear" {{on "click" this.clearImageSrc}}>
135
+ <FaIcon @icon="xmark" class="w-3 h-3" />
136
+ </button>
137
+ </div>
138
+ {{else}}
139
+ {{! Empty state — upload or insert variable }}
140
+ <div class="space-y-1.5">
141
+ <FileUpload
142
+ @name="tb-image-{{this.element.uuid}}"
143
+ @accept="image/*"
144
+ @onFileAdded={{this.onImageFileAdded}}
145
+ >
146
+ <div class="flex items-center justify-center w-full px-3 py-3 rounded border-2 border-dashed border-gray-300 dark:border-gray-600 hover:border-blue-400 dark:hover:border-blue-500 cursor-pointer transition-colors group">
147
+ {{#if this.isUploadingImage}}
148
+ <span class="text-xs text-gray-500 dark:text-gray-400">Uploading...</span>
149
+ {{else}}
150
+ <div class="flex flex-col items-center space-y-1">
151
+ <FaIcon @icon="arrow-up-from-bracket" class="w-4 h-4 text-gray-400 group-hover:text-blue-500 transition-colors" />
152
+ <span class="text-xs text-gray-500 dark:text-gray-400 group-hover:text-blue-500 transition-colors">Click to upload image</span>
153
+ </div>
154
+ {{/if}}
155
+ </div>
156
+ </FileUpload>
157
+ <div class="flex items-center space-x-1.5">
158
+ <div class="flex-1 h-px bg-gray-200 dark:bg-gray-700"></div>
159
+ <span class="text-xs text-gray-400">or</span>
160
+ <div class="flex-1 h-px bg-gray-200 dark:bg-gray-700"></div>
161
+ </div>
162
+ <button
163
+ type="button"
164
+ class="w-full flex items-center justify-center space-x-1.5 px-2 py-1.5 rounded border border-gray-200 dark:border-gray-700 text-xs text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
165
+ {{on "click" (fn this.openVariablePicker "src")}}
166
+ >
167
+ <FaIcon @icon="code" class="w-3 h-3" />
168
+ <span>Use variable (e.g. {company.logo})</span>
169
+ </button>
170
+ </div>
171
+ {{/if}}
172
+ </TemplateBuilder::PropertiesPanel::Field>
173
+
174
+ <TemplateBuilder::PropertiesPanel::Field @label="Alt Text">
175
+ <input type="text" class="tb-input" value={{this.element.alt}} {{on "change" (fn this.updateProp "alt")}} />
176
+ </TemplateBuilder::PropertiesPanel::Field>
177
+ <TemplateBuilder::PropertiesPanel::Field @label="Object Fit">
178
+ <select class="tb-input" {{on "change" (fn this.updateProp "object_fit")}}>
179
+ {{#each this.objectFitOptions as |opt|}}
180
+ <option value={{opt.value}} selected={{eq this.element.object_fit opt.value}}>{{opt.label}}</option>
181
+ {{/each}}
182
+ </select>
183
+ </TemplateBuilder::PropertiesPanel::Field>
184
+ <TemplateBuilder::PropertiesPanel::Field @label="Border Radius">
185
+ <input type="number" class="tb-input" value={{this.element.border_radius}} min="0" {{on "change" (fn this.updateNumericProp "border_radius")}} />
186
+ </TemplateBuilder::PropertiesPanel::Field>
187
+ </div>
188
+ </TemplateBuilder::PropertiesPanel::Section>
189
+ {{/if}}
190
+
191
+ {{! TABLE SECTION }}
192
+ {{#if this.isTable}}
193
+ {{! Columns }}
194
+ <TemplateBuilder::PropertiesPanel::Section
195
+ @title="Columns"
196
+ @icon="table-columns"
197
+ @isOpen={{this.isSectionOpen "table-columns"}}
198
+ @onToggle={{fn this.toggleSection "table-columns"}}
199
+ >
200
+ <div class="space-y-1.5">
201
+ {{#each this.tableColumns as |col index|}}
202
+ <div class="flex items-center space-x-1">
203
+ <input
204
+ type="text"
205
+ class="tb-input flex-1"
206
+ placeholder="Column label"
207
+ value={{col.label}}
208
+ {{on "change" (fn this.updateColumnLabel index)}}
209
+ />
210
+ <input
211
+ type="text"
212
+ class="tb-input flex-1"
213
+ placeholder="data.key"
214
+ value={{col.key}}
215
+ {{on "change" (fn this.updateColumnKey index)}}
216
+ />
217
+ <button
218
+ type="button"
219
+ class="tb-icon-btn text-red-400 hover:text-red-600"
220
+ title="Remove column"
221
+ {{on "click" (fn this.removeColumn index)}}
222
+ >
223
+ <FaIcon @icon="xmark" class="w-3 h-3" />
224
+ </button>
225
+ </div>
226
+ {{/each}}
227
+ {{#if (eq this.tableColumns.length 0)}}
228
+ <p class="text-xs text-gray-400 dark:text-gray-500 text-center py-2">No columns defined. Add one below.</p>
229
+ {{/if}}
230
+ <button
231
+ type="button"
232
+ class="w-full flex items-center justify-center space-x-1.5 px-2 py-1.5 rounded border border-dashed border-gray-300 dark:border-gray-600 text-xs text-gray-500 dark:text-gray-400 hover:border-blue-400 hover:text-blue-500 transition-colors"
233
+ {{on "click" this.addColumn}}
234
+ >
235
+ <FaIcon @icon="plus" class="w-3 h-3" />
236
+ <span>Add column</span>
237
+ </button>
238
+ </div>
239
+ </TemplateBuilder::PropertiesPanel::Section>
240
+
241
+ {{! Data Source }}
242
+ <TemplateBuilder::PropertiesPanel::Section
243
+ @title="Data Source"
244
+ @icon="database"
245
+ @isOpen={{this.isSectionOpen "table-data"}}
246
+ @onToggle={{fn this.toggleSection "table-data"}}
247
+ >
248
+ <div class="space-y-2">
249
+
250
+ {{! Two-mode toggle: Variable | Manual }}
251
+ <div class="flex rounded overflow-hidden border border-gray-200 dark:border-gray-700 text-xs">
252
+ <button
253
+ type="button"
254
+ class="flex-1 py-1 font-medium transition-colors
255
+ {{if (eq this.tableDataMode 'variable')
256
+ 'bg-blue-500 text-white'
257
+ 'bg-white dark:bg-gray-800 text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-700'}}"
258
+ {{on "click" (fn this.setTableDataMode "variable")}}
259
+ >Variable</button>
260
+ <button
261
+ type="button"
262
+ class="flex-1 py-1 font-medium border-l border-gray-200 dark:border-gray-700 transition-colors
263
+ {{if (eq this.tableDataMode 'manual')
264
+ 'bg-blue-500 text-white'
265
+ 'bg-white dark:bg-gray-800 text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-700'}}"
266
+ {{on "click" (fn this.setTableDataMode "manual")}}
267
+ >Manual</button>
268
+ </div>
269
+
270
+ {{! ── VARIABLE MODE ── }}
271
+ {{#if (eq this.tableDataMode "variable")}}
272
+ <TemplateBuilder::PropertiesPanel::Field @label="Data Variable">
273
+ <div class="flex space-x-1">
274
+ <input
275
+ type="text"
276
+ class="tb-input flex-1"
277
+ placeholder="{order.items}"
278
+ value={{this.element.data_source}}
279
+ {{on "change" (fn this.updateProp "data_source")}}
280
+ />
281
+ <button type="button" class="tb-icon-btn" title="Insert variable"
282
+ {{on "click" (fn this.openVariablePicker "data_source")}}>
283
+ <FaIcon @icon="code" class="w-3 h-3" />
284
+ </button>
285
+ </div>
286
+ </TemplateBuilder::PropertiesPanel::Field>
287
+ <p class="text-xs text-gray-400 dark:text-gray-500">The variable must resolve to an array of objects. Each object's keys should match the column keys defined above.</p>
288
+
289
+ {{! ── MANUAL MODE ── }}
290
+ {{else}}
291
+ <div class="space-y-1.5">
292
+ {{#each this.tableRows as |row rowIndex|}}
293
+ <div class="rounded border border-gray-200 dark:border-gray-700 overflow-hidden">
294
+ <div class="flex items-center justify-between px-2 py-1 bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700">
295
+ <span class="text-xs font-medium text-gray-600 dark:text-gray-400">Row {{add rowIndex 1}}</span>
296
+ <button
297
+ type="button"
298
+ class="text-red-400 hover:text-red-600"
299
+ title="Remove row"
300
+ {{on "click" (fn this.removeRow rowIndex)}}
301
+ >
302
+ <FaIcon @icon="xmark" class="w-3 h-3" />
303
+ </button>
304
+ </div>
305
+ <div class="p-1.5 space-y-1">
306
+ {{#each this.tableColumns as |col|}}
307
+ <div class="flex items-center space-x-1.5">
308
+ <span class="text-xs text-gray-400 dark:text-gray-500 w-16 shrink-0 truncate">{{col.label}}</span>
309
+ <input
310
+ type="text"
311
+ class="tb-input flex-1"
312
+ placeholder={{col.key}}
313
+ value={{get row col.key}}
314
+ {{on "change" (fn this.updateRowCell rowIndex col.key)}}
315
+ />
316
+ </div>
317
+ {{/each}}
318
+ {{#if (eq this.tableColumns.length 0)}}
319
+ <p class="text-xs text-gray-400 text-center py-1">Define columns first.</p>
320
+ {{/if}}
321
+ </div>
322
+ </div>
323
+ {{/each}}
324
+ {{#if (eq this.tableRows.length 0)}}
325
+ <p class="text-xs text-gray-400 dark:text-gray-500 text-center py-2">No rows yet.</p>
326
+ {{/if}}
327
+ <button
328
+ type="button"
329
+ class="w-full flex items-center justify-center space-x-1.5 px-2 py-1.5 rounded border border-dashed border-gray-300 dark:border-gray-600 text-xs text-gray-500 dark:text-gray-400 hover:border-blue-400 hover:text-blue-500 transition-colors"
330
+ {{on "click" this.addRow}}
331
+ >
332
+ <FaIcon @icon="plus" class="w-3 h-3" />
333
+ <span>Add row</span>
334
+ </button>
335
+ </div>
336
+ {{/if}}
337
+
338
+ </div>
339
+ </TemplateBuilder::PropertiesPanel::Section>
340
+
341
+ {{! Table Style }}
342
+ <TemplateBuilder::PropertiesPanel::Section
343
+ @title="Table Style"
344
+ @icon="palette"
345
+ @isOpen={{this.isSectionOpen "table-style"}}
346
+ @onToggle={{fn this.toggleSection "table-style"}}
347
+ >
348
+ <div class="grid grid-cols-2 gap-2">
349
+ <TemplateBuilder::PropertiesPanel::Field @label="Header BG">
350
+ <input type="color" class="tb-color-input" value={{this.element.header_background}} {{on "input" (fn this.updateProp "header_background")}} />
351
+ </TemplateBuilder::PropertiesPanel::Field>
352
+ <TemplateBuilder::PropertiesPanel::Field @label="Header Text">
353
+ <input type="color" class="tb-color-input" value={{this.element.header_color}} {{on "input" (fn this.updateProp "header_color")}} />
354
+ </TemplateBuilder::PropertiesPanel::Field>
355
+ <TemplateBuilder::PropertiesPanel::Field @label="Border Color">
356
+ <input type="color" class="tb-color-input" value={{this.element.border_color}} {{on "input" (fn this.updateProp "border_color")}} />
357
+ </TemplateBuilder::PropertiesPanel::Field>
358
+ <TemplateBuilder::PropertiesPanel::Field @label="Cell Padding">
359
+ <input type="number" class="tb-input" value={{this.element.cell_padding}} min="0" {{on "change" (fn this.updateNumericProp "cell_padding")}} />
360
+ </TemplateBuilder::PropertiesPanel::Field>
361
+ <TemplateBuilder::PropertiesPanel::Field @label="Row Alt Color" @span="2">
362
+ <input type="color" class="tb-color-input" value={{this.element.row_alt_color}} {{on "input" (fn this.updateProp "row_alt_color")}} />
363
+ </TemplateBuilder::PropertiesPanel::Field>
364
+ </div>
365
+ </TemplateBuilder::PropertiesPanel::Section>
366
+ {{/if}}
367
+
368
+ {{! QR CODE / BARCODE SECTION }}
369
+ {{#if (or this.isQrCode this.isBarcode)}}
370
+ <TemplateBuilder::PropertiesPanel::Section
371
+ @title={{if this.isQrCode "QR Code" "Barcode"}}
372
+ @icon={{if this.isQrCode "qrcode" "barcode"}}
373
+ @isOpen={{this.isSectionOpen "code"}}
374
+ @onToggle={{fn this.toggleSection "code"}}
375
+ >
376
+ <div class="space-y-2">
377
+ <TemplateBuilder::PropertiesPanel::Field @label="Value">
378
+ <div class="flex space-x-1">
379
+ <input type="text" class="tb-input flex-1" placeholder="{order.tracking_number}" value={{this.element.value}} {{on "change" (fn this.updateProp "value")}} />
380
+ <button type="button" class="tb-icon-btn" title="Insert variable" {{on "click" (fn this.openVariablePicker "value")}}>
381
+ <FaIcon @icon="code" class="w-3 h-3" />
382
+ </button>
383
+ </div>
384
+ </TemplateBuilder::PropertiesPanel::Field>
385
+ {{#if this.isBarcode}}
386
+ <TemplateBuilder::PropertiesPanel::Field @label="Format">
387
+ <select class="tb-input" {{on "change" (fn this.updateProp "barcode_format")}}>
388
+ <option value="CODE128" selected={{eq this.element.barcode_format "CODE128"}}>CODE128</option>
389
+ <option value="CODE39" selected={{eq this.element.barcode_format "CODE39"}}>CODE39</option>
390
+ <option value="EAN13" selected={{eq this.element.barcode_format "EAN13"}}>EAN-13</option>
391
+ <option value="EAN8" selected={{eq this.element.barcode_format "EAN8"}}>EAN-8</option>
392
+ <option value="UPC" selected={{eq this.element.barcode_format "UPC"}}>UPC</option>
393
+ </select>
394
+ </TemplateBuilder::PropertiesPanel::Field>
395
+ {{/if}}
396
+ </div>
397
+ </TemplateBuilder::PropertiesPanel::Section>
398
+ {{/if}}
399
+
400
+ {{! LINE SECTION }}
401
+ {{#if this.isLine}}
402
+ <TemplateBuilder::PropertiesPanel::Section
403
+ @title="Line"
404
+ @icon="minus"
405
+ @isOpen={{this.isSectionOpen "line"}}
406
+ @onToggle={{fn this.toggleSection "line"}}
407
+ >
408
+ <div class="grid grid-cols-2 gap-2">
409
+ <TemplateBuilder::PropertiesPanel::Field @label="Color">
410
+ <input type="color" class="tb-color-input" value={{this.element.color}} {{on "input" (fn this.updateProp "color")}} />
411
+ </TemplateBuilder::PropertiesPanel::Field>
412
+ <TemplateBuilder::PropertiesPanel::Field @label="Width (px)">
413
+ <input type="number" class="tb-input" value={{this.element.line_width}} min="1" {{on "change" (fn this.updateNumericProp "line_width")}} />
414
+ </TemplateBuilder::PropertiesPanel::Field>
415
+ <TemplateBuilder::PropertiesPanel::Field @label="Style" @span="2">
416
+ <select class="tb-input" {{on "change" (fn this.updateProp "line_style")}}>
417
+ {{#each this.lineStyleOptions as |opt|}}
418
+ <option value={{opt.value}} selected={{eq this.element.line_style opt.value}}>{{opt.label}}</option>
419
+ {{/each}}
420
+ </select>
421
+ </TemplateBuilder::PropertiesPanel::Field>
422
+ </div>
423
+ </TemplateBuilder::PropertiesPanel::Section>
424
+ {{/if}}
425
+
426
+ {{! SHAPE SECTION }}
427
+ {{#if this.isShape}}
428
+ <TemplateBuilder::PropertiesPanel::Section
429
+ @title="Shape"
430
+ @icon="shapes"
431
+ @isOpen={{this.isSectionOpen "shape"}}
432
+ @onToggle={{fn this.toggleSection "shape"}}
433
+ >
434
+ <div class="grid grid-cols-2 gap-2">
435
+ <TemplateBuilder::PropertiesPanel::Field @label="Shape" @span="2">
436
+ <select class="tb-input" {{on "change" (fn this.updateProp "shape")}}>
437
+ {{#each this.shapeOptions as |opt|}}
438
+ <option value={{opt.value}} selected={{eq this.element.shape opt.value}}>{{opt.label}}</option>
439
+ {{/each}}
440
+ </select>
441
+ </TemplateBuilder::PropertiesPanel::Field>
442
+ <TemplateBuilder::PropertiesPanel::Field @label="Fill Color">
443
+ <input type="color" class="tb-color-input" value={{this.element.background_color}} {{on "input" (fn this.updateProp "background_color")}} />
444
+ </TemplateBuilder::PropertiesPanel::Field>
445
+ <TemplateBuilder::PropertiesPanel::Field @label="Border Radius">
446
+ <input type="number" class="tb-input" value={{this.element.border_radius}} min="0" {{on "change" (fn this.updateNumericProp "border_radius")}} />
447
+ </TemplateBuilder::PropertiesPanel::Field>
448
+ </div>
449
+ </TemplateBuilder::PropertiesPanel::Section>
450
+ {{/if}}
451
+
452
+ {{! TEXT STYLE SECTION (text elements only) }}
453
+ {{#if this.isText}}
454
+ <TemplateBuilder::PropertiesPanel::Section
455
+ @title="Text"
456
+ @icon="font"
457
+ @isOpen={{this.isSectionOpen "text"}}
458
+ @onToggle={{fn this.toggleSection "text"}}
459
+ >
460
+ <div class="space-y-2">
461
+ <TemplateBuilder::PropertiesPanel::Field @label="Font Family">
462
+ <select class="tb-input" {{on "change" (fn this.updateProp "font_family")}}>
463
+ {{#each this.fontFamilyOptions as |opt|}}
464
+ <option value={{opt.value}} selected={{eq this.element.font_family opt.value}}>{{opt.label}}</option>
465
+ {{/each}}
466
+ </select>
467
+ </TemplateBuilder::PropertiesPanel::Field>
468
+ <div class="grid grid-cols-2 gap-2">
469
+ <TemplateBuilder::PropertiesPanel::Field @label="Size (px)">
470
+ <input type="number" class="tb-input" value={{this.element.font_size}} min="6" max="200" {{on "change" (fn this.updateNumericProp "font_size")}} />
471
+ </TemplateBuilder::PropertiesPanel::Field>
472
+ <TemplateBuilder::PropertiesPanel::Field @label="Weight">
473
+ <select class="tb-input" {{on "change" (fn this.updateProp "font_weight")}}>
474
+ {{#each this.fontWeightOptions as |opt|}}
475
+ <option value={{opt.value}} selected={{eq this.element.font_weight opt.value}}>{{opt.label}}</option>
476
+ {{/each}}
477
+ </select>
478
+ </TemplateBuilder::PropertiesPanel::Field>
479
+ <TemplateBuilder::PropertiesPanel::Field @label="Line Height">
480
+ <input type="number" class="tb-input" value={{this.element.line_height}} min="0.5" max="5" step="0.1" {{on "change" (fn this.updateNumericProp "line_height")}} />
481
+ </TemplateBuilder::PropertiesPanel::Field>
482
+ <TemplateBuilder::PropertiesPanel::Field @label="Letter Spacing">
483
+ <input type="number" class="tb-input" value={{this.element.letter_spacing}} step="0.5" {{on "change" (fn this.updateNumericProp "letter_spacing")}} />
484
+ </TemplateBuilder::PropertiesPanel::Field>
485
+ </div>
486
+ <TemplateBuilder::PropertiesPanel::Field @label="Color">
487
+ <input type="color" class="tb-color-input" value={{this.element.color}} {{on "input" (fn this.updateProp "color")}} />
488
+ </TemplateBuilder::PropertiesPanel::Field>
489
+ <TemplateBuilder::PropertiesPanel::Field @label="Alignment">
490
+ <div class="flex space-x-1">
491
+ {{#each this.textAlignOptions as |opt|}}
492
+ <button
493
+ type="button"
494
+ class="flex-1 py-1 rounded border text-xs {{if (eq this.element.text_align opt.value) 'border-blue-500 bg-blue-50 dark:bg-blue-900/20 text-blue-600' 'border-gray-200 dark:border-gray-700 text-gray-500 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800'}}"
495
+ {{on "click" (fn this.updateProp "text_align" opt.value)}}
496
+ >
497
+ <FaIcon @icon={{opt.icon}} class="w-3 h-3 mx-auto" />
498
+ </button>
499
+ {{/each}}
500
+ </div>
501
+ </TemplateBuilder::PropertiesPanel::Field>
502
+ <TemplateBuilder::PropertiesPanel::Field @label="Background">
503
+ <input type="color" class="tb-color-input" value={{this.element.background_color}} {{on "input" (fn this.updateProp "background_color")}} />
504
+ </TemplateBuilder::PropertiesPanel::Field>
505
+ <TemplateBuilder::PropertiesPanel::Field @label="Padding (px)">
506
+ <input type="number" class="tb-input" value={{this.element.padding}} min="0" {{on "change" (fn this.updateNumericProp "padding")}} />
507
+ </TemplateBuilder::PropertiesPanel::Field>
508
+ </div>
509
+ </TemplateBuilder::PropertiesPanel::Section>
510
+ {{/if}}
511
+
512
+ {{! BORDER SECTION (non-line elements) }}
513
+ {{#if this.hasBorderOptions}}
514
+ <TemplateBuilder::PropertiesPanel::Section
515
+ @title="Border"
516
+ @icon="border-all"
517
+ @isOpen={{this.isSectionOpen "border"}}
518
+ @onToggle={{fn this.toggleSection "border"}}
519
+ >
520
+ <div class="grid grid-cols-2 gap-2">
521
+ <TemplateBuilder::PropertiesPanel::Field @label="Width (px)">
522
+ <input type="number" class="tb-input" value={{this.element.border_width}} min="0" {{on "change" (fn this.updateNumericProp "border_width")}} />
523
+ </TemplateBuilder::PropertiesPanel::Field>
524
+ <TemplateBuilder::PropertiesPanel::Field @label="Color">
525
+ <input type="color" class="tb-color-input" value={{this.element.border_color}} {{on "input" (fn this.updateProp "border_color")}} />
526
+ </TemplateBuilder::PropertiesPanel::Field>
527
+ <TemplateBuilder::PropertiesPanel::Field @label="Style">
528
+ <select class="tb-input" {{on "change" (fn this.updateProp "border_style")}}>
529
+ {{#each this.lineStyleOptions as |opt|}}
530
+ <option value={{opt.value}} selected={{eq this.element.border_style opt.value}}>{{opt.label}}</option>
531
+ {{/each}}
532
+ </select>
533
+ </TemplateBuilder::PropertiesPanel::Field>
534
+ <TemplateBuilder::PropertiesPanel::Field @label="Radius (px)">
535
+ <input type="number" class="tb-input" value={{this.element.border_radius}} min="0" {{on "change" (fn this.updateNumericProp "border_radius")}} />
536
+ </TemplateBuilder::PropertiesPanel::Field>
537
+ </div>
538
+ </TemplateBuilder::PropertiesPanel::Section>
539
+ {{/if}}
540
+
541
+ {{else}}
542
+ {{! ============================================================ }}
543
+ {{! CANVAS / TEMPLATE SETTINGS (no element selected) }}
544
+ {{! ============================================================ }}
545
+
546
+ <div class="flex items-center justify-between px-3 py-2 border-b border-gray-200 dark:border-gray-700 sticky top-0 bg-white dark:bg-gray-900 z-10">
547
+ <span class="text-xs font-semibold text-gray-700 dark:text-gray-300">Canvas Settings</span>
548
+ </div>
549
+
550
+ <div class="p-3 space-y-3">
551
+ <TemplateBuilder::PropertiesPanel::Field @label="Template Name">
552
+ <input type="text" class="tb-input" value={{@template.name}} {{on "change" (fn this.updateTemplateProp "name")}} />
553
+ </TemplateBuilder::PropertiesPanel::Field>
554
+ <TemplateBuilder::PropertiesPanel::Field @label="Paper Size">
555
+ <select class="tb-input" {{on "change" (fn this.updateTemplateProp "paper_size")}}>
556
+ {{#each this.paperSizeOptions as |opt|}}
557
+ <option value={{opt.value}} selected={{eq @template.paper_size opt.value}}>{{opt.label}}</option>
558
+ {{/each}}
559
+ </select>
560
+ </TemplateBuilder::PropertiesPanel::Field>
561
+ <TemplateBuilder::PropertiesPanel::Field @label="Orientation">
562
+ <select class="tb-input" {{on "change" (fn this.updateTemplateProp "orientation")}}>
563
+ {{#each this.orientationOptions as |opt|}}
564
+ <option value={{opt.value}} selected={{eq @template.orientation opt.value}}>{{opt.label}}</option>
565
+ {{/each}}
566
+ </select>
567
+ </TemplateBuilder::PropertiesPanel::Field>
568
+ <TemplateBuilder::PropertiesPanel::Field @label="Background Color">
569
+ <input type="color" class="tb-color-input" value={{@template.background_color}} {{on "input" (fn this.updateTemplateProp "background_color")}} />
570
+ </TemplateBuilder::PropertiesPanel::Field>
571
+ <TemplateBuilder::PropertiesPanel::Field @label="Description">
572
+ <textarea class="tb-input w-full min-h-16 resize-none" {{on "input" (fn this.updateTemplateProp "description" value="target.value")}}>{{@template.description}}</textarea>
573
+ </TemplateBuilder::PropertiesPanel::Field>
574
+ </div>
575
+ {{/if}}
576
+ </div>