@htlkg/components 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/dist/composables/index.js +388 -0
  2. package/dist/composables/index.js.map +1 -0
  3. package/package.json +41 -0
  4. package/src/composables/index.ts +6 -0
  5. package/src/composables/useForm.test.ts +229 -0
  6. package/src/composables/useForm.ts +130 -0
  7. package/src/composables/useFormValidation.test.ts +189 -0
  8. package/src/composables/useFormValidation.ts +83 -0
  9. package/src/composables/useModal.property.test.ts +164 -0
  10. package/src/composables/useModal.ts +43 -0
  11. package/src/composables/useNotifications.test.ts +166 -0
  12. package/src/composables/useNotifications.ts +81 -0
  13. package/src/composables/useTable.property.test.ts +198 -0
  14. package/src/composables/useTable.ts +134 -0
  15. package/src/composables/useTabs.property.test.ts +247 -0
  16. package/src/composables/useTabs.ts +101 -0
  17. package/src/data/Chart.demo.vue +340 -0
  18. package/src/data/Chart.md +525 -0
  19. package/src/data/Chart.vue +133 -0
  20. package/src/data/DataList.md +80 -0
  21. package/src/data/DataList.test.ts +69 -0
  22. package/src/data/DataList.vue +46 -0
  23. package/src/data/SearchableSelect.md +107 -0
  24. package/src/data/SearchableSelect.vue +124 -0
  25. package/src/data/Table.demo.vue +296 -0
  26. package/src/data/Table.md +588 -0
  27. package/src/data/Table.property.test.ts +548 -0
  28. package/src/data/Table.test.ts +562 -0
  29. package/src/data/Table.unit.test.ts +544 -0
  30. package/src/data/Table.vue +321 -0
  31. package/src/data/index.ts +5 -0
  32. package/src/domain/BrandCard.md +81 -0
  33. package/src/domain/BrandCard.vue +63 -0
  34. package/src/domain/BrandSelector.md +84 -0
  35. package/src/domain/BrandSelector.vue +65 -0
  36. package/src/domain/ProductBadge.md +60 -0
  37. package/src/domain/ProductBadge.vue +47 -0
  38. package/src/domain/UserAvatar.md +84 -0
  39. package/src/domain/UserAvatar.vue +60 -0
  40. package/src/domain/domain-components.property.test.ts +449 -0
  41. package/src/domain/index.ts +4 -0
  42. package/src/forms/DateRange.demo.vue +273 -0
  43. package/src/forms/DateRange.md +337 -0
  44. package/src/forms/DateRange.vue +110 -0
  45. package/src/forms/JsonSchemaForm.demo.vue +549 -0
  46. package/src/forms/JsonSchemaForm.md +112 -0
  47. package/src/forms/JsonSchemaForm.property.test.ts +817 -0
  48. package/src/forms/JsonSchemaForm.test.ts +601 -0
  49. package/src/forms/JsonSchemaForm.unit.test.ts +801 -0
  50. package/src/forms/JsonSchemaForm.vue +615 -0
  51. package/src/forms/index.ts +3 -0
  52. package/src/index.ts +17 -0
  53. package/src/navigation/Breadcrumbs.demo.vue +142 -0
  54. package/src/navigation/Breadcrumbs.md +102 -0
  55. package/src/navigation/Breadcrumbs.test.ts +69 -0
  56. package/src/navigation/Breadcrumbs.vue +58 -0
  57. package/src/navigation/Stepper.demo.vue +337 -0
  58. package/src/navigation/Stepper.md +174 -0
  59. package/src/navigation/Stepper.vue +146 -0
  60. package/src/navigation/Tabs.demo.vue +293 -0
  61. package/src/navigation/Tabs.md +163 -0
  62. package/src/navigation/Tabs.test.ts +176 -0
  63. package/src/navigation/Tabs.vue +104 -0
  64. package/src/navigation/index.ts +5 -0
  65. package/src/overlays/Alert.demo.vue +377 -0
  66. package/src/overlays/Alert.md +248 -0
  67. package/src/overlays/Alert.test.ts +166 -0
  68. package/src/overlays/Alert.vue +70 -0
  69. package/src/overlays/Drawer.md +140 -0
  70. package/src/overlays/Drawer.test.ts +92 -0
  71. package/src/overlays/Drawer.vue +76 -0
  72. package/src/overlays/Modal.demo.vue +149 -0
  73. package/src/overlays/Modal.md +385 -0
  74. package/src/overlays/Modal.test.ts +128 -0
  75. package/src/overlays/Modal.vue +86 -0
  76. package/src/overlays/Notification.md +150 -0
  77. package/src/overlays/Notification.test.ts +96 -0
  78. package/src/overlays/Notification.vue +58 -0
  79. package/src/overlays/index.ts +4 -0
