@sonicjs-cms/core 2.0.0-alpha.9 → 2.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.
@@ -1,783 +0,0 @@
1
- // src/templates/form.template.ts
2
- function renderForm(data) {
3
- return `
4
- <form
5
- ${data.id ? `id="${data.id}"` : ""}
6
- ${data.hxPost ? `hx-post="${data.hxPost}"` : data.hxPut ? `hx-put="${data.hxPut}"` : data.action ? `action="${data.action}"` : ""}
7
- ${data.hxTarget ? `hx-target="${data.hxTarget}"` : ""}
8
- method="${data.method || "POST"}"
9
- class="${data.className || "space-y-6"}"
10
- ${data.fields.some((f) => f.type === "file") ? 'enctype="multipart/form-data"' : ""}
11
- >
12
- ${data.title ? `
13
- <div class="mb-6">
14
- <h2 class="text-lg font-medium text-gray-1">${data.title}</h2>
15
- ${data.description ? `<p class="mt-1 text-sm text-gray-4">${data.description}</p>` : ""}
16
- </div>
17
- ` : ""}
18
-
19
- <div id="form-messages"></div>
20
-
21
- ${data.fields.map((field) => renderFormField(field)).join("")}
22
-
23
- <div class="flex justify-between items-center pt-6 border-t border-gray-7">
24
- <div class="flex space-x-4">
25
- ${data.submitButtons.map((button) => `
26
- <button
27
- type="${button.type || "submit"}"
28
- ${button.name ? `name="${button.name}"` : ""}
29
- ${button.value ? `value="${button.value}"` : ""}
30
- ${button.onclick ? `onclick="${button.onclick}"` : ""}
31
- class="btn ${button.className || "btn-primary"}"
32
- >
33
- ${button.label}
34
- </button>
35
- `).join("")}
36
- </div>
37
- </div>
38
- </form>
39
- `;
40
- }
41
- function renderFormField(field) {
42
- const fieldId = `field-${field.name}`;
43
- const required = field.required ? "required" : "";
44
- const readonly = field.readonly ? "readonly" : "";
45
- const placeholder = field.placeholder ? `placeholder="${field.placeholder}"` : "";
46
- let fieldHTML = "";
47
- switch (field.type) {
48
- case "text":
49
- case "email":
50
- case "number":
51
- case "date":
52
- fieldHTML = `
53
- <input
54
- type="${field.type === "date" ? "datetime-local" : field.type}"
55
- id="${fieldId}"
56
- name="${field.name}"
57
- value="${field.value || ""}"
58
- class="form-input ${field.className || ""}"
59
- ${placeholder}
60
- ${required}
61
- ${readonly}
62
- ${field.validation?.min !== void 0 ? `min="${field.validation.min}"` : ""}
63
- ${field.validation?.max !== void 0 ? `max="${field.validation.max}"` : ""}
64
- ${field.validation?.pattern ? `pattern="${field.validation.pattern}"` : ""}
65
- >
66
- `;
67
- break;
68
- case "textarea":
69
- fieldHTML = `
70
- <textarea
71
- id="${fieldId}"
72
- name="${field.name}"
73
- class="form-textarea ${field.className || ""}"
74
- rows="${field.rows || 4}"
75
- ${placeholder}
76
- ${required}
77
- >${field.value || ""}</textarea>
78
- `;
79
- break;
80
- case "rich_text":
81
- const uniqueId = `${field.name}-${Date.now()}`;
82
- fieldHTML = `
83
- <div class="markdown-field">
84
- <textarea id="${uniqueId}" name="${field.name}" class="form-textarea" rows="8">${field.value || ""}</textarea>
85
- <script>
86
- if (typeof EasyMDE !== 'undefined') {
87
- new EasyMDE({
88
- element: document.getElementById('${uniqueId}'),
89
- minHeight: '300px',
90
- spellChecker: false,
91
- status: ['autosave', 'lines', 'words', 'cursor'],
92
- autosave: {
93
- enabled: true,
94
- uniqueId: '${uniqueId}',
95
- delay: 1000
96
- },
97
- renderingConfig: {
98
- singleLineBreaks: false,
99
- codeSyntaxHighlighting: true
100
- }
101
- });
102
- }
103
- </script>
104
- </div>
105
- `;
106
- break;
107
- case "select":
108
- fieldHTML = `
109
- <select
110
- id="${fieldId}"
111
- name="${field.name}"
112
- class="form-input ${field.className || ""}"
113
- ${required}
114
- >
115
- ${field.options ? field.options.map((option) => `
116
- <option value="${option.value}" ${option.selected || field.value === option.value ? "selected" : ""}>
117
- ${option.label}
118
- </option>
119
- `).join("") : ""}
120
- </select>
121
- `;
122
- break;
123
- case "multi_select":
124
- fieldHTML = `
125
- <select
126
- id="${fieldId}"
127
- name="${field.name}"
128
- class="form-input ${field.className || ""}"
129
- multiple
130
- ${required}
131
- >
132
- ${field.options ? field.options.map((option) => `
133
- <option value="${option.value}" ${option.selected ? "selected" : ""}>
134
- ${option.label}
135
- </option>
136
- `).join("") : ""}
137
- </select>
138
- `;
139
- break;
140
- case "checkbox":
141
- fieldHTML = `
142
- <input
143
- type="checkbox"
144
- id="${fieldId}"
145
- name="${field.name}"
146
- value="1"
147
- class="size-4 rounded border border-white/15 bg-white/5 checked:border-transparent checked:bg-white disabled:opacity-50 focus:outline-none focus:ring-2 focus:ring-white/20 focus:ring-offset-2 ${field.className || ""}"
148
- ${field.value ? "checked" : ""}
149
- ${required}
150
- >
151
- <label for="${fieldId}" class="ml-2 text-sm text-white">${field.label}</label>
152
- `;
153
- break;
154
- default:
155
- fieldHTML = `
156
- <input
157
- type="text"
158
- id="${fieldId}"
159
- name="${field.name}"
160
- value="${field.value || ""}"
161
- class="form-input ${field.className || ""}"
162
- ${placeholder}
163
- ${required}
164
- >
165
- `;
166
- break;
167
- }
168
- if (field.type === "checkbox") {
169
- return `
170
- <div class="form-group">
171
- <div class="flex items-center">
172
- ${fieldHTML}
173
- </div>
174
- ${field.helpText ? `<p class="text-sm text-zinc-500 dark:text-zinc-400 mt-1 ml-6">${field.helpText}</p>` : ""}
175
- </div>
176
- `;
177
- }
178
- return `
179
- <div class="form-group">
180
- <label for="${fieldId}" class="form-label">
181
- ${field.label}${field.required ? " *" : ""}
182
- </label>
183
- ${fieldHTML}
184
- ${field.helpText ? `<p class="text-sm text-zinc-500 dark:text-zinc-400 mt-1">${field.helpText}</p>` : ""}
185
- </div>
186
- `;
187
- }
188
-
189
- // src/templates/table.template.ts
190
- function renderTable(data) {
191
- const tableId = data.tableId || `table-${Math.random().toString(36).substr(2, 9)}`;
192
- if (data.rows.length === 0) {
193
- return `
194
- <div class="rounded-xl bg-white dark:bg-zinc-900 shadow-sm ring-1 ring-zinc-950/5 dark:ring-white/10 p-8 text-center">
195
- <div class="text-zinc-500 dark:text-zinc-400">
196
- <svg class="mx-auto h-12 w-12 text-zinc-400 dark:text-zinc-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
197
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
198
- </svg>
199
- <p class="mt-2 text-sm text-zinc-500 dark:text-zinc-400">${data.emptyMessage || "No data available"}</p>
200
- </div>
201
- </div>
202
- `;
203
- }
204
- return `
205
- <div class="${data.className || ""}" id="${tableId}">
206
- ${data.title ? `
207
- <div class="px-4 sm:px-0 mb-4">
208
- <h3 class="text-base font-semibold text-zinc-950 dark:text-white">${data.title}</h3>
209
- </div>
210
- ` : ""}
211
- <div class="overflow-x-auto">
212
- <table class="min-w-full sortable-table">
213
- <thead>
214
- <tr>
215
- ${data.selectable ? `
216
- <th class="px-4 py-3.5 text-center sm:pl-0">
217
- <div class="flex items-center justify-center">
218
- <div class="group grid size-4 grid-cols-1">
219
- <input type="checkbox" id="select-all-${tableId}" class="col-start-1 row-start-1 appearance-none rounded border border-white/10 bg-white/5 checked:border-cyan-500 checked:bg-cyan-500 indeterminate:border-cyan-500 indeterminate:bg-cyan-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-cyan-500 disabled:border-white/5 disabled:bg-white/10 disabled:checked:bg-white/10 forced-colors:appearance-auto row-checkbox" />
220
- <svg viewBox="0 0 14 14" fill="none" class="pointer-events-none col-start-1 row-start-1 size-3.5 self-center justify-self-center stroke-white group-has-[:disabled]:stroke-white/25">
221
- <path d="M3 8L6 11L11 3.5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="opacity-0 group-has-[:checked]:opacity-100" />
222
- <path d="M3 7H11" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="opacity-0 group-has-[:indeterminate]:opacity-100" />
223
- </svg>
224
- </div>
225
- </div>
226
- </th>
227
- ` : ""}
228
- ${data.columns.map((column, index) => {
229
- const isFirst = index === 0 && !data.selectable;
230
- const isLast = index === data.columns.length - 1;
231
- return `
232
- <th class="px-4 py-3.5 text-left text-sm font-semibold text-zinc-950 dark:text-white ${isFirst ? "sm:pl-0" : ""} ${isLast ? "sm:pr-0" : ""} ${column.className || ""}">
233
- ${column.sortable ? `
234
- <button
235
- class="flex items-center gap-x-2 hover:text-zinc-700 dark:hover:text-zinc-300 transition-colors sort-btn text-left"
236
- data-column="${column.key}"
237
- data-sort-type="${column.sortType || "string"}"
238
- data-sort-direction="none"
239
- onclick="sortTable('${tableId}', '${column.key}', '${column.sortType || "string"}')"
240
- >
241
- <span>${column.label}</span>
242
- <div class="sort-icons flex flex-col">
243
- <svg class="w-3 h-3 sort-up opacity-30" fill="currentColor" viewBox="0 0 20 20">
244
- <path fill-rule="evenodd" d="M14.707 12.707a1 1 0 01-1.414 0L10 9.414l-3.293 3.293a1 1 0 01-1.414-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 010 1.414z" clip-rule="evenodd" />
245
- </svg>
246
- <svg class="w-3 h-3 sort-down opacity-30 -mt-1" fill="currentColor" viewBox="0 0 20 20">
247
- <path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" />
248
- </svg>
249
- </div>
250
- </button>
251
- ` : column.label}
252
- </th>
253
- `;
254
- }).join("")}
255
- </tr>
256
- </thead>
257
- <tbody>
258
- ${data.rows.map((row) => {
259
- if (!row) return "";
260
- const clickableClass = data.rowClickable ? "cursor-pointer" : "";
261
- const clickHandler = data.rowClickable && data.rowClickUrl ? `onclick="window.location.href='${data.rowClickUrl(row)}'"` : "";
262
- return `
263
- <tr class="group border-t border-zinc-950/5 dark:border-white/5 hover:bg-gradient-to-r hover:from-cyan-50/50 hover:via-blue-50/30 hover:to-purple-50/50 dark:hover:from-cyan-900/20 dark:hover:via-blue-900/10 dark:hover:to-purple-900/20 hover:shadow-sm hover:shadow-cyan-500/5 dark:hover:shadow-cyan-400/5 transition-all duration-300 ${clickableClass}" ${clickHandler}>
264
- ${data.selectable ? `
265
- <td class="px-4 py-4 sm:pl-0" onclick="event.stopPropagation()">
266
- <div class="flex items-center justify-center">
267
- <div class="group grid size-4 grid-cols-1">
268
- <input type="checkbox" value="${row.id || ""}" class="col-start-1 row-start-1 appearance-none rounded border border-white/10 bg-white/5 checked:border-cyan-500 checked:bg-cyan-500 indeterminate:border-cyan-500 indeterminate:bg-cyan-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-cyan-500 disabled:border-white/5 disabled:bg-white/10 disabled:checked:bg-white/10 forced-colors:appearance-auto row-checkbox" />
269
- <svg viewBox="0 0 14 14" fill="none" class="pointer-events-none col-start-1 row-start-1 size-3.5 self-center justify-self-center stroke-white group-has-[:disabled]:stroke-white/25">
270
- <path d="M3 8L6 11L11 3.5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="opacity-0 group-has-[:checked]:opacity-100" />
271
- <path d="M3 7H11" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="opacity-0 group-has-[:indeterminate]:opacity-100" />
272
- </svg>
273
- </div>
274
- </div>
275
- </td>
276
- ` : ""}
277
- ${data.columns.map((column, colIndex) => {
278
- const value = row[column.key];
279
- const displayValue = column.render ? column.render(value, row) : value;
280
- const stopPropagation = column.key === "actions" ? 'onclick="event.stopPropagation()"' : "";
281
- const isFirst = colIndex === 0 && !data.selectable;
282
- const isLast = colIndex === data.columns.length - 1;
283
- return `
284
- <td class="px-4 py-4 text-sm text-zinc-500 dark:text-zinc-400 ${isFirst ? "sm:pl-0 font-medium text-zinc-950 dark:text-white" : ""} ${isLast ? "sm:pr-0" : ""} ${column.className || ""}" ${stopPropagation}>
285
- ${displayValue || ""}
286
- </td>
287
- `;
288
- }).join("")}
289
- </tr>
290
- `;
291
- }).join("")}
292
- </tbody>
293
- </table>
294
- </div>
295
-
296
- <script>
297
- // Table sorting functionality
298
- window.sortTable = function(tableId, column, sortType) {
299
- const tableContainer = document.getElementById(tableId);
300
- const table = tableContainer.querySelector('.sortable-table');
301
- const tbody = table.querySelector('tbody');
302
- const rows = Array.from(tbody.querySelectorAll('tr'));
303
- const headerBtn = table.querySelector(\`[data-column="\${column}"]\`);
304
-
305
- // Get current sort direction
306
- let direction = headerBtn.getAttribute('data-sort-direction');
307
-
308
- // Reset all sort indicators
309
- table.querySelectorAll('.sort-btn').forEach(btn => {
310
- btn.setAttribute('data-sort-direction', 'none');
311
- btn.querySelectorAll('.sort-up, .sort-down').forEach(icon => {
312
- icon.classList.add('opacity-30');
313
- icon.classList.remove('opacity-100', 'text-zinc-950', 'dark:text-white');
314
- });
315
- });
316
-
317
- // Determine new direction
318
- if (direction === 'none' || direction === 'desc') {
319
- direction = 'asc';
320
- } else {
321
- direction = 'desc';
322
- }
323
-
324
- // Update current header
325
- headerBtn.setAttribute('data-sort-direction', direction);
326
- const upIcon = headerBtn.querySelector('.sort-up');
327
- const downIcon = headerBtn.querySelector('.sort-down');
328
-
329
- if (direction === 'asc') {
330
- upIcon.classList.remove('opacity-30');
331
- upIcon.classList.add('opacity-100', 'text-zinc-950', 'dark:text-white');
332
- downIcon.classList.add('opacity-30');
333
- downIcon.classList.remove('opacity-100', 'text-zinc-950', 'dark:text-white');
334
- } else {
335
- downIcon.classList.remove('opacity-30');
336
- downIcon.classList.add('opacity-100', 'text-zinc-950', 'dark:text-white');
337
- upIcon.classList.add('opacity-30');
338
- upIcon.classList.remove('opacity-100', 'text-zinc-950', 'dark:text-white');
339
- }
340
-
341
- // Find column index (accounting for potential select column)
342
- const headers = Array.from(table.querySelectorAll('th'));
343
- const selectableOffset = table.querySelector('input[id^="select-all"]') ? 1 : 0;
344
- const columnIndex = headers.findIndex(th => th.querySelector(\`[data-column="\${column}"]\`)) - selectableOffset;
345
-
346
- // Sort rows
347
- rows.sort((a, b) => {
348
- const aCell = a.children[columnIndex + selectableOffset];
349
- const bCell = b.children[columnIndex + selectableOffset];
350
-
351
- if (!aCell || !bCell) return 0;
352
-
353
- let aValue = aCell.textContent.trim();
354
- let bValue = bCell.textContent.trim();
355
-
356
- // Handle different sort types
357
- switch (sortType) {
358
- case 'number':
359
- aValue = parseFloat(aValue.replace(/[^0-9.-]/g, '')) || 0;
360
- bValue = parseFloat(bValue.replace(/[^0-9.-]/g, '')) || 0;
361
- break;
362
- case 'date':
363
- aValue = new Date(aValue).getTime() || 0;
364
- bValue = new Date(bValue).getTime() || 0;
365
- break;
366
- case 'boolean':
367
- aValue = aValue.toLowerCase() === 'true' || aValue.toLowerCase() === 'published' || aValue.toLowerCase() === 'active';
368
- bValue = bValue.toLowerCase() === 'true' || bValue.toLowerCase() === 'published' || bValue.toLowerCase() === 'active';
369
- break;
370
- default: // string
371
- aValue = aValue.toLowerCase();
372
- bValue = bValue.toLowerCase();
373
- }
374
-
375
- if (aValue < bValue) return direction === 'asc' ? -1 : 1;
376
- if (aValue > bValue) return direction === 'asc' ? 1 : -1;
377
- return 0;
378
- });
379
-
380
- // Re-append sorted rows
381
- rows.forEach(row => tbody.appendChild(row));
382
- };
383
-
384
- // Select all functionality
385
- document.addEventListener('DOMContentLoaded', function() {
386
- document.querySelectorAll('[id^="select-all"]').forEach(selectAll => {
387
- selectAll.addEventListener('change', function() {
388
- const tableId = this.id.replace('select-all-', '');
389
- const table = document.getElementById(tableId);
390
- if (table) {
391
- const checkboxes = table.querySelectorAll('.row-checkbox');
392
- checkboxes.forEach(checkbox => {
393
- checkbox.checked = this.checked;
394
- });
395
- }
396
- });
397
- });
398
- });
399
- </script>
400
- </div>
401
- `;
402
- }
403
-
404
- // src/templates/pagination.template.ts
405
- function renderPagination(data) {
406
- const shouldShowPagination = data.totalPages > 1 || data.showPageSizeSelector !== false && data.totalItems > 0;
407
- if (!shouldShowPagination) {
408
- return "";
409
- }
410
- const buildUrl = (page, limit) => {
411
- const params = new URLSearchParams(data.queryParams || {});
412
- params.set("page", page.toString());
413
- if (data.itemsPerPage !== 20) {
414
- params.set("limit", data.itemsPerPage.toString());
415
- }
416
- return `${data.baseUrl}?${params.toString()}`;
417
- };
418
- const buildPageSizeUrl = (limit) => {
419
- const params = new URLSearchParams(data.queryParams || {});
420
- params.set("page", "1");
421
- params.set("limit", limit.toString());
422
- return `${data.baseUrl}?${params.toString()}`;
423
- };
424
- const generatePageNumbers = () => {
425
- const maxNumbers = data.maxPageNumbers || 5;
426
- const half = Math.floor(maxNumbers / 2);
427
- let start = Math.max(1, data.currentPage - half);
428
- let end = Math.min(data.totalPages, start + maxNumbers - 1);
429
- if (end - start + 1 < maxNumbers) {
430
- start = Math.max(1, end - maxNumbers + 1);
431
- }
432
- const pages = [];
433
- for (let i = start; i <= end; i++) {
434
- pages.push(i);
435
- }
436
- return pages;
437
- };
438
- return `
439
- <div class="rounded-xl bg-white dark:bg-zinc-900 shadow-sm ring-1 ring-zinc-950/5 dark:ring-white/10 px-4 py-3 flex items-center justify-between mt-4">
440
- ${data.totalPages > 1 ? `
441
- <!-- Mobile Pagination -->
442
- <div class="flex-1 flex justify-between sm:hidden">
443
- ${data.currentPage > 1 ? `
444
- <a href="${buildUrl(data.currentPage - 1)}" class="inline-flex items-center rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm font-semibold text-zinc-950 dark:text-white shadow-sm ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 hover:bg-zinc-50 dark:hover:bg-zinc-700 transition-colors">
445
- Previous
446
- </a>
447
- ` : `
448
- <span class="inline-flex items-center rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm font-semibold text-zinc-400 dark:text-zinc-600 shadow-sm ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 opacity-50 cursor-not-allowed">Previous</span>
449
- `}
450
-
451
- ${data.currentPage < data.totalPages ? `
452
- <a href="${buildUrl(data.currentPage + 1)}" class="inline-flex items-center rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm font-semibold text-zinc-950 dark:text-white shadow-sm ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 hover:bg-zinc-50 dark:hover:bg-zinc-700 transition-colors">
453
- Next
454
- </a>
455
- ` : `
456
- <span class="inline-flex items-center rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm font-semibold text-zinc-400 dark:text-zinc-600 shadow-sm ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 opacity-50 cursor-not-allowed">Next</span>
457
- `}
458
- </div>
459
- ` : ""}
460
-
461
- <!-- Desktop Pagination -->
462
- <div class="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
463
- <div class="flex items-center gap-4">
464
- <p class="text-sm text-zinc-500 dark:text-zinc-400">
465
- Showing <span class="font-medium text-zinc-950 dark:text-white">${data.startItem}</span> to
466
- <span class="font-medium text-zinc-950 dark:text-white">${data.endItem}</span> of
467
- <span class="font-medium text-zinc-950 dark:text-white">${data.totalItems}</span> results
468
- </p>
469
- ${data.showPageSizeSelector !== false ? `
470
- <div class="flex items-center gap-2">
471
- <label for="page-size" class="text-sm text-zinc-500 dark:text-zinc-400">Per page:</label>
472
- <div class="grid grid-cols-1">
473
- <select
474
- id="page-size"
475
- onchange="window.location.href = this.value"
476
- class="col-start-1 row-start-1 w-full appearance-none rounded-md bg-white/5 dark:bg-white/5 py-1.5 pl-3 pr-8 text-sm text-zinc-950 dark:text-white outline outline-1 -outline-offset-1 outline-zinc-500/30 dark:outline-zinc-400/30 *:bg-white dark:*:bg-zinc-800 focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-zinc-500 dark:focus-visible:outline-zinc-400"
477
- >
478
- ${(data.pageSizeOptions || [10, 20, 50, 100]).map((size) => `
479
- <option value="${buildPageSizeUrl(size)}" ${size === data.itemsPerPage ? "selected" : ""}>
480
- ${size}
481
- </option>
482
- `).join("")}
483
- </select>
484
- <svg viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" class="pointer-events-none col-start-1 row-start-1 mr-2 size-4 self-center justify-self-end text-zinc-600 dark:text-zinc-400">
485
- <path d="M4.22 6.22a.75.75 0 0 1 1.06 0L8 8.94l2.72-2.72a.75.75 0 1 1 1.06 1.06l-3.25 3.25a.75.75 0 0 1-1.06 0L4.22 7.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd" fill-rule="evenodd" />
486
- </svg>
487
- </div>
488
- </div>
489
- ` : ""}
490
- </div>
491
-
492
- ${data.totalPages > 1 ? `
493
- <div class="flex items-center gap-x-1">
494
- <!-- Previous Button -->
495
- ${data.currentPage > 1 ? `
496
- <a href="${buildUrl(data.currentPage - 1)}"
497
- class="rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm font-semibold text-zinc-950 dark:text-white shadow-sm ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 hover:bg-zinc-50 dark:hover:bg-zinc-700 transition-colors">
498
- Previous
499
- </a>
500
- ` : ""}
501
-
502
- <!-- Page Numbers -->
503
- ${data.showPageNumbers !== false ? `
504
- <!-- First page if not in range -->
505
- ${(() => {
506
- const pageNumbers = generatePageNumbers();
507
- const firstPage = pageNumbers.length > 0 ? pageNumbers[0] : null;
508
- return firstPage && firstPage > 1 ? `
509
- <a href="${buildUrl(1)}"
510
- class="rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm font-semibold text-zinc-950 dark:text-white shadow-sm ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 hover:bg-zinc-50 dark:hover:bg-zinc-700 transition-colors">
511
- 1
512
- </a>
513
- ${firstPage > 2 ? `
514
- <span class="px-2 text-sm text-zinc-500 dark:text-zinc-400">...</span>
515
- ` : ""}
516
- ` : "";
517
- })()}
518
-
519
- <!-- Page number buttons -->
520
- ${generatePageNumbers().map((pageNum) => `
521
- ${pageNum === data.currentPage ? `
522
- <span class="rounded-lg bg-zinc-950 dark:bg-white px-3 py-2 text-sm font-semibold text-white dark:text-zinc-950">
523
- ${pageNum}
524
- </span>
525
- ` : `
526
- <a href="${buildUrl(pageNum)}"
527
- class="rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm font-semibold text-zinc-950 dark:text-white shadow-sm ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 hover:bg-zinc-50 dark:hover:bg-zinc-700 transition-colors">
528
- ${pageNum}
529
- </a>
530
- `}
531
- `).join("")}
532
-
533
- <!-- Last page if not in range -->
534
- ${(() => {
535
- const pageNumbers = generatePageNumbers();
536
- const lastPageNum = pageNumbers.length > 0 ? pageNumbers.slice(-1)[0] : null;
537
- return lastPageNum && lastPageNum < data.totalPages ? `
538
- ${lastPageNum < data.totalPages - 1 ? `
539
- <span class="px-2 text-sm text-zinc-500 dark:text-zinc-400">...</span>
540
- ` : ""}
541
- <a href="${buildUrl(data.totalPages)}"
542
- class="rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm font-semibold text-zinc-950 dark:text-white shadow-sm ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 hover:bg-zinc-50 dark:hover:bg-zinc-700 transition-colors">
543
- ${data.totalPages}
544
- </a>
545
- ` : "";
546
- })()}
547
- ` : ""}
548
-
549
- <!-- Next Button -->
550
- ${data.currentPage < data.totalPages ? `
551
- <a href="${buildUrl(data.currentPage + 1)}"
552
- class="rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm font-semibold text-zinc-950 dark:text-white shadow-sm ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 hover:bg-zinc-50 dark:hover:bg-zinc-700 transition-colors">
553
- Next
554
- </a>
555
- ` : ""}
556
- </div>
557
- ` : ""}
558
- </div>
559
- </div>
560
- `;
561
- }
562
-
563
- // src/templates/alert.template.ts
564
- function renderAlert(data) {
565
- const typeClasses = {
566
- success: "bg-green-50 dark:bg-green-500/10 border border-green-600/20 dark:border-green-500/20",
567
- error: "bg-error/10 border border-red-600/20 dark:border-red-500/20",
568
- warning: "bg-amber-50 dark:bg-amber-500/10 border border-amber-600/20 dark:border-amber-500/20",
569
- info: "bg-blue-50 dark:bg-blue-500/10 border border-blue-600/20 dark:border-blue-500/20"
570
- };
571
- const iconClasses = {
572
- success: "text-green-600 dark:text-green-400",
573
- error: "text-red-600 dark:text-red-400",
574
- warning: "text-amber-600 dark:text-amber-400",
575
- info: "text-blue-600 dark:text-blue-400"
576
- };
577
- const textClasses = {
578
- success: "text-green-900 dark:text-green-300",
579
- error: "text-red-900 dark:text-red-300",
580
- warning: "text-amber-900 dark:text-amber-300",
581
- info: "text-blue-900 dark:text-blue-300"
582
- };
583
- const messageTextClasses = {
584
- success: "text-green-700 dark:text-green-400",
585
- error: "text-red-700 dark:text-red-400",
586
- warning: "text-amber-700 dark:text-amber-400",
587
- info: "text-blue-700 dark:text-blue-400"
588
- };
589
- const icons = {
590
- success: `<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" />`,
591
- error: `<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd" />`,
592
- warning: `<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />`,
593
- info: `<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd" />`
594
- };
595
- return `
596
- <div class="rounded-lg p-4 ${typeClasses[data.type]} ${data.className || ""}" ${data.dismissible ? 'id="dismissible-alert"' : ""}>
597
- <div class="flex">
598
- ${data.icon !== false ? `
599
- <div class="flex-shrink-0">
600
- <svg class="h-5 w-5 ${iconClasses[data.type]}" viewBox="0 0 20 20" fill="currentColor">
601
- ${icons[data.type]}
602
- </svg>
603
- </div>
604
- ` : ""}
605
- <div class="${data.icon !== false ? "ml-3" : ""}">
606
- ${data.title ? `
607
- <h3 class="text-sm font-semibold ${textClasses[data.type]}">
608
- ${data.title}
609
- </h3>
610
- ` : ""}
611
- <div class="${data.title ? "mt-1 text-sm" : "text-sm"} ${messageTextClasses[data.type]}">
612
- <p>${data.message}</p>
613
- </div>
614
- </div>
615
- ${data.dismissible ? `
616
- <div class="ml-auto pl-3">
617
- <div class="-mx-1.5 -my-1.5">
618
- <button
619
- type="button"
620
- class="inline-flex rounded-md p-1.5 ${iconClasses[data.type]} hover:bg-opacity-20 focus:outline-none focus:ring-2 focus:ring-offset-2"
621
- onclick="document.getElementById('dismissible-alert').remove()"
622
- >
623
- <span class="sr-only">Dismiss</span>
624
- <svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
625
- <path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd" />
626
- </svg>
627
- </button>
628
- </div>
629
- </div>
630
- ` : ""}
631
- </div>
632
- </div>
633
- `;
634
- }
635
-
636
- // src/templates/confirmation-dialog.template.ts
637
- function renderConfirmationDialog(options) {
638
- const {
639
- id,
640
- title,
641
- message,
642
- confirmText = "Confirm",
643
- cancelText = "Cancel",
644
- confirmClass = "bg-red-500 hover:bg-red-400",
645
- iconColor = "red",
646
- onConfirm = ""
647
- } = options;
648
- const iconColorClasses = {
649
- red: "bg-red-500/10 text-red-400",
650
- yellow: "bg-yellow-500/10 text-yellow-400",
651
- blue: "bg-blue-500/10 text-blue-400"
652
- };
653
- return `
654
- <el-dialog>
655
- <dialog
656
- id="${id}"
657
- aria-labelledby="${id}-title"
658
- class="fixed inset-0 m-0 size-auto max-h-none max-w-none overflow-y-auto bg-transparent p-0 backdrop:bg-transparent"
659
- >
660
- <el-dialog-backdrop class="fixed inset-0 bg-gray-900/50 transition-opacity data-[closed]:opacity-0 data-[enter]:duration-300 data-[leave]:duration-200 data-[enter]:ease-out data-[leave]:ease-in"></el-dialog-backdrop>
661
-
662
- <div tabindex="0" class="flex min-h-full items-end justify-center p-4 text-center focus:outline focus:outline-0 sm:items-center sm:p-0">
663
- <el-dialog-panel class="relative transform overflow-hidden rounded-lg bg-gray-800 px-4 pb-4 pt-5 text-left shadow-xl outline outline-1 -outline-offset-1 outline-white/10 transition-all data-[closed]:translate-y-4 data-[closed]:opacity-0 data-[enter]:duration-300 data-[leave]:duration-200 data-[enter]:ease-out data-[leave]:ease-in sm:my-8 sm:w-full sm:max-w-lg sm:p-6 data-[closed]:sm:translate-y-0 data-[closed]:sm:scale-95">
664
- <div class="sm:flex sm:items-start">
665
- <div class="mx-auto flex size-12 shrink-0 items-center justify-center rounded-full ${iconColorClasses[iconColor]} sm:mx-0 sm:size-10">
666
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" data-slot="icon" aria-hidden="true" class="size-6">
667
- <path d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126ZM12 15.75h.007v.008H12v-.008Z" stroke-linecap="round" stroke-linejoin="round" />
668
- </svg>
669
- </div>
670
- <div class="mt-3 text-center sm:ml-4 sm:mt-0 sm:text-left">
671
- <h3 id="${id}-title" class="text-base font-semibold text-white">${title}</h3>
672
- <div class="mt-2">
673
- <p class="text-sm text-gray-400">${message}</p>
674
- </div>
675
- </div>
676
- </div>
677
- <div class="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
678
- <button
679
- type="button"
680
- onclick="${onConfirm}; document.getElementById('${id}').close()"
681
- command="close"
682
- commandfor="${id}"
683
- class="confirm-button inline-flex w-full justify-center rounded-md ${confirmClass} px-3 py-2 text-sm font-semibold text-white sm:ml-3 sm:w-auto"
684
- >
685
- ${confirmText}
686
- </button>
687
- <button
688
- type="button"
689
- command="close"
690
- commandfor="${id}"
691
- class="mt-3 inline-flex w-full justify-center rounded-md bg-white/10 px-3 py-2 text-sm font-semibold text-white ring-1 ring-inset ring-white/5 hover:bg-white/20 sm:mt-0 sm:w-auto"
692
- >
693
- ${cancelText}
694
- </button>
695
- </div>
696
- </el-dialog-panel>
697
- </div>
698
- </dialog>
699
- </el-dialog>
700
- `;
701
- }
702
- function getConfirmationDialogScript() {
703
- return `
704
- <script src="https://cdn.jsdelivr.net/npm/@tailwindplus/elements@1" type="module"></script>
705
- <script>
706
- function showConfirmDialog(dialogId) {
707
- const dialog = document.getElementById(dialogId);
708
- if (dialog) {
709
- dialog.showModal();
710
- }
711
- }
712
- </script>
713
- `;
714
- }
715
-
716
- // src/templates/filter-bar.template.ts
717
- function renderFilterBar(data) {
718
- return `
719
- <div class="rounded-xl bg-white dark:bg-zinc-900 shadow-sm ring-1 ring-zinc-950/5 dark:ring-white/10 p-6 mb-6">
720
- <form id="filter-form" class="flex flex-wrap gap-4 items-center">
721
- ${data.filters.map((filter) => `
722
- <div class="flex items-center space-x-2">
723
- <label class="text-sm font-medium text-zinc-500 dark:text-zinc-400">${filter.label}:</label>
724
- <select
725
- name="${filter.name}"
726
- class="rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 focus:ring-2 focus:ring-blue-600 dark:focus:ring-blue-500 focus:outline-none transition-colors"
727
- onchange="updateFilters()"
728
- >
729
- ${filter.options.map((option) => `
730
- <option value="${option.value}" ${option.selected ? "selected" : ""}>
731
- ${option.label}
732
- </option>
733
- `).join("")}
734
- </select>
735
- </div>
736
- `).join("")}
737
-
738
- ${data.actions && data.actions.length > 0 ? `
739
- <div class="flex items-center space-x-2 ml-auto">
740
- ${data.actions.map((action) => `
741
- <button
742
- type="button"
743
- class="inline-flex items-center rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm font-semibold text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 hover:bg-zinc-50 dark:hover:bg-zinc-700 transition-colors"
744
- ${action.onclick ? `onclick="${action.onclick}"` : ""}
745
- ${action.hxGet ? `hx-get="${action.hxGet}"` : ""}
746
- ${action.hxTarget ? `hx-target="${action.hxTarget}"` : ""}
747
- >
748
- ${action.label}
749
- </button>
750
- `).join("")}
751
- </div>
752
- ` : ""}
753
- </form>
754
-
755
- <script>
756
- function updateFilters() {
757
- const form = document.getElementById('filter-form');
758
- const formData = new FormData(form);
759
- const params = new URLSearchParams(window.location.search);
760
-
761
- // Update params with form values
762
- for (const [key, value] of formData.entries()) {
763
- if (value) {
764
- params.set(key, value);
765
- } else {
766
- params.delete(key);
767
- }
768
- }
769
-
770
- // Reset to page 1 when filters change
771
- params.set('page', '1');
772
-
773
- // Update URL and reload
774
- window.location.href = window.location.pathname + '?' + params.toString();
775
- }
776
- </script>
777
- </div>
778
- `;
779
- }
780
-
781
- export { getConfirmationDialogScript, renderAlert, renderConfirmationDialog, renderFilterBar, renderForm, renderFormField, renderPagination, renderTable };
782
- //# sourceMappingURL=chunk-KRJMGD4E.js.map
783
- //# sourceMappingURL=chunk-KRJMGD4E.js.map