@a-vision-software/vue-input-components 1.1.29 → 1.1.31

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.
@@ -0,0 +1,27 @@
1
+ import { createRouter, createWebHashHistory } from 'vue-router'
2
+ import DashboardView from '../views/DashboardView.vue'
3
+ import TextInputTestView from '../views/TextInputTestView.vue'
4
+ import FileUploadTestView from '../views/FileUploadTestView.vue'
5
+
6
+ const router = createRouter({
7
+ history: createWebHashHistory(),
8
+ routes: [
9
+ {
10
+ path: '/',
11
+ name: 'dashboard',
12
+ component: DashboardView,
13
+ },
14
+ {
15
+ path: '/text-input-test',
16
+ name: 'text-input-test',
17
+ component: TextInputTestView,
18
+ },
19
+ {
20
+ path: '/file-upload-test',
21
+ name: 'file-upload-test',
22
+ component: FileUploadTestView,
23
+ },
24
+ ],
25
+ })
26
+
27
+ export default router
package/src/types.d.ts ADDED
@@ -0,0 +1,23 @@
1
+ declare module '@a-vision-software/vue-input-components' {
2
+ import { DefineComponent } from 'vue'
3
+
4
+ export const TextInput: DefineComponent<{
5
+ modelValue?: string
6
+ label?: string
7
+ placeholder?: string
8
+ required?: boolean
9
+ disabled?: boolean
10
+ error?: string
11
+ }>
12
+
13
+ export const FileUpload: DefineComponent<{
14
+ modelValue?: File[]
15
+ maxFiles?: number
16
+ maxFileSize?: number
17
+ accept?: string
18
+ uploadUrl?: string
19
+ required?: boolean
20
+ disabled?: boolean
21
+ error?: string
22
+ }>
23
+ }
@@ -0,0 +1,57 @@
1
+ <template>
2
+ <div class="dashboard">
3
+ <router-link to="/text-input-test" class="tile">
4
+ <font-awesome-icon icon="keyboard" size="2x" />
5
+ <span>Text Inputs Test</span>
6
+ </router-link>
7
+ <router-link to="/file-upload-test" class="tile">
8
+ <font-awesome-icon icon="upload" size="2x" />
9
+ <span>File Upload Test</span>
10
+ </router-link>
11
+ </div>
12
+ </template>
13
+
14
+ <script setup lang="ts"></script>
15
+
16
+ <style scoped>
17
+ .dashboard {
18
+ min-height: 100vh;
19
+ display: grid;
20
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
21
+ gap: 2rem;
22
+ place-items: center;
23
+ padding: 2rem;
24
+ }
25
+
26
+ .tile {
27
+ display: grid;
28
+ grid-template-rows: auto auto;
29
+ gap: 1rem;
30
+ justify-items: center;
31
+ align-items: center;
32
+ padding: 2rem;
33
+ background: #f8f9fa;
34
+ border: 1px solid #e9ecef;
35
+ border-radius: 0.75rem;
36
+ cursor: pointer;
37
+ transition: all 0.2s ease;
38
+ width: 200px;
39
+ text-decoration: none;
40
+ }
41
+
42
+ .tile:hover {
43
+ background: #e9ecef;
44
+ transform: translateY(-2px);
45
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
46
+ }
47
+
48
+ .tile svg {
49
+ color: #495057;
50
+ }
51
+
52
+ .tile span {
53
+ font-weight: 500;
54
+ color: #495057;
55
+ text-align: center;
56
+ }
57
+ </style>
@@ -0,0 +1,177 @@
1
+ <template>
2
+ <div class="file-upload-test">
3
+ <div class="header">
4
+ <router-link to="/" class="back-link">
5
+ <font-awesome-icon icon="home" />
6
+ <span>Back to Dashboard</span>
7
+ </router-link>
8
+ <h1>File Upload Test</h1>
9
+ </div>
10
+ <div class="file-upload-grid">
11
+ <div class="upload-section">
12
+ <h2>Basic Upload</h2>
13
+ <FileUpload
14
+ icon="upload"
15
+ upload-url="https://httpbin.org/post"
16
+ @upload-complete="handleUploadComplete"
17
+ @upload-error="handleUploadError"
18
+ />
19
+ <div
20
+ v-if="uploadStatus.basic && uploadStatus.basic.message"
21
+ :class="['status-message', uploadStatus.basic.type]"
22
+ >
23
+ {{ uploadStatus.basic.message }}
24
+ </div>
25
+ </div>
26
+ <div class="upload-section">
27
+ <h2>Image Upload</h2>
28
+ <FileUpload
29
+ icon="image"
30
+ @files-selected="handleFilesSelected"
31
+ @start-upload="handleStartUpload"
32
+ />
33
+ </div>
34
+ <div class="upload-section">
35
+ <h2>Document Upload</h2>
36
+ <FileUpload icon="file" />
37
+ </div>
38
+ </div>
39
+ </div>
40
+ </template>
41
+
42
+ <script setup lang="ts">
43
+ import { ref } from 'vue'
44
+ import FileUpload from '@/components/FileUpload.vue'
45
+
46
+ interface UploadStatus {
47
+ type: 'success' | 'error'
48
+ message: string
49
+ }
50
+
51
+ const uploadStatus = ref<Record<string, UploadStatus>>({
52
+ basic: { type: 'success', message: '' },
53
+ })
54
+
55
+ const handleUploadComplete = (files: File[]) => {
56
+ uploadStatus.value.basic = {
57
+ type: 'success',
58
+ message: `Successfully uploaded ${files.length} file(s)`,
59
+ }
60
+ // Clear status after 3 seconds
61
+ setTimeout(() => {
62
+ uploadStatus.value.basic.message = ''
63
+ }, 3000)
64
+ }
65
+
66
+ const handleUploadError = (error: string) => {
67
+ uploadStatus.value.basic = {
68
+ type: 'error',
69
+ message: error,
70
+ }
71
+ // Clear status after 3 seconds
72
+ setTimeout(() => {
73
+ uploadStatus.value.basic.message = ''
74
+ }, 3000)
75
+ }
76
+
77
+ const handleFilesSelected = (files: File[]) => {
78
+ console.log('Files selected:', files)
79
+ }
80
+
81
+ const handleStartUpload = (files: File[]) => {
82
+ console.log('Start upload:', files)
83
+ }
84
+ </script>
85
+
86
+ <style scoped>
87
+ .file-upload-test {
88
+ padding: 2rem;
89
+ min-height: 100vh;
90
+ background-image: url('https://images.unsplash.com/photo-1519681393784-d120267933ba?auto=format&fit=crop&q=80&w=2070');
91
+ background-size: cover;
92
+ background-position: center;
93
+ background-attachment: fixed;
94
+ }
95
+
96
+ .file-upload-grid {
97
+ display: grid;
98
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
99
+ gap: 2rem;
100
+ margin-top: 2rem;
101
+ }
102
+
103
+ .upload-section {
104
+ background: rgba(255, 255, 255, 0.8);
105
+ padding: 2rem;
106
+ border-radius: 0.75rem;
107
+ border: 1px solid rgba(255, 255, 255, 0.2);
108
+ backdrop-filter: blur(5px);
109
+ }
110
+
111
+ .upload-section h2 {
112
+ margin-bottom: 1rem;
113
+ color: #495057;
114
+ }
115
+
116
+ .status-message {
117
+ margin-top: 1rem;
118
+ padding: 0.5rem;
119
+ border-radius: 0.25rem;
120
+ font-size: 0.875rem;
121
+ }
122
+
123
+ .status-message.success {
124
+ background-color: #d4edda;
125
+ color: #155724;
126
+ border: 1px solid #c3e6cb;
127
+ }
128
+
129
+ .status-message.error {
130
+ background-color: #f8d7da;
131
+ color: #721c24;
132
+ border: 1px solid #f5c6cb;
133
+ }
134
+
135
+ .group {
136
+ display: flex;
137
+ flex-direction: column;
138
+ gap: 1rem;
139
+ padding: 2rem;
140
+ border-radius: 1rem;
141
+ background-color: rgba(255, 255, 255, 0.8);
142
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
143
+ backdrop-filter: blur(5px);
144
+ }
145
+
146
+ .error {
147
+ color: var(--error-color);
148
+ background-color: var(--error-color-light);
149
+ border: 1px solid var(--error-color);
150
+ }
151
+
152
+ .header {
153
+ display: flex;
154
+ align-items: center;
155
+ gap: 0.5rem;
156
+ margin-bottom: 1rem;
157
+ }
158
+
159
+ .back-link {
160
+ display: flex;
161
+ align-items: center;
162
+ gap: 0.25rem;
163
+ color: var(--text-color);
164
+ text-decoration: none;
165
+ font-size: 1rem;
166
+ transition: color 0.2s;
167
+ }
168
+
169
+ .back-link:hover {
170
+ color: var(--primary-color);
171
+ }
172
+
173
+ h1 {
174
+ margin: 0;
175
+ color: #212529;
176
+ }
177
+ </style>
@@ -0,0 +1,348 @@
1
+ <template>
2
+ <div class="text-input-test">
3
+ <div class="header">
4
+ <router-link to="/" class="back-link">
5
+ <font-awesome-icon icon="home" />
6
+ <span>Back to Dashboard</span>
7
+ </router-link>
8
+ <h1>Text Input Testing</h1>
9
+ </div>
10
+ <div class="test-container">
11
+ <div class="column">
12
+ <div class="input-group">
13
+ <h2>Account Information</h2>
14
+ <TextInput
15
+ v-model="username"
16
+ type="text"
17
+ icon="user"
18
+ placeholder="Enter your username"
19
+ :required="true"
20
+ :error="usernameError"
21
+ label="Username"
22
+ label-position="left"
23
+ label-align="right"
24
+ label-width="20%"
25
+ total-width="100%"
26
+ />
27
+ <TextInput
28
+ v-model="password"
29
+ type="password"
30
+ icon="lock"
31
+ placeholder="Enter your password"
32
+ :required="true"
33
+ :error="passwordError"
34
+ label="Password"
35
+ label-position="left"
36
+ label-align="right"
37
+ label-width="20%"
38
+ total-width="100%"
39
+ />
40
+ </div>
41
+ <div class="input-group">
42
+ <h2>Additional Information</h2>
43
+ <TextInput
44
+ v-model="bio"
45
+ isTextarea
46
+ label="Bio"
47
+ icon="user-circle"
48
+ placeholder="Tell us about yourself..."
49
+ :rows="4"
50
+ :maxLength="500"
51
+ :error="bioError"
52
+ :autosave="handleBioAutosave"
53
+ label-position="top"
54
+ label-align="left"
55
+ total-width="100%"
56
+ height="10rem"
57
+ max-height="20rem"
58
+ />
59
+ <TextInput
60
+ v-model="feedback"
61
+ isTextarea
62
+ label="Feedback"
63
+ icon="comment"
64
+ placeholder="Share your thoughts..."
65
+ :rows="3"
66
+ :maxLength="1000"
67
+ :error="feedbackError"
68
+ :autosave="handleFeedbackAutosave"
69
+ label-position="left"
70
+ label-align="left"
71
+ label-width="auto"
72
+ total-width="100%"
73
+ max-height="8rem"
74
+ />
75
+ </div>
76
+ </div>
77
+ <div class="column">
78
+ <div class="input-group">
79
+ <h2>Address Information</h2>
80
+ <TextInput
81
+ v-model="street"
82
+ label="Street Address"
83
+ type="text"
84
+ icon="road"
85
+ placeholder="Enter your street address"
86
+ :error="streetError"
87
+ :autosave="handleStreetAutosave"
88
+ label-position="top"
89
+ label-align="left"
90
+ total-width="100%"
91
+ />
92
+ <div class="address-row">
93
+ <TextInput
94
+ v-model="city"
95
+ label="City"
96
+ type="text"
97
+ icon="building"
98
+ placeholder="Enter your city"
99
+ :error="cityError"
100
+ :autosave="handleCityAutosave"
101
+ label-position="top"
102
+ label-align="left"
103
+ total-width="48%"
104
+ />
105
+ <TextInput
106
+ v-model="postalCode"
107
+ label="Postal Code"
108
+ type="text"
109
+ icon="fas fa-hashtag"
110
+ placeholder="1234AB"
111
+ :error="postalCodeError"
112
+ @update:modelValue="handlePostalCodeInput"
113
+ :autosave="handlePostalCodeAutosave"
114
+ label-position="top"
115
+ label-align="left"
116
+ total-width="48%"
117
+ />
118
+ </div>
119
+ <TextInput
120
+ v-model="country"
121
+ label="Country"
122
+ type="text"
123
+ icon="flag"
124
+ placeholder="Enter your country"
125
+ :error="countryError"
126
+ :autosave="handleCountryAutosave"
127
+ label-position="top"
128
+ label-align="left"
129
+ total-width="100%"
130
+ />
131
+ <TextInput
132
+ v-model="comment"
133
+ label="Comment"
134
+ type="text"
135
+ placeholder="Your comment"
136
+ label-position="top"
137
+ label-align="left"
138
+ total-width="100%"
139
+ />
140
+ </div>
141
+ </div>
142
+ </div>
143
+ </div>
144
+ </template>
145
+
146
+ <script setup lang="ts">
147
+ import { ref } from 'vue'
148
+ import TextInput from '@/components/TextInput.vue'
149
+
150
+ const username = ref('')
151
+ const password = ref('')
152
+ const street = ref('')
153
+ const city = ref('')
154
+ const postalCode = ref('')
155
+ const country = ref('')
156
+ const comment = ref('')
157
+ const bio = ref('')
158
+ const feedback = ref('')
159
+
160
+ const usernameError = ref('')
161
+ const passwordError = ref('')
162
+ const streetError = ref('')
163
+ const cityError = ref('')
164
+ const postalCodeError = ref('')
165
+ const countryError = ref('')
166
+ const bioError = ref('')
167
+ const feedbackError = ref('')
168
+
169
+ const formatPostalCode = (value: string) => {
170
+ // Remove all non-alphanumeric characters except spaces
171
+ let formatted = value.replace(/[^a-zA-Z0-9\s]/g, '')
172
+
173
+ // Convert to uppercase
174
+ formatted = formatted.toUpperCase()
175
+
176
+ // Format as NNNN AA
177
+ if (formatted.length > 4) {
178
+ formatted = formatted.slice(0, 4) + ' ' + formatted.slice(4, 6)
179
+ }
180
+
181
+ return formatted
182
+ }
183
+
184
+ const handlePostalCodeInput = (_value: string) => {
185
+ postalCodeError.value = ''
186
+ }
187
+
188
+ const handlePostalCodeAutosave = async (value: string) => {
189
+ // First validate the format
190
+ if (value.length > 0) {
191
+ const postalCodeRegex = /^[0-9]{4}\s?[A-Za-z]{2}$/
192
+ if (!postalCodeRegex.test(value)) {
193
+ postalCodeError.value = 'Postal code must be in format NNNN AA (e.g., 1234 AB)'
194
+ return
195
+ }
196
+ }
197
+
198
+ // Format the value after successful validation
199
+ const formattedValue = formatPostalCode(value)
200
+ postalCode.value = formattedValue
201
+
202
+ await new Promise((resolve) => setTimeout(resolve, 1000))
203
+ postalCodeError.value = ''
204
+ }
205
+
206
+ const handleStreetAutosave = async (value: string) => {
207
+ console.log('Autosaving street:', value)
208
+ await new Promise((resolve) => setTimeout(resolve, 1000))
209
+ streetError.value = ''
210
+ }
211
+
212
+ const handleCityAutosave = async (value: string) => {
213
+ console.log('Autosaving city:', value)
214
+ await new Promise((resolve) => setTimeout(resolve, 1000))
215
+ cityError.value = ''
216
+ }
217
+
218
+ const handleCountryAutosave = async (value: string) => {
219
+ console.log('Autosaving country:', value)
220
+ await new Promise((resolve) => setTimeout(resolve, 1000))
221
+ countryError.value = ''
222
+ }
223
+
224
+ const handleBioAutosave = async (value: string) => {
225
+ console.log('Autosaving bio:', value)
226
+ await new Promise((resolve) => setTimeout(resolve, 1000))
227
+ bioError.value = ''
228
+ }
229
+
230
+ const handleFeedbackAutosave = async (value: string) => {
231
+ console.log('Autosaving feedback:', value)
232
+ await new Promise((resolve) => setTimeout(resolve, 1000))
233
+ feedbackError.value = ''
234
+ }
235
+ </script>
236
+
237
+ <style scoped>
238
+ .text-input-test {
239
+ padding: 2rem;
240
+ min-height: 100vh;
241
+ background-image: url('https://images.unsplash.com/photo-1519681393784-d120267933ba?auto=format&fit=crop&q=80&w=2070');
242
+ background-size: cover;
243
+ background-position: center;
244
+ background-attachment: fixed;
245
+ }
246
+
247
+ .text-input-grid {
248
+ display: grid;
249
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
250
+ gap: 2rem;
251
+ margin-top: 2rem;
252
+ }
253
+
254
+ .input-section {
255
+ background: rgba(255, 255, 255, 0.9);
256
+ padding: 2rem;
257
+ border-radius: 0.75rem;
258
+ border: 1px solid rgba(255, 255, 255, 0.2);
259
+ backdrop-filter: blur(10px);
260
+ }
261
+
262
+ .input-section h2 {
263
+ margin-bottom: 1rem;
264
+ color: var(--text-color);
265
+ }
266
+
267
+ .group {
268
+ display: flex;
269
+ flex-direction: column;
270
+ gap: 1rem;
271
+ padding: 2rem;
272
+ border-radius: 1rem;
273
+ background-color: rgba(255, 255, 255, 1);
274
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
275
+ backdrop-filter: blur(5px);
276
+ }
277
+
278
+ .header {
279
+ display: flex;
280
+ align-items: center;
281
+ gap: 0.5rem;
282
+ margin-bottom: 1rem;
283
+ }
284
+
285
+ .back-link {
286
+ display: flex;
287
+ align-items: center;
288
+ gap: 0.25rem;
289
+ color: var(--text-color);
290
+ text-decoration: none;
291
+ font-size: 1rem;
292
+ transition: color 0.2s;
293
+ }
294
+
295
+ .back-link:hover {
296
+ color: var(--primary-color);
297
+ }
298
+
299
+ h1 {
300
+ margin: 0;
301
+ color: #212529;
302
+ }
303
+
304
+ h2 {
305
+ color: #495057;
306
+ font-size: 1.1rem;
307
+ margin-bottom: 0.5rem;
308
+ }
309
+
310
+ .test-container {
311
+ display: grid;
312
+ grid-template-columns: repeat(auto-fit, minmax(min(100%, 600px), 1fr));
313
+ gap: 1rem;
314
+ }
315
+
316
+ .column {
317
+ display: flex;
318
+ flex-direction: column;
319
+ gap: 1rem;
320
+ }
321
+
322
+ .input-group {
323
+ background: rgba(255, 255, 255, 0.8);
324
+ border-radius: 8px;
325
+ padding: 1rem;
326
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
327
+ height: fit-content;
328
+ backdrop-filter: blur(5px);
329
+ }
330
+
331
+ .input-group h2 {
332
+ margin: 0 0 0.5rem 0;
333
+ color: var(--text-color);
334
+ font-size: 1.1rem;
335
+ }
336
+
337
+ .address-row {
338
+ display: flex;
339
+ justify-content: space-between;
340
+ gap: 0.5rem;
341
+ }
342
+
343
+ @media (max-width: 1200px) {
344
+ .test-container {
345
+ grid-template-columns: 1fr;
346
+ }
347
+ }
348
+ </style>