@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.
- package/dist/composables/index.js +388 -0
- package/dist/composables/index.js.map +1 -0
- package/package.json +41 -0
- package/src/composables/index.ts +6 -0
- package/src/composables/useForm.test.ts +229 -0
- package/src/composables/useForm.ts +130 -0
- package/src/composables/useFormValidation.test.ts +189 -0
- package/src/composables/useFormValidation.ts +83 -0
- package/src/composables/useModal.property.test.ts +164 -0
- package/src/composables/useModal.ts +43 -0
- package/src/composables/useNotifications.test.ts +166 -0
- package/src/composables/useNotifications.ts +81 -0
- package/src/composables/useTable.property.test.ts +198 -0
- package/src/composables/useTable.ts +134 -0
- package/src/composables/useTabs.property.test.ts +247 -0
- package/src/composables/useTabs.ts +101 -0
- package/src/data/Chart.demo.vue +340 -0
- package/src/data/Chart.md +525 -0
- package/src/data/Chart.vue +133 -0
- package/src/data/DataList.md +80 -0
- package/src/data/DataList.test.ts +69 -0
- package/src/data/DataList.vue +46 -0
- package/src/data/SearchableSelect.md +107 -0
- package/src/data/SearchableSelect.vue +124 -0
- package/src/data/Table.demo.vue +296 -0
- package/src/data/Table.md +588 -0
- package/src/data/Table.property.test.ts +548 -0
- package/src/data/Table.test.ts +562 -0
- package/src/data/Table.unit.test.ts +544 -0
- package/src/data/Table.vue +321 -0
- package/src/data/index.ts +5 -0
- package/src/domain/BrandCard.md +81 -0
- package/src/domain/BrandCard.vue +63 -0
- package/src/domain/BrandSelector.md +84 -0
- package/src/domain/BrandSelector.vue +65 -0
- package/src/domain/ProductBadge.md +60 -0
- package/src/domain/ProductBadge.vue +47 -0
- package/src/domain/UserAvatar.md +84 -0
- package/src/domain/UserAvatar.vue +60 -0
- package/src/domain/domain-components.property.test.ts +449 -0
- package/src/domain/index.ts +4 -0
- package/src/forms/DateRange.demo.vue +273 -0
- package/src/forms/DateRange.md +337 -0
- package/src/forms/DateRange.vue +110 -0
- package/src/forms/JsonSchemaForm.demo.vue +549 -0
- package/src/forms/JsonSchemaForm.md +112 -0
- package/src/forms/JsonSchemaForm.property.test.ts +817 -0
- package/src/forms/JsonSchemaForm.test.ts +601 -0
- package/src/forms/JsonSchemaForm.unit.test.ts +801 -0
- package/src/forms/JsonSchemaForm.vue +615 -0
- package/src/forms/index.ts +3 -0
- package/src/index.ts +17 -0
- package/src/navigation/Breadcrumbs.demo.vue +142 -0
- package/src/navigation/Breadcrumbs.md +102 -0
- package/src/navigation/Breadcrumbs.test.ts +69 -0
- package/src/navigation/Breadcrumbs.vue +58 -0
- package/src/navigation/Stepper.demo.vue +337 -0
- package/src/navigation/Stepper.md +174 -0
- package/src/navigation/Stepper.vue +146 -0
- package/src/navigation/Tabs.demo.vue +293 -0
- package/src/navigation/Tabs.md +163 -0
- package/src/navigation/Tabs.test.ts +176 -0
- package/src/navigation/Tabs.vue +104 -0
- package/src/navigation/index.ts +5 -0
- package/src/overlays/Alert.demo.vue +377 -0
- package/src/overlays/Alert.md +248 -0
- package/src/overlays/Alert.test.ts +166 -0
- package/src/overlays/Alert.vue +70 -0
- package/src/overlays/Drawer.md +140 -0
- package/src/overlays/Drawer.test.ts +92 -0
- package/src/overlays/Drawer.vue +76 -0
- package/src/overlays/Modal.demo.vue +149 -0
- package/src/overlays/Modal.md +385 -0
- package/src/overlays/Modal.test.ts +128 -0
- package/src/overlays/Modal.vue +86 -0
- package/src/overlays/Notification.md +150 -0
- package/src/overlays/Notification.test.ts +96 -0
- package/src/overlays/Notification.vue +58 -0
- 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>
|