@@ -0,0 +1,110 @@
1
+ <template>
2
+ <uiDateRange
3
+ :id="id"
4
+ :loading="loading"
5
+ :literals="literals"
6
+ :values="internalValues"
7
+ :color="color"
8
+ :error="error"
9
+ :disabled="disabled"
10
+ :label="label"
11
+ :placeholder="placeholder"
12
+ @uiDateRangeButtonClicked="handleSearch"
13
+ />
14
+ </template>
15
+
16
+ <script setup lang="ts">
17
+ import { ref, watch, computed } from 'vue';
18
+ import { uiDateRange } from '@hotelinking/ui';
19
+
20
+ export interface DateRangeValue {
21
+ from: Date | string;
22
+ to: Date | string;
23
+ }
24
+
25
+ export interface DateRangeLiterals {
26
+ from: string;
27
+ to: string;
28
+ search: string;
29
+ }
30
+
31
+ interface Props {
32
+ id?: string;
33
+ modelValue?: DateRangeValue;
34
+ literals?: DateRangeLiterals;
35
+ loading?: boolean;
36
+ color?: 'primary' | 'secondary' | 'light' | 'green' | 'yellow' | 'red' | 'black' | 'gray' | 'white';
37
+ error?: string;
38
+ disabled?: boolean;
39
+ label?: string;
40
+ placeholder?: string;
41
+ }
42
+
43
+ const props = withDefaults(defineProps<Props>(), {
44
+ id: () => `date-range-${Math.random().toString(36).substr(2, 9)}`,
45
+ loading: false,
46
+ literals: () => ({
47
+ from: 'From',
48
+ to: 'To',
49
+ search: 'Search'
50
+ }),
51
+ modelValue: () => ({
52
+ from: '',
53
+ to: new Date().toISOString().slice(0, 16)
54
+ })
55
+ });
56
+
57
+ const emit = defineEmits<{
58
+ 'update:modelValue': [value: DateRangeValue | undefined];
59
+ 'search': [value: DateRangeValue | undefined];
60
+ 'change': [value: DateRangeValue | undefined];
61
+ }>();
62
+
63
+ // Internal state
64
+ const internalValues = ref<DateRangeValue>({
65
+ from: props.modelValue?.from || '',
66
+ to: props.modelValue?.to || new Date().toISOString().slice(0, 16)
67
+ });
68
+
69
+ // Watch for external changes
70
+ watch(() => props.modelValue, (newValue) => {
71
+ if (newValue) {
72
+ internalValues.value = {
73
+ from: newValue.from || '',
74
+ to: newValue.to || new Date().toISOString().slice(0, 16)
75
+ };
76
+ }
77
+ }, { deep: true });
78
+
79
+ // Handle search button click
80
+ function handleSearch(dates: DateRangeValue | undefined) {
81
+ emit('update:modelValue', dates);
82
+ emit('search', dates);
83
+ emit('change', dates);
84
+ }
85
+
86
+ // Exposed methods
87
+ function reset() {
88
+ const resetValue: DateRangeValue = {
89
+ from: '',
90
+ to: new Date().toISOString().slice(0, 16)
91
+ };
92
+ internalValues.value = resetValue;
93
+ emit('update:modelValue', resetValue);
94
+ }
95
+
96
+ function getValue() {
97
+ return internalValues.value;
98
+ }
99
+
100
+ function setValue(value: DateRangeValue) {
101
+ internalValues.value = value;
102
+ emit('update:modelValue', value);
103
+ }
104
+
105
+ defineExpose({
106
+ reset,
107
+ getValue,
108
+ setValue
109
+ });
110
+ </script>
@@ -0,0 +1,549 @@
1
+ <script setup lang="ts">
2
+ import { ref } from 'vue';
3
+ import { JsonSchemaForm } from '@htlkg/components/forms';
4
+
5
+ // Comprehensive JSON Schema showcasing different field types
6
+ const schema = ref({
7
+ type: 'object',
8
+ title: 'Product Configuration',
9
+ description: 'Configure your product settings with various field types',
10
+ properties: {
11
+ // Basic string input
12
+ productName: {
13
+ type: 'string',
14
+ title: 'Product Name',
15
+ description: 'Enter a unique product name',
16
+ minLength: 3,
17
+ maxLength: 50
18
+ },
19
+
20
+ // Email format
21
+ contactEmail: {
22
+ type: 'string',
23
+ title: 'Contact Email',
24
+ format: 'email',
25
+ description: 'Primary contact email address'
26
+ },
27
+
28
+ // URL format
29
+ websiteUrl: {
30
+ type: 'string',
31
+ title: 'Website URL',
32
+ format: 'uri',
33
+ description: 'Product website or documentation URL'
34
+ },
35
+
36
+ // Number with range
37
+ price: {
38
+ type: 'number',
39
+ title: 'Price (USD)',
40
+ description: 'Product price in US dollars',
41
+ minimum: 0,
42
+ maximum: 10000
43
+ },
44
+
45
+ // Integer with range (good for slider)
46
+ quantity: {
47
+ type: 'integer',
48
+ title: 'Stock Quantity',
49
+ description: 'Available inventory',
50
+ minimum: 0,
51
+ maximum: 1000,
52
+ default: 100
53
+ },
54
+
55
+ // Enum/Select dropdown
56
+ category: {
57
+ type: 'string',
58
+ title: 'Product Category',
59
+ enum: ['Electronics', 'Clothing', 'Food', 'Books', 'Toys', 'Other'],
60
+ description: 'Select the product category'
61
+ },
62
+
63
+ // Boolean toggle
64
+ isActive: {
65
+ type: 'boolean',
66
+ title: 'Active Status',
67
+ description: 'Enable or disable this product',
68
+ default: true
69
+ },
70
+
71
+ // Boolean checkbox
72
+ featured: {
73
+ type: 'boolean',
74
+ title: 'Featured Product',
75
+ description: 'Display on homepage',
76
+ default: false
77
+ },
78
+
79
+ // Textarea (long string)
80
+ description: {
81
+ type: 'string',
82
+ title: 'Product Description',
83
+ description: 'Detailed product description',
84
+ minLength: 10,
85
+ maxLength: 500
86
+ },
87
+
88
+ // Array of strings (tags)
89
+ tags: {
90
+ type: 'array',
91
+ title: 'Product Tags',
92
+ description: 'Add searchable tags',
93
+ items: {
94
+ type: 'string'
95
+ },
96
+ minItems: 1,
97
+ maxItems: 10
98
+ },
99
+
100
+ // Array of objects (variants)
101
+ variants: {
102
+ type: 'array',
103
+ title: 'Product Variants',
104
+ description: 'Different versions of this product',
105
+ items: {
106
+ type: 'object',
107
+ properties: {
108
+ name: {
109
+ type: 'string',
110
+ title: 'Variant Name'
111
+ },
112
+ sku: {
113
+ type: 'string',
114
+ title: 'SKU'
115
+ },
116
+ price: {
117
+ type: 'number',
118
+ title: 'Price'
119
+ },
120
+ inStock: {
121
+ type: 'boolean',
122
+ title: 'In Stock'
123
+ }
124
+ },
125
+ required: ['name', 'sku']
126
+ }
127
+ },
128
+
129
+ // Nested object
130
+ shipping: {
131
+ type: 'object',
132
+ title: 'Shipping Information',
133
+ properties: {
134
+ weight: {
135
+ type: 'number',
136
+ title: 'Weight (kg)',
137
+ minimum: 0
138
+ },
139
+ dimensions: {
140
+ type: 'object',
141
+ title: 'Dimensions (cm)',
142
+ properties: {
143
+ length: { type: 'number', title: 'Length' },
144
+ width: { type: 'number', title: 'Width' },
145
+ height: { type: 'number', title: 'Height' }
146
+ }
147
+ },
148
+ freeShipping: {
149
+ type: 'boolean',
150
+ title: 'Free Shipping',
151
+ default: false
152
+ }
153
+ }
154
+ },
155
+
156
+ // Advanced settings (collapsible)
157
+ advanced: {
158
+ type: 'object',
159
+ title: 'Advanced Settings',
160
+ properties: {
161
+ enableAnalytics: {
162
+ type: 'boolean',
163
+ title: 'Enable Analytics',
164
+ default: true
165
+ },
166
+ customCss: {
167
+ type: 'string',
168
+ title: 'Custom CSS',
169
+ description: 'Additional styling'
170
+ },
171
+ apiKey: {
172
+ type: 'string',
173
+ title: 'API Key',
174
+ description: 'Integration API key'
175
+ }
176
+ }
177
+ }
178
+ },
179
+ required: ['productName', 'contactEmail', 'category', 'price']
180
+ });
181
+
182
+ // UI Schema for better rendering
183
+ const uiSchema = ref({
184
+ 'ui:order': [
185
+ 'productName',
186
+ 'contactEmail',
187
+ 'websiteUrl',
188
+ 'category',
189
+ 'price',
190
+ 'quantity',
191
+ 'isActive',
192
+ 'featured',
193
+ 'description',
194
+ 'tags',
195
+ 'variants',
196
+ 'shipping',
197
+ 'advanced'
198
+ ],
199
+ productName: {
200
+ 'ui:placeholder': 'e.g., Premium Wireless Headphones'
201
+ },
202
+ contactEmail: {
203
+ 'ui:placeholder': 'contact@example.com'
204
+ },
205
+ websiteUrl: {
206
+ 'ui:placeholder': 'https://example.com/product'
207
+ },
208
+ description: {
209
+ 'ui:widget': 'textarea',
210
+ 'ui:placeholder': 'Enter a detailed product description...'
211
+ },
212
+ quantity: {
213
+ 'ui:widget': 'slider',
214
+ 'ui:help': 'Use slider to set inventory quantity'
215
+ },
216
+ tags: {
217
+ 'ui:widget': 'array',
218
+ 'ui:help': 'Press Enter to add each tag'
219
+ },
220
+ variants: {
221
+ 'ui:widget': 'array'
222
+ },
223
+ shipping: {
224
+ 'ui:widget': 'card',
225
+ 'ui:collapsed': false
226
+ },
227
+ advanced: {
228
+ 'ui:widget': 'collapsible',
229
+ 'ui:collapsed': true,
230
+ apiKey: {
231
+ 'ui:widget': 'password'
232
+ }
233
+ }
234
+ });
235
+
236
+ // Form data with sample values
237
+ const formData = ref({
238
+ productName: 'Premium Wireless Headphones',
239
+ contactEmail: 'sales@example.com',
240
+ websiteUrl: 'https://example.com/headphones',
241
+ price: 299.99,
242
+ quantity: 150,
243
+ category: 'Electronics',
244
+ isActive: true,
245
+ featured: false,
246
+ description: 'High-quality wireless headphones with active noise cancellation, 30-hour battery life, and premium sound quality.',
247
+ tags: ['wireless', 'audio', 'premium'],
248
+ variants: [
249
+ { name: 'Black', sku: 'WH-001-BLK', price: 299.99, inStock: true },
250
+ { name: 'Silver', sku: 'WH-001-SLV', price: 299.99, inStock: true }
251
+ ],
252
+ shipping: {
253
+ weight: 0.5,
254
+ dimensions: {
255
+ length: 20,
256
+ width: 18,
257
+ height: 10
258
+ },
259
+ freeShipping: true
260
+ },
261
+ advanced: {
262
+ enableAnalytics: true,
263
+ customCss: '',
264
+ apiKey: ''
265
+ }
266
+ });
267
+
268
+ // Interactive controls
269
+ const loading = ref(false);
270
+ const showValidation = ref(true);
271
+
272
+ // Form state
273
+ const submitResult = ref<{ success: boolean; message: string } | null>(null);
274
+ const validationErrors = ref<Array<{ field: string; message: string }>>([]);
275
+
276
+ // Predefined schema examples
277
+ const schemaExamples = {
278
+ product: {
279
+ name: 'Product Configuration',
280
+ schema: schema.value,
281
+ uiSchema: uiSchema.value,
282
+ data: formData.value
283
+ },
284
+ simple: {
285
+ name: 'Simple Form',
286
+ schema: {
287
+ type: 'object',
288
+ title: 'User Profile',
289
+ description: 'Basic user information',
290
+ properties: {
291
+ name: { type: 'string', title: 'Full Name', minLength: 1 },
292
+ email: { type: 'string', title: 'Email', format: 'email', minLength: 1 },
293
+ age: { type: 'number', title: 'Age', minimum: 18, maximum: 120 },
294
+ subscribe: { type: 'boolean', title: 'Subscribe to Newsletter' }
295
+ },
296
+ required: ['name', 'email']
297
+ },
298
+ uiSchema: {},
299
+ data: { name: '', email: '', age: 25, subscribe: false }
300
+ },
301
+ advanced: {
302
+ name: 'Advanced Settings',
303
+ schema: {
304
+ type: 'object',
305
+ title: 'API Configuration',
306
+ description: 'Configure API integration settings',
307
+ properties: {
308
+ apiUrl: { type: 'string', title: 'API URL', format: 'uri', minLength: 1 },
309
+ apiKey: { type: 'string', title: 'API Key', minLength: 1 },
310
+ timeout: { type: 'integer', title: 'Timeout (seconds)', minimum: 5, maximum: 300 },
311
+ retries: { type: 'integer', title: 'Max Retries', minimum: 0, maximum: 10 },
312
+ enableLogging: { type: 'boolean', title: 'Enable Logging' },
313
+ logLevel: {
314
+ type: 'string',
315
+ title: 'Log Level',
316
+ enum: ['debug', 'info', 'warn', 'error']
317
+ }
318
+ },
319
+ required: ['apiUrl', 'apiKey']
320
+ },
321
+ uiSchema: {
322
+ apiKey: { 'ui:widget': 'password' },
323
+ timeout: { 'ui:widget': 'slider' },
324
+ retries: { 'ui:widget': 'slider' }
325
+ },
326
+ data: {
327
+ apiUrl: 'https://api.example.com',
328
+ apiKey: '',
329
+ timeout: 30,
330
+ retries: 3,
331
+ enableLogging: true,
332
+ logLevel: 'info'
333
+ }
334
+ }
335
+ };
336
+
337
+ const currentExample = ref<keyof typeof schemaExamples>('product');
338
+
339
+ // Switch schema example
340
+ const switchExample = (exampleKey: keyof typeof schemaExamples) => {
341
+ currentExample.value = exampleKey;
342
+ const example = schemaExamples[exampleKey];
343
+ schema.value = example.schema;
344
+ uiSchema.value = example.uiSchema;
345
+ formData.value = { ...example.data };
346
+ submitResult.value = null;
347
+ validationErrors.value = [];
348
+ };
349
+
350
+ // Handle form submission
351
+ const handleSubmit = (values: any) => {
352
+ loading.value = true;
353
+
354
+ // Simulate API call
355
+ setTimeout(() => {
356
+ loading.value = false;
357
+ submitResult.value = {
358
+ success: true,
359
+ message: 'Configuration saved successfully!'
360
+ };
361
+ validationErrors.value = [];
362
+ console.log('Form submitted:', values);
363
+ }, 1500);
364
+ };
365
+
366
+ // Handle validation errors
367
+ const handleValidationError = (errors: Array<{ field: string; message: string }>) => {
368
+ validationErrors.value = errors;
369
+ submitResult.value = {
370
+ success: false,
371
+ message: 'Please fix the validation errors'
372
+ };
373
+ };
374
+
375
+ // Reset to empty form
376
+ const resetForm = () => {
377
+ const example = schemaExamples[currentExample.value];
378
+ formData.value = { ...example.data };
379
+ submitResult.value = null;
380
+ validationErrors.value = [];
381
+ };
382
+
383
+ // Toggle loading state
384
+ const toggleLoading = () => {
385
+ loading.value = !loading.value;
386
+ };
387
+ </script>
388
+
389
+ <template>
390
+ <div>
391
+ <!-- Interactive Controls Panel -->
392
+ <div class="mb-6 p-4 bg-purple-50 border border-purple-200 rounded-lg">
393
+ <h4 class="text-sm font-semibold text-purple-900 mb-3">🎮 Interactive Controls</h4>
394
+
395
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
396
+ <!-- Schema Examples -->
397
+ <div>
398
+ <label class="block text-sm font-medium text-gray-700 mb-2">
399
+ Schema Example:
400
+ </label>
401
+ <div class="flex gap-2">
402
+ <button
403
+ v-for="(example, key) in schemaExamples"
404
+ :key="key"
405
+ @click="switchExample(key as keyof typeof schemaExamples)"
406
+ :class="[
407
+ 'px-3 py-2 text-sm rounded-md transition-colors',
408
+ currentExample === key
409
+ ? 'bg-purple-600 text-white'
410
+ : 'bg-white text-gray-700 border border-gray-300 hover:bg-gray-50'
411
+ ]"
412
+ >
413
+ {{ example.name }}
414
+ </button>
415
+ </div>
416
+ </div>
417
+
418
+ <!-- State Controls -->
419
+ <div>
420
+ <label class="block text-sm font-medium text-gray-700 mb-2">
421
+ Component State:
422
+ </label>
423
+ <div class="flex gap-2">
424
+ <button
425
+ @click="toggleLoading"
426
+ :class="[
427
+ 'px-3 py-2 text-sm rounded-md transition-colors',
428
+ loading
429
+ ? 'bg-blue-600 text-white'
430
+ : 'bg-white text-gray-700 border border-gray-300 hover:bg-gray-50'
431
+ ]"
432
+ >
433
+ {{ loading ? '⏳ Loading ON' : '⚡ Loading OFF' }}
434
+ </button>
435
+ <button
436
+ @click="resetForm"
437
+ class="px-3 py-2 text-sm bg-white text-gray-700 border border-gray-300 rounded-md hover:bg-gray-50 transition-colors"
438
+ >
439
+ 🔄 Reset Form
440
+ </button>
441
+ </div>
442
+ </div>
443
+ </div>
444
+ </div>
445
+
446
+ <!-- Result Display -->
447
+ <div
448
+ v-if="submitResult"
449
+ class="mb-6 p-4 rounded border"
450
+ :class="
451
+ submitResult.success
452
+ ? 'bg-green-50 border-green-200'
453
+ : 'bg-red-50 border-red-200'
454
+ "
455
+ >
456
+ <p
457
+ class="text-sm font-medium"
458
+ :class="submitResult.success ? 'text-green-800' : 'text-red-800'"
459
+ >
460
+ {{ submitResult.message }}
461
+ </p>
462
+ <ul v-if="validationErrors.length > 0" class="mt-2 text-sm text-red-700">
463
+ <li v-for="error in validationErrors" :key="error.field">
464
+ {{ error.field }}: {{ error.message }}
465
+ </li>
466
+ </ul>
467
+ </div>
468
+
469
+ <!-- Field Types Info -->
470
+ <div class="mb-6 p-4 bg-blue-50 border border-blue-200 rounded-lg">
471
+ <h4 class="text-sm font-semibold text-blue-900 mb-2">📋 Field Types Demonstrated:</h4>
472
+ <div class="grid grid-cols-2 md:grid-cols-3 gap-2 text-xs text-blue-800">
473
+ <div>✓ Text inputs</div>
474
+ <div>✓ Email validation</div>
475
+ <div>✓ URL validation</div>
476
+ <div>✓ Number inputs</div>
477
+ <div>✓ Range sliders</div>
478
+ <div>✓ Select dropdowns</div>
479
+ <div>✓ Boolean toggles</div>
480
+ <div>✓ Checkboxes</div>
481
+ <div>✓ Textarea</div>
482
+ <div>✓ Arrays (tags)</div>
483
+ <div>✓ Array of objects</div>
484
+ <div>✓ Nested objects</div>
485
+ <div>✓ Collapsible sections</div>
486
+ <div>✓ Password fields</div>
487
+ <div>✓ Required validation</div>
488
+ </div>
489
+ </div>
490
+
491
+ <!-- JSON Schema Form Component -->
492
+ <div class="bg-white p-6 rounded-lg border">
493
+ <JsonSchemaForm
494
+ v-model="formData"
495
+ :schema="schema"
496
+ :ui-schema="uiSchema"
497
+ :loading="loading"
498
+ @submit="handleSubmit"
499
+ @validation-error="handleValidationError"
500
+ >
501
+ <template #actions>
502
+ <div class="flex justify-end gap-3">
503
+ <button
504
+ type="button"
505
+ @click="resetForm"
506
+ :disabled="loading"
507
+ class="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
508
+ >
509
+ Reset
510
+ </button>
511
+ <button
512
+ type="submit"
513
+ :disabled="loading"
514
+ class="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
515
+ >
516
+ {{ loading ? 'Saving...' : 'Save Configuration' }}
517
+ </button>
518
+ </div>
519
+ </template>
520
+ </JsonSchemaForm>
521
+ </div>
522
+
523
+ <!-- Form Data Display -->
524
+ <div class="mt-6 p-4 bg-gray-50 rounded border">
525
+ <h4 class="text-sm font-semibold text-gray-900 mb-2">Current Form Data:</h4>
526
+ <pre class="text-xs text-gray-700 overflow-x-auto">{{ JSON.stringify(formData, null, 2) }}</pre>
527
+ </div>
528
+
529
+ <!-- Schema Display -->
530
+ <div class="mt-6 p-4 bg-gray-50 rounded border">
531
+ <details>
532
+ <summary class="text-sm font-semibold text-gray-900 mb-2 cursor-pointer">
533
+ View JSON Schema Definition
534
+ </summary>
535
+ <pre class="text-xs text-gray-700 overflow-x-auto mt-2">{{ JSON.stringify(schema, null, 2) }}</pre>
536
+ </details>
537
+ </div>
538
+
539
+ <!-- UI Schema Display -->
540
+ <div class="mt-6 p-4 bg-gray-50 rounded border">
541
+ <details>
542
+ <summary class="text-sm font-semibold text-gray-900 mb-2 cursor-pointer">
543
+ View UI Schema Configuration
544
+ </summary>
545
+ <pre class="text-xs text-gray-700 overflow-x-auto mt-2">{{ JSON.stringify(uiSchema, null, 2) }}</pre>
546
+ </details>
547
+ </div>
548
+ </div>
549
+ </template>