@a-vision-software/vue-input-components 1.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.
- package/README.md +419 -0
- package/dist/favicon.ico +0 -0
- package/dist/index.css +1 -0
- package/dist/index.mjs +300 -0
- package/dist/index.umd.js +1 -0
- package/package.json +57 -0
- package/src/assets/colors.css +67 -0
package/README.md
ADDED
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
# Vue Input Components
|
|
2
|
+
|
|
3
|
+
A collection of reusable Vue 3 input components with TypeScript support.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
### TextInput Component
|
|
8
|
+
|
|
9
|
+
- Customizable label position (top, left, right, bottom)
|
|
10
|
+
- Label alignment options (left, right, center)
|
|
11
|
+
- Icon support with click-to-focus functionality
|
|
12
|
+
- Error and success states with messages
|
|
13
|
+
- Required field indicator
|
|
14
|
+
- Autosave functionality with debounce
|
|
15
|
+
- Change indicators (saved, changed)
|
|
16
|
+
- Fully responsive
|
|
17
|
+
- Accessible
|
|
18
|
+
- TypeScript support
|
|
19
|
+
- Multiline text input support (textarea)
|
|
20
|
+
- Adjustable number of visible rows
|
|
21
|
+
- Optional maximum length limit
|
|
22
|
+
- Vertical resizing support
|
|
23
|
+
|
|
24
|
+
### FileUpload Component
|
|
25
|
+
|
|
26
|
+
- Drag and drop file upload
|
|
27
|
+
- File size validation (20MB limit)
|
|
28
|
+
- Multiple file selection
|
|
29
|
+
- Progress bar for upload status
|
|
30
|
+
- Customizable icons
|
|
31
|
+
- Automatic or manual upload modes
|
|
32
|
+
- File list display with sizes
|
|
33
|
+
- Error handling and status messages
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm install
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Development
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npm run dev
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Build
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npm run build
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Installation in Other Projects
|
|
54
|
+
|
|
55
|
+
### Option 1: Copy Components Directly
|
|
56
|
+
|
|
57
|
+
1. Copy the following files to your project:
|
|
58
|
+
|
|
59
|
+
- `src/components/TextInput.vue`
|
|
60
|
+
- `src/components/FileUpload.vue`
|
|
61
|
+
- `src/assets/colors.css`
|
|
62
|
+
|
|
63
|
+
2. Install required dependencies in your project:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
npm install @fortawesome/fontawesome-svg-core @fortawesome/free-regular-svg-icons @fortawesome/free-solid-svg-icons @fortawesome/vue-fontawesome
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
3. Import the color variables in your main CSS file:
|
|
70
|
+
|
|
71
|
+
```css
|
|
72
|
+
@import './assets/colors.css';
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
4. Import and register Font Awesome in your main.js or main.ts:
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
import { library } from '@fortawesome/fontawesome-svg-core'
|
|
79
|
+
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
|
|
80
|
+
import { faUser, faUpload, faImage, faFile, faHome } from '@fortawesome/free-solid-svg-icons'
|
|
81
|
+
|
|
82
|
+
library.add(faUser, faUpload, faImage, faFile, faHome)
|
|
83
|
+
app.component('font-awesome-icon', FontAwesomeIcon)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Option 2: Create a Package
|
|
87
|
+
|
|
88
|
+
1. Create a new package.json with the following structure:
|
|
89
|
+
|
|
90
|
+
```json
|
|
91
|
+
{
|
|
92
|
+
"name": "vue-input-components",
|
|
93
|
+
"version": "1.0.0",
|
|
94
|
+
"main": "dist/index.js",
|
|
95
|
+
"types": "dist/index.d.ts",
|
|
96
|
+
"files": ["dist"],
|
|
97
|
+
"dependencies": {
|
|
98
|
+
"@fortawesome/fontawesome-svg-core": "^6.7.2",
|
|
99
|
+
"@fortawesome/free-regular-svg-icons": "^6.7.2",
|
|
100
|
+
"@fortawesome/free-solid-svg-icons": "^6.7.2",
|
|
101
|
+
"@fortawesome/vue-fontawesome": "^3.0.8",
|
|
102
|
+
"vue": "^3.5.13"
|
|
103
|
+
},
|
|
104
|
+
"peerDependencies": {
|
|
105
|
+
"vue": "^3.5.13"
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
2. Build the components for distribution:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
npm run build
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
3. Publish to npm:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
npm publish
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
4. Install in other projects:
|
|
123
|
+
```bash
|
|
124
|
+
npm install vue-input-components
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Usage in Other Projects
|
|
128
|
+
|
|
129
|
+
1. Import the components:
|
|
130
|
+
|
|
131
|
+
```vue
|
|
132
|
+
<script setup>
|
|
133
|
+
import { TextInput, FileUpload } from 'vue-input-components'
|
|
134
|
+
</script>
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
2. Use the components in your template:
|
|
138
|
+
|
|
139
|
+
```vue
|
|
140
|
+
<template>
|
|
141
|
+
<TextInput v-model="username" label="Username" icon="user" :required="true" />
|
|
142
|
+
|
|
143
|
+
<FileUpload
|
|
144
|
+
icon="upload"
|
|
145
|
+
upload-url="https://api.example.com/upload"
|
|
146
|
+
@upload-complete="handleUploadComplete"
|
|
147
|
+
/>
|
|
148
|
+
</template>
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
3. Import the color variables in your project's main CSS file:
|
|
152
|
+
```css
|
|
153
|
+
@import 'vue-input-components/dist/colors.css';
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Customization
|
|
157
|
+
|
|
158
|
+
1. Override color variables in your project:
|
|
159
|
+
|
|
160
|
+
```css
|
|
161
|
+
:root {
|
|
162
|
+
--primary-color: #your-color;
|
|
163
|
+
--text-color: #your-color;
|
|
164
|
+
/* Override other variables as needed */
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
2. Customize component styles:
|
|
169
|
+
|
|
170
|
+
```css
|
|
171
|
+
.text-input {
|
|
172
|
+
/* Your custom styles */
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.file-upload {
|
|
176
|
+
/* Your custom styles */
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Requirements
|
|
181
|
+
|
|
182
|
+
- Vue 3.x
|
|
183
|
+
- TypeScript (optional but recommended)
|
|
184
|
+
- Font Awesome (for icons)
|
|
185
|
+
|
|
186
|
+
### Browser Support
|
|
187
|
+
|
|
188
|
+
All components use modern browser features and should work in all modern browsers that support:
|
|
189
|
+
|
|
190
|
+
- CSS Variables
|
|
191
|
+
- Flexbox
|
|
192
|
+
- File API (for FileUpload)
|
|
193
|
+
- Drag and Drop API (for FileUpload)
|
|
194
|
+
- FormData (for FileUpload)
|
|
195
|
+
- XMLHttpRequest (for FileUpload)
|
|
196
|
+
|
|
197
|
+
## Components
|
|
198
|
+
|
|
199
|
+
### TextInput
|
|
200
|
+
|
|
201
|
+
Basic text input component with advanced features, including textarea support.
|
|
202
|
+
|
|
203
|
+
#### Props
|
|
204
|
+
|
|
205
|
+
| Prop | Type | Default | Description |
|
|
206
|
+
| ------------- | -------------------------------------- | --------- | ------------------------------------------ |
|
|
207
|
+
| modelValue | string | required | The input value (v-model) |
|
|
208
|
+
| label | string | undefined | Input label |
|
|
209
|
+
| type | string | 'text' | Input type (text, password, email, etc.) |
|
|
210
|
+
| icon | string | undefined | Font Awesome icon name |
|
|
211
|
+
| placeholder | string | undefined | Input placeholder |
|
|
212
|
+
| required | boolean | false | Whether the field is required |
|
|
213
|
+
| disabled | boolean | false | Whether the field is disabled |
|
|
214
|
+
| error | string | undefined | Error message |
|
|
215
|
+
| success | string | undefined | Success message |
|
|
216
|
+
| labelPosition | 'top' \| 'left' \| 'right' \| 'bottom' | 'top' | Label position |
|
|
217
|
+
| labelAlign | 'left' \| 'right' \| 'center' | 'left' | Label text alignment |
|
|
218
|
+
| totalWidth | string | '100%' | Total width of the component |
|
|
219
|
+
| inputWidth | string | undefined | Width of the input field |
|
|
220
|
+
| labelWidth | string | undefined | Width of the label (when position is left) |
|
|
221
|
+
| autosave | (value: string) => Promise<void> | undefined | Autosave callback function |
|
|
222
|
+
| isTextarea | boolean | false | Whether to render as a textarea |
|
|
223
|
+
| maxHeight | string | '14rem' | Maximum height for textarea |
|
|
224
|
+
| height | string | '5.5rem' | Initial height for textarea |
|
|
225
|
+
|
|
226
|
+
#### Events
|
|
227
|
+
|
|
228
|
+
| Event | Payload | Description |
|
|
229
|
+
| ----------------- | ------- | --------------------------------------------------- |
|
|
230
|
+
| update:modelValue | string | Emitted when the input value changes |
|
|
231
|
+
| changed | void | Emitted when the value has changed (500ms debounce) |
|
|
232
|
+
| saved | void | Emitted when autosave completes successfully |
|
|
233
|
+
|
|
234
|
+
#### Examples
|
|
235
|
+
|
|
236
|
+
##### Basic Text Input
|
|
237
|
+
|
|
238
|
+
```vue
|
|
239
|
+
<template>
|
|
240
|
+
<TextInput
|
|
241
|
+
v-model="username"
|
|
242
|
+
label="Username"
|
|
243
|
+
type="text"
|
|
244
|
+
icon="user"
|
|
245
|
+
placeholder="Enter your username"
|
|
246
|
+
:error="usernameError"
|
|
247
|
+
:autosave="handleUsernameAutosave"
|
|
248
|
+
label-position="top"
|
|
249
|
+
label-align="left"
|
|
250
|
+
required
|
|
251
|
+
/>
|
|
252
|
+
</template>
|
|
253
|
+
|
|
254
|
+
<script setup lang="ts">
|
|
255
|
+
import { ref } from 'vue'
|
|
256
|
+
import TextInput from '@/components/TextInput.vue'
|
|
257
|
+
|
|
258
|
+
const username = ref('')
|
|
259
|
+
const usernameError = ref('')
|
|
260
|
+
|
|
261
|
+
const handleUsernameAutosave = async (value: string) => {
|
|
262
|
+
// Your autosave logic here
|
|
263
|
+
}
|
|
264
|
+
</script>
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
##### Textarea Input
|
|
268
|
+
|
|
269
|
+
```vue
|
|
270
|
+
<template>
|
|
271
|
+
<TextInput
|
|
272
|
+
v-model="description"
|
|
273
|
+
label="Description"
|
|
274
|
+
icon="pen"
|
|
275
|
+
placeholder="Enter a detailed description"
|
|
276
|
+
:isTextarea="true"
|
|
277
|
+
:error="descriptionError"
|
|
278
|
+
:autosave="handleDescriptionAutosave"
|
|
279
|
+
label-position="top"
|
|
280
|
+
label-align="left"
|
|
281
|
+
required
|
|
282
|
+
/>
|
|
283
|
+
</template>
|
|
284
|
+
|
|
285
|
+
<script setup lang="ts">
|
|
286
|
+
import { ref } from 'vue'
|
|
287
|
+
import TextInput from '@/components/TextInput.vue'
|
|
288
|
+
|
|
289
|
+
const description = ref('')
|
|
290
|
+
const descriptionError = ref('')
|
|
291
|
+
|
|
292
|
+
const handleDescriptionAutosave = async (value: string) => {
|
|
293
|
+
// Your autosave logic here
|
|
294
|
+
}
|
|
295
|
+
</script>
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### FileUpload
|
|
299
|
+
|
|
300
|
+
Flexible file upload component with drag and drop support.
|
|
301
|
+
|
|
302
|
+
#### Props
|
|
303
|
+
|
|
304
|
+
| Prop | Type | Required | Default | Description |
|
|
305
|
+
| ----------- | ------ | -------- | -------- | ---------------------------------------------------------------------- |
|
|
306
|
+
| `icon` | string | No | 'upload' | Font Awesome icon name to display |
|
|
307
|
+
| `uploadUrl` | string | No | - | URL to upload files to. If not provided, manual upload mode is enabled |
|
|
308
|
+
|
|
309
|
+
#### Events
|
|
310
|
+
|
|
311
|
+
| Event | Parameters | Description |
|
|
312
|
+
| ----------------- | --------------- | --------------------------------------------------- |
|
|
313
|
+
| `upload-complete` | `files: File[]` | Emitted when files are successfully uploaded |
|
|
314
|
+
| `upload-error` | `error: string` | Emitted when an upload error occurs |
|
|
315
|
+
| `files-selected` | `files: File[]` | Emitted when files are selected (manual mode) |
|
|
316
|
+
| `start-upload` | `files: File[]` | Emitted when upload button is clicked (manual mode) |
|
|
317
|
+
|
|
318
|
+
#### Examples
|
|
319
|
+
|
|
320
|
+
##### Automatic Upload Mode
|
|
321
|
+
|
|
322
|
+
```vue
|
|
323
|
+
<template>
|
|
324
|
+
<FileUpload
|
|
325
|
+
icon="upload"
|
|
326
|
+
upload-url="https://api.example.com/upload"
|
|
327
|
+
@upload-complete="handleUploadComplete"
|
|
328
|
+
@upload-error="handleUploadError"
|
|
329
|
+
/>
|
|
330
|
+
</template>
|
|
331
|
+
|
|
332
|
+
<script setup>
|
|
333
|
+
import FileUpload from '@/components/FileUpload.vue'
|
|
334
|
+
|
|
335
|
+
const handleUploadComplete = (files) => {
|
|
336
|
+
console.log('Uploaded files:', files)
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const handleUploadError = (error) => {
|
|
340
|
+
console.error('Upload error:', error)
|
|
341
|
+
}
|
|
342
|
+
</script>
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
##### Manual Upload Mode
|
|
346
|
+
|
|
347
|
+
```vue
|
|
348
|
+
<template>
|
|
349
|
+
<FileUpload
|
|
350
|
+
icon="image"
|
|
351
|
+
@files-selected="handleFilesSelected"
|
|
352
|
+
@start-upload="handleStartUpload"
|
|
353
|
+
/>
|
|
354
|
+
</template>
|
|
355
|
+
|
|
356
|
+
<script setup>
|
|
357
|
+
import FileUpload from '@/components/FileUpload.vue'
|
|
358
|
+
|
|
359
|
+
const handleFilesSelected = (files) => {
|
|
360
|
+
console.log('Selected files:', files)
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const handleStartUpload = (files) => {
|
|
364
|
+
// Implement your custom upload logic here
|
|
365
|
+
console.log('Starting upload for:', files)
|
|
366
|
+
}
|
|
367
|
+
</script>
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
## Styling
|
|
371
|
+
|
|
372
|
+
All components use CSS variables for theming. You can customize the colors by overriding these variables in your application's CSS:
|
|
373
|
+
|
|
374
|
+
```css
|
|
375
|
+
:root {
|
|
376
|
+
/* Base colors */
|
|
377
|
+
--primary-color: #3498db;
|
|
378
|
+
--primary-color-light: rgba(52, 152, 219, 0.2);
|
|
379
|
+
--secondary-color: #2ecc71;
|
|
380
|
+
|
|
381
|
+
/* Text colors */
|
|
382
|
+
--text-color: #2c3e50;
|
|
383
|
+
--text-color-light: #7f8c8d;
|
|
384
|
+
|
|
385
|
+
/* UI colors */
|
|
386
|
+
--border-color: #dcdfe6;
|
|
387
|
+
--icon-color: #95a5a6;
|
|
388
|
+
|
|
389
|
+
/* State colors */
|
|
390
|
+
--error-color: #e74c3c;
|
|
391
|
+
--error-color-light: rgba(231, 76, 60, 0.2);
|
|
392
|
+
--success-color: #2ecc71;
|
|
393
|
+
--success-color-light: rgba(46, 204, 113, 0.2);
|
|
394
|
+
--warning-color: #f1c40f;
|
|
395
|
+
--warning-color-light: rgba(241, 196, 15, 0.2);
|
|
396
|
+
|
|
397
|
+
/* Background colors */
|
|
398
|
+
--disabled-color: #bdc3c7;
|
|
399
|
+
--disabled-background: #f5f7fa;
|
|
400
|
+
--card-bg: #ffffff;
|
|
401
|
+
--background-color: #f8f9fa;
|
|
402
|
+
|
|
403
|
+
/* File Upload specific colors */
|
|
404
|
+
--upload-border-color: var(--border-color);
|
|
405
|
+
--upload-bg-color: var(--background-color);
|
|
406
|
+
--upload-dragging-border-color: var(--primary-color);
|
|
407
|
+
--upload-dragging-bg-color: var(--primary-color-light);
|
|
408
|
+
--upload-has-files-border-color: var(--success-color);
|
|
409
|
+
--upload-has-files-bg-color: var(--success-color-light);
|
|
410
|
+
--upload-icon-color: var(--icon-color);
|
|
411
|
+
--upload-text-color: var(--text-color-light);
|
|
412
|
+
--progress-bg-color: var(--disabled-background);
|
|
413
|
+
--progress-color: var(--primary-color);
|
|
414
|
+
}
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
## License
|
|
418
|
+
|
|
419
|
+
MIT
|
package/dist/favicon.ico
ADDED
|
Binary file
|
package/dist/index.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.text-input[data-v-cc2497d0]{display:grid;gap:.5rem;width:100%;margin-top:.7rem}.text-input.label-top[data-v-cc2497d0]{grid-template-rows:auto 1fr}.text-input.label-left[data-v-cc2497d0]{grid-template-columns:30% 1fr;align-items:start;gap:1rem}.text-input.label-left .label[data-v-cc2497d0]{padding-top:.75rem;width:100%}.label[data-v-cc2497d0]{font-weight:500;color:var(--text-color);text-align:left}.label-align-left .label[data-v-cc2497d0]{text-align:left}.label-align-right .label[data-v-cc2497d0]{text-align:right}.label-align-center .label[data-v-cc2497d0]{text-align:center}.required[data-v-cc2497d0]{color:var(--danger-color);margin-left:.25rem}.input-wrapper[data-v-cc2497d0]{position:relative;display:grid;grid-template-columns:1fr;border:1px solid var(--border-color);border-radius:.5rem;transition:all .2s ease;width:100%;min-height:2rem;background:var(--input-bg-color)}.input-wrapper.has-icon[data-v-cc2497d0]{grid-template-columns:auto 1fr}.input-wrapper[data-v-cc2497d0]:focus-within{border-color:var(--primary-color);box-shadow:0 0 0 3px var(--shadow-color)}.input-wrapper.has-error[data-v-cc2497d0]{border-color:var(--danger-color);border-bottom-left-radius:0;border-bottom-right-radius:0}.icon-wrapper[data-v-cc2497d0]{display:grid;place-items:start;padding:1rem;border-right:1px solid var(--border-color);cursor:pointer;overflow:hidden}.icon-wrapper[data-v-cc2497d0]:hover{background-color:var(--input-bg-hover)}.icon[data-v-cc2497d0]{color:var(--text-muted);font-size:1rem}.input[data-v-cc2497d0]{padding:.75rem 1rem;border:none;outline:none;font-size:1rem;color:var(--text-color);background:transparent;width:100%;line-height:var(--line-height)}.input[data-v-cc2497d0]::placeholder{color:var(--text-muted)}.input[data-v-cc2497d0]:disabled{background-color:var(--input-bg-disabled);cursor:not-allowed}.message[data-v-cc2497d0]{position:absolute;bottom:-1.5rem;left:0;font-size:.75rem;white-space:nowrap}.error-message[data-v-cc2497d0]{position:absolute;bottom:0;left:0;right:0;padding:.25rem .75rem;background-color:var(--danger-color);color:#fff;font-size:.75rem;border-radius:0 0 .5rem .5rem;transform:translateY(100%);transition:transform .2s ease;line-height:var(--line-height);z-index:1}.success-message[data-v-cc2497d0]{position:absolute;bottom:-1.5rem;left:0;color:var(--success-color);font-size:.75rem;line-height:var(--line-height)}.status-indicator[data-v-cc2497d0]{position:absolute;top:-.1rem;right:.5rem;font-size:.75rem;color:var(--text-muted);line-height:0px;background-color:var(--input-bg-color);padding:0 .25rem}.saved-indicator[data-v-cc2497d0]{color:var(--success-color)}.changed-indicator[data-v-cc2497d0]{color:var(--warning-color)}.fade-enter-active[data-v-cc2497d0],.fade-leave-active[data-v-cc2497d0]{transition:opacity .2s ease}.fade-enter-from[data-v-cc2497d0],.fade-leave-to[data-v-cc2497d0]{opacity:0}textarea[data-v-cc2497d0]{min-height:var(--textarea-height, 5.5rem);max-height:var(--max-textarea-height, 14rem);overflow-y:auto;resize:none}.file-upload[data-v-75b2aeea]{width:100%;max-width:600px;margin:0 auto}.upload-area[data-v-75b2aeea]{border:2px dashed var(--upload-border-color);border-radius:.75rem;padding:2rem;text-align:center;cursor:pointer;transition:all .3s ease;background-color:var(--upload-bg-color)}.upload-area.is-dragging[data-v-75b2aeea]{border-color:var(--upload-dragging-border-color);background-color:var(--upload-dragging-bg-color)}.upload-area.has-files[data-v-75b2aeea]{border-color:var(--upload-has-files-border-color);background-color:var(--upload-has-files-bg-color)}.file-input[data-v-75b2aeea]{display:none}.upload-content[data-v-75b2aeea]{display:flex;flex-direction:column;align-items:center;gap:1rem}.upload-content[data-v-75b2aeea] svg{font-size:2rem;color:var(--upload-icon-color)}.selected-files[data-v-75b2aeea]{width:100%;text-align:left;max-height:200px;overflow-y:auto;font-size:.75rem;color:var(--upload-text-color)}.file-info[data-v-75b2aeea]{display:flex;justify-content:space-between;align-items:center;padding:.125rem 0;border-radius:.25rem;gap:.5rem;font-size:.75rem}.file-name[data-v-75b2aeea]{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;margin-right:.5rem}.file-size[data-v-75b2aeea]{font-size:.7rem;flex-shrink:0}.error-message[data-v-75b2aeea]{color:var(--error-text-color);margin-top:1rem;font-size:.875rem}.progress-bar[data-v-75b2aeea]{height:.5rem;background-color:var(--progress-bg-color);border-radius:.25rem;margin-top:1rem;overflow:hidden}.progress[data-v-75b2aeea]{height:100%;background-color:var(--progress-color);transition:width .3s ease}.status-message[data-v-75b2aeea]{margin-top:1rem;padding:.5rem;border-radius:.25rem;font-size:.875rem}.status-message.success[data-v-75b2aeea]{background-color:var(--success-bg-color);color:var(--success-text-color)}.status-message.error[data-v-75b2aeea]{background-color:var(--error-bg-color);color:var(--error-text-color)}.upload-button[data-v-75b2aeea]{margin-top:1rem;padding:.5rem 1rem;background-color:var(--primary-color);color:#fff;border:none;border-radius:.25rem;cursor:pointer;font-size:.875rem;transition:background-color .3s ease}.upload-button[data-v-75b2aeea]:hover{background-color:var(--primary-color-light)}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import { defineComponent as z, computed as q, ref as u, onUnmounted as H, resolveComponent as A, createElementBlock as s, openBlock as o, normalizeStyle as L, normalizeClass as I, createCommentVNode as c, createElementVNode as m, toDisplayString as g, createVNode as U, Transition as B, withCtx as M, watch as N, withModifiers as $, Fragment as P, renderList as R } from "vue";
|
|
2
|
+
const O = ["for"], X = ["id", "type", "value", "placeholder", "required", "disabled"], j = ["id", "value", "placeholder", "required", "disabled"], G = {
|
|
3
|
+
key: 3,
|
|
4
|
+
class: "status-indicator required-indicator"
|
|
5
|
+
}, K = {
|
|
6
|
+
key: 0,
|
|
7
|
+
class: "status-indicator saved-indicator"
|
|
8
|
+
}, Z = {
|
|
9
|
+
key: 0,
|
|
10
|
+
class: "status-indicator changed-indicator"
|
|
11
|
+
}, J = {
|
|
12
|
+
key: 4,
|
|
13
|
+
class: "error-message"
|
|
14
|
+
}, Q = {
|
|
15
|
+
key: 5,
|
|
16
|
+
class: "message success-message"
|
|
17
|
+
}, Y = /* @__PURE__ */ z({
|
|
18
|
+
__name: "TextInput",
|
|
19
|
+
props: {
|
|
20
|
+
modelValue: {},
|
|
21
|
+
label: {},
|
|
22
|
+
type: {},
|
|
23
|
+
icon: {},
|
|
24
|
+
placeholder: {},
|
|
25
|
+
required: { type: Boolean },
|
|
26
|
+
disabled: { type: Boolean },
|
|
27
|
+
error: {},
|
|
28
|
+
success: {},
|
|
29
|
+
labelPosition: {},
|
|
30
|
+
labelAlign: {},
|
|
31
|
+
totalWidth: {},
|
|
32
|
+
inputWidth: {},
|
|
33
|
+
labelWidth: {},
|
|
34
|
+
autosave: { type: Function },
|
|
35
|
+
isTextarea: { type: Boolean },
|
|
36
|
+
maxHeight: {},
|
|
37
|
+
height: {}
|
|
38
|
+
},
|
|
39
|
+
emits: ["update:modelValue", "changed", "saved"],
|
|
40
|
+
setup(v, { emit: b }) {
|
|
41
|
+
const r = v, p = b, h = q(() => `input-${Math.random().toString(36).substr(2, 9)}`), t = u(!1), f = u(!1), y = u(!1), i = u(null), d = u(null), k = u(null), T = q(() => r.label ? r.labelPosition === "left" && r.labelWidth ? {
|
|
42
|
+
"grid-template-columns": `${r.labelWidth} 1fr`
|
|
43
|
+
} : {} : {}), C = async (e) => {
|
|
44
|
+
if (r.autosave)
|
|
45
|
+
try {
|
|
46
|
+
await r.autosave(e), r.error || (p("saved"), t.value = !0, f.value = !1, setTimeout(() => {
|
|
47
|
+
t.value = !1;
|
|
48
|
+
}, 3e3));
|
|
49
|
+
} catch (_) {
|
|
50
|
+
console.error("Autosave failed:", _);
|
|
51
|
+
}
|
|
52
|
+
}, D = (e) => {
|
|
53
|
+
i.value && clearTimeout(i.value), d.value && clearTimeout(d.value), r.error || (f.value = !0), d.value = window.setTimeout(() => {
|
|
54
|
+
p("changed"), y.value = !0;
|
|
55
|
+
}, 500), i.value = window.setTimeout(() => {
|
|
56
|
+
C(e);
|
|
57
|
+
}, 1500);
|
|
58
|
+
}, E = () => {
|
|
59
|
+
var e;
|
|
60
|
+
(e = k.value) == null || e.focus();
|
|
61
|
+
}, S = (e) => {
|
|
62
|
+
e.style.height = "auto", e.style.height = `${e.scrollHeight}px`;
|
|
63
|
+
}, F = (e) => {
|
|
64
|
+
const _ = e.target.value;
|
|
65
|
+
p("update:modelValue", _), D(_), S(e.target);
|
|
66
|
+
};
|
|
67
|
+
return H(() => {
|
|
68
|
+
i.value && clearTimeout(i.value), d.value && clearTimeout(d.value);
|
|
69
|
+
}), (e, _) => {
|
|
70
|
+
const l = A("font-awesome-icon");
|
|
71
|
+
return o(), s("div", {
|
|
72
|
+
class: I(["text-input", {
|
|
73
|
+
[`label-${e.labelPosition}`]: e.label,
|
|
74
|
+
[`label-align-${e.labelAlign}`]: e.label
|
|
75
|
+
}]),
|
|
76
|
+
style: L([
|
|
77
|
+
{ width: e.totalWidth || "100%" },
|
|
78
|
+
T.value,
|
|
79
|
+
{
|
|
80
|
+
"--max-textarea-height": r.maxHeight || r.height || "14rem",
|
|
81
|
+
"--textarea-height": r.height || "5.5rem"
|
|
82
|
+
}
|
|
83
|
+
])
|
|
84
|
+
}, [
|
|
85
|
+
e.label ? (o(), s("label", {
|
|
86
|
+
key: 0,
|
|
87
|
+
for: h.value,
|
|
88
|
+
class: "label"
|
|
89
|
+
}, g(e.label), 9, O)) : c("", !0),
|
|
90
|
+
m("div", {
|
|
91
|
+
class: I(["input-wrapper", {
|
|
92
|
+
"has-error": e.error,
|
|
93
|
+
"has-icon": e.icon
|
|
94
|
+
}])
|
|
95
|
+
}, [
|
|
96
|
+
e.icon ? (o(), s("div", {
|
|
97
|
+
key: 0,
|
|
98
|
+
class: "icon-wrapper",
|
|
99
|
+
onClick: E
|
|
100
|
+
}, [
|
|
101
|
+
U(l, {
|
|
102
|
+
icon: e.icon,
|
|
103
|
+
class: "icon"
|
|
104
|
+
}, null, 8, ["icon"])
|
|
105
|
+
])) : c("", !0),
|
|
106
|
+
e.isTextarea ? (o(), s("textarea", {
|
|
107
|
+
key: 2,
|
|
108
|
+
id: h.value,
|
|
109
|
+
value: e.modelValue,
|
|
110
|
+
placeholder: e.placeholder,
|
|
111
|
+
required: e.required,
|
|
112
|
+
disabled: e.disabled,
|
|
113
|
+
class: "input",
|
|
114
|
+
onInput: F,
|
|
115
|
+
ref_key: "inputRef",
|
|
116
|
+
ref: k
|
|
117
|
+
}, null, 40, j)) : (o(), s("input", {
|
|
118
|
+
key: 1,
|
|
119
|
+
id: h.value,
|
|
120
|
+
type: e.type,
|
|
121
|
+
value: e.modelValue,
|
|
122
|
+
placeholder: e.placeholder,
|
|
123
|
+
required: e.required,
|
|
124
|
+
disabled: e.disabled,
|
|
125
|
+
class: "input",
|
|
126
|
+
onInput: F,
|
|
127
|
+
ref_key: "inputRef",
|
|
128
|
+
ref: k
|
|
129
|
+
}, null, 40, X)),
|
|
130
|
+
e.required && !t.value && !f.value ? (o(), s("span", G, "required")) : c("", !0),
|
|
131
|
+
U(B, { name: "fade" }, {
|
|
132
|
+
default: M(() => [
|
|
133
|
+
t.value && !e.error ? (o(), s("span", K, "saved")) : c("", !0)
|
|
134
|
+
]),
|
|
135
|
+
_: 1
|
|
136
|
+
}),
|
|
137
|
+
U(B, { name: "fade" }, {
|
|
138
|
+
default: M(() => [
|
|
139
|
+
f.value && !e.error ? (o(), s("span", Z, "changed")) : c("", !0)
|
|
140
|
+
]),
|
|
141
|
+
_: 1
|
|
142
|
+
}),
|
|
143
|
+
e.error ? (o(), s("div", J, g(e.error), 1)) : c("", !0),
|
|
144
|
+
e.success ? (o(), s("span", Q, g(e.success), 1)) : c("", !0)
|
|
145
|
+
], 2)
|
|
146
|
+
], 6);
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
}), V = (v, b) => {
|
|
150
|
+
const r = v.__vccOpts || v;
|
|
151
|
+
for (const [p, h] of b)
|
|
152
|
+
r[p] = h;
|
|
153
|
+
return r;
|
|
154
|
+
}, x = /* @__PURE__ */ V(Y, [["__scopeId", "data-v-cc2497d0"]]), ee = { class: "file-upload" }, ae = { class: "upload-content" }, le = { key: 0 }, te = {
|
|
155
|
+
key: 1,
|
|
156
|
+
class: "selected-files"
|
|
157
|
+
}, se = { class: "file-name" }, oe = { class: "file-size" }, re = {
|
|
158
|
+
key: 0,
|
|
159
|
+
class: "error-message"
|
|
160
|
+
}, ne = {
|
|
161
|
+
key: 1,
|
|
162
|
+
class: "progress-bar"
|
|
163
|
+
}, ie = 20 * 1024 * 1024, ue = /* @__PURE__ */ z({
|
|
164
|
+
__name: "FileUpload",
|
|
165
|
+
props: {
|
|
166
|
+
icon: {},
|
|
167
|
+
uploadUrl: {}
|
|
168
|
+
},
|
|
169
|
+
emits: ["upload-complete", "upload-error", "files-selected", "start-upload"],
|
|
170
|
+
setup(v, { emit: b }) {
|
|
171
|
+
const r = v, p = b, h = u(null), t = u([]), f = u(!1), y = u(0), i = u(""), d = u(null), k = (l) => {
|
|
172
|
+
if (l === 0) return "0 Bytes";
|
|
173
|
+
const a = 1024, n = ["Bytes", "KB", "MB", "GB"], w = Math.floor(Math.log(l) / Math.log(a));
|
|
174
|
+
return parseFloat((l / Math.pow(a, w)).toFixed(2)) + " " + n[w];
|
|
175
|
+
}, T = (l) => l.size > ie ? (i.value = `File "${l.name}" exceeds the maximum size of 20MB`, !1) : !0, C = () => {
|
|
176
|
+
f.value = !0;
|
|
177
|
+
}, D = () => {
|
|
178
|
+
f.value = !1;
|
|
179
|
+
}, E = (l) => {
|
|
180
|
+
var a;
|
|
181
|
+
if (f.value = !1, i.value = "", (a = l.dataTransfer) != null && a.files) {
|
|
182
|
+
const n = Array.from(l.dataTransfer.files);
|
|
183
|
+
n.every(T) && (t.value = [...t.value, ...n]);
|
|
184
|
+
}
|
|
185
|
+
}, S = () => {
|
|
186
|
+
var l;
|
|
187
|
+
(l = h.value) == null || l.click();
|
|
188
|
+
}, F = (l) => {
|
|
189
|
+
i.value = "";
|
|
190
|
+
const a = l.target;
|
|
191
|
+
if (a.files) {
|
|
192
|
+
const n = Array.from(a.files);
|
|
193
|
+
n.every(T) && (t.value = [...t.value, ...n]);
|
|
194
|
+
}
|
|
195
|
+
a.value = "";
|
|
196
|
+
}, e = async () => {
|
|
197
|
+
if (!r.uploadUrl) {
|
|
198
|
+
i.value = "No upload URL provided";
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
if (t.value.length === 0) {
|
|
202
|
+
i.value = "No files selected";
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
const l = new FormData();
|
|
206
|
+
t.value.forEach((a) => {
|
|
207
|
+
l.append("files", a);
|
|
208
|
+
});
|
|
209
|
+
try {
|
|
210
|
+
const a = new XMLHttpRequest();
|
|
211
|
+
a.upload.addEventListener("progress", (n) => {
|
|
212
|
+
n.lengthComputable && (y.value = n.loaded / n.total * 100);
|
|
213
|
+
}), a.addEventListener("load", () => {
|
|
214
|
+
if (a.status >= 200 && a.status < 300)
|
|
215
|
+
d.value = {
|
|
216
|
+
type: "success",
|
|
217
|
+
message: "Upload completed successfully"
|
|
218
|
+
}, p("upload-complete", t.value), t.value = [], y.value = 0;
|
|
219
|
+
else
|
|
220
|
+
throw new Error(`Upload failed with status ${a.status}`);
|
|
221
|
+
}), a.addEventListener("error", () => {
|
|
222
|
+
throw new Error("Upload failed");
|
|
223
|
+
}), a.open("POST", r.uploadUrl), a.send(l);
|
|
224
|
+
} catch (a) {
|
|
225
|
+
const n = a instanceof Error ? a.message : "Upload failed";
|
|
226
|
+
i.value = n, d.value = {
|
|
227
|
+
type: "error",
|
|
228
|
+
message: n
|
|
229
|
+
}, p("upload-error", n);
|
|
230
|
+
}
|
|
231
|
+
}, _ = () => {
|
|
232
|
+
p("start-upload", t.value);
|
|
233
|
+
};
|
|
234
|
+
return N(t, (l) => {
|
|
235
|
+
l.length > 0 && (r.uploadUrl ? e() : p("files-selected", l));
|
|
236
|
+
}), (l, a) => {
|
|
237
|
+
const n = A("font-awesome-icon");
|
|
238
|
+
return o(), s("div", ee, [
|
|
239
|
+
m("div", {
|
|
240
|
+
class: I(["upload-area", { "is-dragging": f.value, "has-files": t.value.length > 0 }]),
|
|
241
|
+
onDragenter: $(C, ["prevent"]),
|
|
242
|
+
onDragleave: $(D, ["prevent"]),
|
|
243
|
+
onDragover: a[0] || (a[0] = $(() => {
|
|
244
|
+
}, ["prevent"])),
|
|
245
|
+
onDrop: $(E, ["prevent"]),
|
|
246
|
+
onClick: S
|
|
247
|
+
}, [
|
|
248
|
+
m("input", {
|
|
249
|
+
ref_key: "fileInput",
|
|
250
|
+
ref: h,
|
|
251
|
+
type: "file",
|
|
252
|
+
multiple: "",
|
|
253
|
+
class: "file-input",
|
|
254
|
+
onChange: F
|
|
255
|
+
}, null, 544),
|
|
256
|
+
m("div", ae, [
|
|
257
|
+
U(n, {
|
|
258
|
+
icon: ["fas", l.icon || "upload"]
|
|
259
|
+
}, null, 8, ["icon"]),
|
|
260
|
+
t.value.length === 0 ? (o(), s("p", le, "Drag & drop files here or click to select")) : (o(), s("div", te, [
|
|
261
|
+
m("p", null, g(t.value.length) + " file(s) selected", 1),
|
|
262
|
+
(o(!0), s(P, null, R(t.value, (w, W) => (o(), s("div", {
|
|
263
|
+
key: W,
|
|
264
|
+
class: "file-info"
|
|
265
|
+
}, [
|
|
266
|
+
m("span", se, g(w.name), 1),
|
|
267
|
+
m("span", oe, g(k(w.size)), 1)
|
|
268
|
+
]))), 128))
|
|
269
|
+
]))
|
|
270
|
+
])
|
|
271
|
+
], 34),
|
|
272
|
+
i.value ? (o(), s("div", re, g(i.value), 1)) : c("", !0),
|
|
273
|
+
y.value > 0 && y.value < 100 ? (o(), s("div", ne, [
|
|
274
|
+
m("div", {
|
|
275
|
+
class: "progress",
|
|
276
|
+
style: L({ width: `${y.value}%` })
|
|
277
|
+
}, null, 4)
|
|
278
|
+
])) : c("", !0),
|
|
279
|
+
d.value ? (o(), s("div", {
|
|
280
|
+
key: 2,
|
|
281
|
+
class: I(["status-message", d.value.type])
|
|
282
|
+
}, g(d.value.message), 3)) : c("", !0),
|
|
283
|
+
t.value.length > 0 && !l.uploadUrl ? (o(), s("button", {
|
|
284
|
+
key: 3,
|
|
285
|
+
class: "upload-button",
|
|
286
|
+
onClick: _
|
|
287
|
+
}, " Upload Files ")) : c("", !0)
|
|
288
|
+
]);
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
}), de = /* @__PURE__ */ V(ue, [["__scopeId", "data-v-75b2aeea"]]), pe = {
|
|
292
|
+
install: (v) => {
|
|
293
|
+
v.component("TextInput", x), v.component("FileUpload", de);
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
export {
|
|
297
|
+
de as FileUpload,
|
|
298
|
+
x as TextInput,
|
|
299
|
+
pe as default
|
|
300
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(function(d,e){typeof exports=="object"&&typeof module<"u"?e(exports,require("vue")):typeof define=="function"&&define.amd?define(["exports","vue"],e):(d=typeof globalThis<"u"?globalThis:d||self,e(d.VueInputComponents={},d.Vue))})(this,function(d,e){"use strict";const S=["for"],D=["id","type","value","placeholder","required","disabled"],F=["id","value","placeholder","required","disabled"],U={key:3,class:"status-indicator required-indicator"},$={key:0,class:"status-indicator saved-indicator"},I={key:0,class:"status-indicator changed-indicator"},M={key:4,class:"error-message"},z={key:5,class:"message success-message"},q=e.defineComponent({__name:"TextInput",props:{modelValue:{},label:{},type:{},icon:{},placeholder:{},required:{type:Boolean},disabled:{type:Boolean},error:{},success:{},labelPosition:{},labelAlign:{},totalWidth:{},inputWidth:{},labelWidth:{},autosave:{type:Function},isTextarea:{type:Boolean},maxHeight:{},height:{}},emits:["update:modelValue","changed","saved"],setup(p,{emit:g}){const n=p,c=g,m=e.computed(()=>`input-${Math.random().toString(36).substr(2,9)}`),a=e.ref(!1),u=e.ref(!1),f=e.ref(!1),r=e.ref(null),i=e.ref(null),k=e.ref(null),B=e.computed(()=>n.label?n.labelPosition==="left"&&n.labelWidth?{"grid-template-columns":`${n.labelWidth} 1fr`}:{}:{}),E=async t=>{if(n.autosave)try{await n.autosave(t),n.error||(c("saved"),a.value=!0,u.value=!1,setTimeout(()=>{a.value=!1},3e3))}catch(h){console.error("Autosave failed:",h)}},b=t=>{r.value&&clearTimeout(r.value),i.value&&clearTimeout(i.value),n.error||(u.value=!0),i.value=window.setTimeout(()=>{c("changed"),f.value=!0},500),r.value=window.setTimeout(()=>{E(t)},1500)},w=()=>{var t;(t=k.value)==null||t.focus()},V=t=>{t.style.height="auto",t.style.height=`${t.scrollHeight}px`},_=t=>{const h=t.target.value;c("update:modelValue",h),b(h),V(t.target)};return e.onUnmounted(()=>{r.value&&clearTimeout(r.value),i.value&&clearTimeout(i.value)}),(t,h)=>{const l=e.resolveComponent("font-awesome-icon");return e.openBlock(),e.createElementBlock("div",{class:e.normalizeClass(["text-input",{[`label-${t.labelPosition}`]:t.label,[`label-align-${t.labelAlign}`]:t.label}]),style:e.normalizeStyle([{width:t.totalWidth||"100%"},B.value,{"--max-textarea-height":n.maxHeight||n.height||"14rem","--textarea-height":n.height||"5.5rem"}])},[t.label?(e.openBlock(),e.createElementBlock("label",{key:0,for:m.value,class:"label"},e.toDisplayString(t.label),9,S)):e.createCommentVNode("",!0),e.createElementVNode("div",{class:e.normalizeClass(["input-wrapper",{"has-error":t.error,"has-icon":t.icon}])},[t.icon?(e.openBlock(),e.createElementBlock("div",{key:0,class:"icon-wrapper",onClick:w},[e.createVNode(l,{icon:t.icon,class:"icon"},null,8,["icon"])])):e.createCommentVNode("",!0),t.isTextarea?(e.openBlock(),e.createElementBlock("textarea",{key:2,id:m.value,value:t.modelValue,placeholder:t.placeholder,required:t.required,disabled:t.disabled,class:"input",onInput:_,ref_key:"inputRef",ref:k},null,40,F)):(e.openBlock(),e.createElementBlock("input",{key:1,id:m.value,type:t.type,value:t.modelValue,placeholder:t.placeholder,required:t.required,disabled:t.disabled,class:"input",onInput:_,ref_key:"inputRef",ref:k},null,40,D)),t.required&&!a.value&&!u.value?(e.openBlock(),e.createElementBlock("span",U,"required")):e.createCommentVNode("",!0),e.createVNode(e.Transition,{name:"fade"},{default:e.withCtx(()=>[a.value&&!t.error?(e.openBlock(),e.createElementBlock("span",$,"saved")):e.createCommentVNode("",!0)]),_:1}),e.createVNode(e.Transition,{name:"fade"},{default:e.withCtx(()=>[u.value&&!t.error?(e.openBlock(),e.createElementBlock("span",I,"changed")):e.createCommentVNode("",!0)]),_:1}),t.error?(e.openBlock(),e.createElementBlock("div",M,e.toDisplayString(t.error),1)):e.createCommentVNode("",!0),t.success?(e.openBlock(),e.createElementBlock("span",z,e.toDisplayString(t.success),1)):e.createCommentVNode("",!0)],2)],6)}}}),C=(p,g)=>{const n=p.__vccOpts||p;for(const[c,m]of g)n[c]=m;return n},N=C(q,[["__scopeId","data-v-cc2497d0"]]),v={class:"file-upload"},A={class:"upload-content"},L={key:0},P={key:1,class:"selected-files"},W={class:"file-name"},H={class:"file-size"},R={key:0,class:"error-message"},j={key:1,class:"progress-bar"},O=20*1024*1024,T=C(e.defineComponent({__name:"FileUpload",props:{icon:{},uploadUrl:{}},emits:["upload-complete","upload-error","files-selected","start-upload"],setup(p,{emit:g}){const n=p,c=g,m=e.ref(null),a=e.ref([]),u=e.ref(!1),f=e.ref(0),r=e.ref(""),i=e.ref(null),k=l=>{if(l===0)return"0 Bytes";const o=1024,s=["Bytes","KB","MB","GB"],y=Math.floor(Math.log(l)/Math.log(o));return parseFloat((l/Math.pow(o,y)).toFixed(2))+" "+s[y]},B=l=>l.size>O?(r.value=`File "${l.name}" exceeds the maximum size of 20MB`,!1):!0,E=()=>{u.value=!0},b=()=>{u.value=!1},w=l=>{var o;if(u.value=!1,r.value="",(o=l.dataTransfer)!=null&&o.files){const s=Array.from(l.dataTransfer.files);s.every(B)&&(a.value=[...a.value,...s])}},V=()=>{var l;(l=m.value)==null||l.click()},_=l=>{r.value="";const o=l.target;if(o.files){const s=Array.from(o.files);s.every(B)&&(a.value=[...a.value,...s])}o.value=""},t=async()=>{if(!n.uploadUrl){r.value="No upload URL provided";return}if(a.value.length===0){r.value="No files selected";return}const l=new FormData;a.value.forEach(o=>{l.append("files",o)});try{const o=new XMLHttpRequest;o.upload.addEventListener("progress",s=>{s.lengthComputable&&(f.value=s.loaded/s.total*100)}),o.addEventListener("load",()=>{if(o.status>=200&&o.status<300)i.value={type:"success",message:"Upload completed successfully"},c("upload-complete",a.value),a.value=[],f.value=0;else throw new Error(`Upload failed with status ${o.status}`)}),o.addEventListener("error",()=>{throw new Error("Upload failed")}),o.open("POST",n.uploadUrl),o.send(l)}catch(o){const s=o instanceof Error?o.message:"Upload failed";r.value=s,i.value={type:"error",message:s},c("upload-error",s)}},h=()=>{c("start-upload",a.value)};return e.watch(a,l=>{l.length>0&&(n.uploadUrl?t():c("files-selected",l))}),(l,o)=>{const s=e.resolveComponent("font-awesome-icon");return e.openBlock(),e.createElementBlock("div",v,[e.createElementVNode("div",{class:e.normalizeClass(["upload-area",{"is-dragging":u.value,"has-files":a.value.length>0}]),onDragenter:e.withModifiers(E,["prevent"]),onDragleave:e.withModifiers(b,["prevent"]),onDragover:o[0]||(o[0]=e.withModifiers(()=>{},["prevent"])),onDrop:e.withModifiers(w,["prevent"]),onClick:V},[e.createElementVNode("input",{ref_key:"fileInput",ref:m,type:"file",multiple:"",class:"file-input",onChange:_},null,544),e.createElementVNode("div",A,[e.createVNode(s,{icon:["fas",l.icon||"upload"]},null,8,["icon"]),a.value.length===0?(e.openBlock(),e.createElementBlock("p",L,"Drag & drop files here or click to select")):(e.openBlock(),e.createElementBlock("div",P,[e.createElementVNode("p",null,e.toDisplayString(a.value.length)+" file(s) selected",1),(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(a.value,(y,G)=>(e.openBlock(),e.createElementBlock("div",{key:G,class:"file-info"},[e.createElementVNode("span",W,e.toDisplayString(y.name),1),e.createElementVNode("span",H,e.toDisplayString(k(y.size)),1)]))),128))]))])],34),r.value?(e.openBlock(),e.createElementBlock("div",R,e.toDisplayString(r.value),1)):e.createCommentVNode("",!0),f.value>0&&f.value<100?(e.openBlock(),e.createElementBlock("div",j,[e.createElementVNode("div",{class:"progress",style:e.normalizeStyle({width:`${f.value}%`})},null,4)])):e.createCommentVNode("",!0),i.value?(e.openBlock(),e.createElementBlock("div",{key:2,class:e.normalizeClass(["status-message",i.value.type])},e.toDisplayString(i.value.message),3)):e.createCommentVNode("",!0),a.value.length>0&&!l.uploadUrl?(e.openBlock(),e.createElementBlock("button",{key:3,class:"upload-button",onClick:h}," Upload Files ")):e.createCommentVNode("",!0)])}}}),[["__scopeId","data-v-75b2aeea"]]),X={install:p=>{p.component("TextInput",N),p.component("FileUpload",T)}};d.FileUpload=T,d.TextInput=N,d.default=X,Object.defineProperties(d,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@a-vision-software/vue-input-components",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A collection of reusable Vue 3 input components with TypeScript support",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"src/assets/colors.css"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"dev": "vite",
|
|
13
|
+
"build": "run-p type-check \"build-only {@}\" --",
|
|
14
|
+
"preview": "vite preview",
|
|
15
|
+
"build-only": "vite build && vue-tsc --declaration --emitDeclarationOnly",
|
|
16
|
+
"type-check": "vue-tsc --noEmit",
|
|
17
|
+
"lint": "eslint . --fix",
|
|
18
|
+
"format": "prettier --write src/"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"vue",
|
|
22
|
+
"vue3",
|
|
23
|
+
"components",
|
|
24
|
+
"input",
|
|
25
|
+
"form",
|
|
26
|
+
"typescript"
|
|
27
|
+
],
|
|
28
|
+
"author": "",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@fortawesome/fontawesome-svg-core": "^6.7.2",
|
|
32
|
+
"@fortawesome/free-regular-svg-icons": "^6.7.2",
|
|
33
|
+
"@fortawesome/free-solid-svg-icons": "^6.7.2",
|
|
34
|
+
"@fortawesome/vue-fontawesome": "^3.0.8",
|
|
35
|
+
"vue": "^3.5.13"
|
|
36
|
+
},
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"vue": "^3.5.13"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@tsconfig/node22": "^22.0.1",
|
|
42
|
+
"@types/node": "^22.14.0",
|
|
43
|
+
"@vitejs/plugin-vue": "^5.2.3",
|
|
44
|
+
"@vue/eslint-config-prettier": "^10.2.0",
|
|
45
|
+
"@vue/eslint-config-typescript": "^14.5.0",
|
|
46
|
+
"@vue/tsconfig": "^0.7.0",
|
|
47
|
+
"eslint": "^9.22.0",
|
|
48
|
+
"eslint-plugin-vue": "~10.0.0",
|
|
49
|
+
"jiti": "^2.4.2",
|
|
50
|
+
"npm-run-all2": "^7.0.2",
|
|
51
|
+
"prettier": "3.5.3",
|
|
52
|
+
"typescript": "~5.8.0",
|
|
53
|
+
"vite": "^6.2.4",
|
|
54
|
+
"vite-plugin-vue-devtools": "^7.7.2",
|
|
55
|
+
"vue-tsc": "^2.2.8"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
/* Base colors */
|
|
3
|
+
--primary-color: #3498db;
|
|
4
|
+
--primary-color-light: rgba(52, 152, 219, 0.2);
|
|
5
|
+
--secondary-color: #2ecc71;
|
|
6
|
+
|
|
7
|
+
/* Text colors */
|
|
8
|
+
--text-color: #2c3e50;
|
|
9
|
+
--text-color-light: #7f8c8d;
|
|
10
|
+
|
|
11
|
+
/* UI colors */
|
|
12
|
+
--border-color: #dcdfe6;
|
|
13
|
+
--icon-color: #95a5a6;
|
|
14
|
+
|
|
15
|
+
/* State colors */
|
|
16
|
+
--error-color: #e74c3c;
|
|
17
|
+
--error-color-light: rgba(231, 76, 60, 0.2);
|
|
18
|
+
--success-color: #2ecc71;
|
|
19
|
+
--success-color-light: rgba(46, 204, 113, 0.2);
|
|
20
|
+
--warning-color: #f1c40f;
|
|
21
|
+
--warning-color-light: rgba(241, 196, 15, 0.2);
|
|
22
|
+
|
|
23
|
+
/* Background colors */
|
|
24
|
+
--disabled-color: #bdc3c7;
|
|
25
|
+
--disabled-background: #f5f7fa;
|
|
26
|
+
--card-bg: #ffffff;
|
|
27
|
+
--background-color: #f8f9fa;
|
|
28
|
+
|
|
29
|
+
/* Input colors */
|
|
30
|
+
--input-bg-color: rgba(255, 255, 255, 0.8);
|
|
31
|
+
--input-bg-hover: rgba(0, 0, 0, 0.05);
|
|
32
|
+
--input-bg-disabled: rgba(0, 0, 0, 0.05);
|
|
33
|
+
|
|
34
|
+
/* Text Colors */
|
|
35
|
+
--text-muted: #6c757d;
|
|
36
|
+
|
|
37
|
+
/* Border Colors */
|
|
38
|
+
--input-bg: #ffffff;
|
|
39
|
+
--input-bg-disabled: #e9ecef;
|
|
40
|
+
|
|
41
|
+
/* Status Colors */
|
|
42
|
+
--danger-color: #dc3545;
|
|
43
|
+
--success-color: #28a745;
|
|
44
|
+
--warning-color: #ffc107;
|
|
45
|
+
|
|
46
|
+
/* Shadow Colors */
|
|
47
|
+
--shadow-color: rgba(74, 108, 247, 0.25);
|
|
48
|
+
|
|
49
|
+
/* Typography */
|
|
50
|
+
--line-height: 20px;
|
|
51
|
+
|
|
52
|
+
/* File Upload Colors */
|
|
53
|
+
--upload-border-color: var(--border-color);
|
|
54
|
+
--upload-bg-color: var(--background-color);
|
|
55
|
+
--upload-dragging-border-color: var(--primary-color);
|
|
56
|
+
--upload-dragging-bg-color: var(--primary-color-light);
|
|
57
|
+
--upload-has-files-border-color: var(--success-color);
|
|
58
|
+
--upload-has-files-bg-color: var(--success-color-light);
|
|
59
|
+
--upload-icon-color: var(--icon-color);
|
|
60
|
+
--upload-text-color: var(--text-color-light);
|
|
61
|
+
--progress-bg-color: var(--disabled-background);
|
|
62
|
+
--progress-color: var(--primary-color);
|
|
63
|
+
--success-bg-color: var(--success-color-light);
|
|
64
|
+
--success-text-color: var(--success-color);
|
|
65
|
+
--error-bg-color: var(--error-color-light);
|
|
66
|
+
--error-text-color: var(--error-color);
|
|
67
|
+
}
|