pns-component-library 1.5.9 → 1.5.11

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 CHANGED
@@ -1,79 +1,648 @@
1
- # PNS Component Library Vue 3 + Vite
1
+ # PNS Component Library
2
2
 
3
- This library provides some ready-to-use responsive components with Vue 3. Developers can directly use the components without setting the non-web UI and UX (all included in the library).
3
+ A comprehensive Vue 3 component library built with modern development practices, providing reusable UI components, composables, and directives for building robust web applications.
4
4
 
5
- ## How to Set Up
6
- You have already created a vue project.
7
- ### Step 1:
8
- ```
5
+ ## 🚀 Features
6
+
7
+ - **9 Production-Ready Components** - Form controls, layout components, and utility components
8
+ - **2 Powerful Composables** - Reusable logic for common patterns
9
+ - **2 Custom Directives** - Loading states and UI enhancements
10
+ - **Vue 3 Composition API** - Modern, performant, and type-safe
11
+ - **Responsive Design** - Mobile-first approach with breakpoint handling
12
+ - **Accessibility** - ARIA attributes and keyboard navigation support
13
+ - **Customizable Themes** - Multiple built-in color schemes
14
+
15
+ ## 📦 Installation
16
+
17
+ ```bash
9
18
  npm install pns-component-library
10
19
  ```
11
- ### Step 2:
12
- In your main.js, import the library and use it for your app instance.
20
+ or
21
+
22
+ ```bash
23
+ npm install pns-component-library@latest
24
+ ```
25
+
26
+ ## 🎯 Quick Start
27
+
28
+ ### 1. Install and Setup
29
+
30
+ First, import and register the library in your `main.js`:
31
+
32
+ ```javascript
33
+ // main.js
34
+ import { createApp } from 'vue'
35
+ import App from './App.vue'
36
+ import PnsComponentLibrary from 'pns-component-library'
37
+ import 'pns-component-library/style.css'
38
+
39
+ const app = createApp(App)
40
+ app.use(PnsComponentLibrary)
41
+ app.mount('#app')
42
+ ```
43
+
44
+ ### 2. Use Components
45
+
46
+ ```vue
47
+ <template>
48
+ <div>
49
+ <!-- Basic checkbox -->
50
+ <Checkbox v-model="isChecked" label="Accept terms" />
51
+
52
+ <!-- Phone input with country selection -->
53
+ <AreaCodePhoneInput v-model="phoneData" />
54
+
55
+ <!-- Responsive button -->
56
+ <ResponsiveButton @click="handleClick">
57
+ Click me
58
+ </ResponsiveButton>
59
+ </div>
60
+ </template>
61
+
62
+ <script setup>
63
+ import { ref } from 'vue'
64
+
65
+ const isChecked = ref(false)
66
+ const phoneData = ref({ area_code: '', phone_number: '' })
67
+
68
+ const handleClick = () => {
69
+ console.log('Button clicked!')
70
+ }
71
+ </script>
72
+ ```
73
+
74
+ ## 📚 Components
75
+
76
+ ### Form Components
77
+
78
+ #### Checkbox
79
+ Customizable checkbox with multiple themes and validation support.
80
+
81
+ ##### Attributes
82
+
83
+ | Attribute | Description | Type | Default |
84
+ |-----------|-------------|------|---------|
85
+ | v-model / modelValue | binding value - `true` when checked, `false` when unchecked | `boolean` | `false` |
86
+ | label | checkbox label | `string` | — |
87
+ | size | checkbox size | `'small' \| 'medium'` | `'medium'` |
88
+ | built_in_theme | color theme | `'green' \| 'primary-blue' \| 'secondary-blue' \| 'red'` | `'green'` |
89
+ | disabled | whether checkbox is disabled | `boolean` | `false` |
90
+ | indeterminate | whether checkbox is indeterminate | `boolean` | `false` |
91
+
92
+ ##### Events
93
+
94
+ | Event | Description | Parameters |
95
+ |-------|-------------|------------|
96
+ | change | triggers when the binding value changes | `(value: boolean)` |
97
+
98
+ ##### Exposes
99
+
100
+ | Method | Description | Type |
101
+ |--------|-------------|------|
102
+ | focus | focus the checkbox | `() => void` |
103
+ | blur | blur the checkbox | `() => void` |
104
+ | alert | show error message | `(message: string) => void` |
105
+ | removeAlertOrErrorEffect | clear error state | `() => void` |
106
+
107
+ ```vue
108
+ <template>
109
+ <Checkbox
110
+ v-model="agreed"
111
+ label="I agree to terms"
112
+ built_in_theme="primary-blue"
113
+ @change="handleChange"
114
+ />
115
+ </template>
116
+ ```
117
+
118
+ #### GroupCheckbox
119
+ Container for managing multiple related checkboxes as a group.
120
+
121
+ ##### Attributes
122
+
123
+ | Attribute | Description | Type | Default |
124
+ |-----------|-------------|------|---------|
125
+ | v-model / modelValue | binding value - array of selected checkbox labels from child Checkbox components | `array` | `[]` |
126
+
127
+ ##### Events
128
+
129
+ | Event | Description | Parameters |
130
+ |-------|-------------|------------|
131
+ | change | triggers when the binding value changes | `(value: array)` |
132
+
133
+ ```vue
134
+ <template>
135
+ <GroupCheckbox v-model="selectedItems">
136
+ <Checkbox v-for="item in options" :key="item" :label="item" />
137
+ </GroupCheckbox>
138
+ </template>
139
+
140
+ <script setup>
141
+ const selectedItems = ref(['option1'])
142
+ const options = ['option1', 'option2', 'option3']
143
+ </script>
144
+ ```
145
+
146
+ #### AreaCodePhoneInput
147
+ International phone number input with country selection and area code handling.
148
+
149
+ ##### Attributes
150
+
151
+ | Attribute | Description | Type | Default |
152
+ |-----------|-------------|------|---------|
153
+ | v-model / modelValue | binding value - object containing `area_code` (country code like "+1") and `phone_number` (local number) | `{area_code: string, phone_number: string}` | `{area_code: '', phone_number: ''}` |
154
+ | country_options | custom country list | `array` | — |
155
+ | enable_backup_country_options | use built-in country list | `boolean` | `true` |
156
+ | country_filterable | enable country search | `boolean` | `true` |
157
+ | disabled | whether input is disabled | `boolean` | `false` |
158
+
159
+ ##### Events
160
+
161
+ | Event | Description | Parameters |
162
+ |-------|-------------|------------|
163
+ | change | triggers when the binding value changes | `(value: object)` |
164
+ | focus | triggers when input is focused | `(event: FocusEvent)` |
165
+ | blur | triggers when input is blurred | `(event: FocusEvent)` |
166
+ | clear | triggers when clear button is clicked | — |
167
+
168
+ ```vue
169
+ <template>
170
+ <AreaCodePhoneInput
171
+ v-model="phoneData"
172
+ :country_filterable="true"
173
+ @change="handlePhoneChange"
174
+ />
175
+ </template>
176
+ ```
177
+
178
+ #### InputBox
179
+ Versatile input field component with validation and styling options.
180
+
181
+ ##### Attributes
182
+
183
+ | Attribute | Description | Type | Default |
184
+ |-----------|-------------|------|---------|
185
+ | v-model / modelValue | binding value - the current input text value | `string` | — |
186
+ | type | input type | `string` | `'text'` |
187
+ | placeholder | placeholder text | `string` | — |
188
+ | clearable | whether to show clear button | `boolean` | `false` |
189
+ | disabled | whether input is disabled | `boolean` | `false` |
190
+
191
+ ##### Events
192
+
193
+ | Event | Description | Parameters |
194
+ |-------|-------------|------------|
195
+ | input | triggers when input value changes | `(value: string)` |
196
+ | change | triggers when input value changes and loses focus | `(value: string)` |
197
+ | focus | triggers when input is focused | `(event: FocusEvent)` |
198
+ | blur | triggers when input is blurred | `(event: FocusEvent)` |
199
+ | clear | triggers when clear button is clicked | — |
200
+
201
+ ##### Exposes
202
+
203
+ | Method | Description | Type |
204
+ |--------|-------------|------|
205
+ | focus | focus the input | `() => void` |
206
+ | blur | blur the input | `() => void` |
207
+ | alert | show alert message | `(message: string) => void` |
208
+ | error | show error message | `(message: string) => void` |
209
+ | removeAlertOrErrorEffect | clear alert/error state | `() => void` |
210
+
211
+ ##### Slots
212
+
213
+ | Name | Description |
214
+ |------|-------------|
215
+ | prefix | content before input |
216
+ | suffix | content after input |
217
+
13
218
  ```vue
14
- import { createApp } from 'vue';
15
- import PNSComponentLibray from "pns-component-library";
16
-
17
- const app = createApp(App);
18
- app.use(PNSComponentLibray);
19
- ```
20
- ### Step 3
21
- Now you should be able to access the components, directives and icons in the library.
22
-
23
- ## Components:
24
- - SimplifiedNotification
25
- - InputBox
26
- - SingleSelector
27
- - ResponsiveButton
28
- - Checkbox
29
- - Radio (support group radio)
30
- - WholePageErrorPopup
31
-
32
- ## Directives:
33
- - component-loading
34
-
35
- ## Composables:
36
- - useWindowSize
37
- - FloatingNotification
38
-
39
- ## Icons:
40
- If `npm install pns-component-library`, and want to use any following icons: <br>
41
- <pre>import { iconsMap } from "pns-component-library</pre>
42
- iconsMap.[iconName] is the URL or Data URI (Uniform Resource Identifier) of the corresponding svg icon.
43
- Eg.
219
+ <InputBox
220
+ v-model="value"
221
+ type="text"
222
+ placeholder="Enter text"
223
+ clearable
224
+ >
225
+ <template #prefix>
226
+ <Icon name="search" />
227
+ </template>
228
+ </InputBox>
229
+ ```
230
+
231
+ #### Radio
232
+ Radio button component for single selection from multiple options.
233
+
234
+ ##### Attributes
235
+
236
+ | Attribute | Description | Type | Default |
237
+ |-----------|-------------|------|---------|
238
+ | v-model / modelValue | binding value - the `value` of the currently selected radio button in the group | `string` | — |
239
+ | value | radio value | `string` | — |
240
+ | label | radio label | `string` | — |
241
+ | name | group name for radio group | `string` | — |
242
+ | size | radio size | `'small' \| 'medium'` | `'medium'` |
243
+ | built_in_theme | color theme | `'primary-blue' \| 'secondary-blue' \| 'red'` | `'primary-blue'` |
244
+ | customized_class | custom CSS class | `string` | — |
245
+ | disabled | whether radio is disabled | `boolean` | `false` |
246
+ | autofocus | whether to auto focus | `boolean` | `false` |
247
+
248
+ ##### Events
249
+
250
+ | Event | Description | Parameters |
251
+ |-------|-------------|------------|
252
+ | change | triggers when the binding value changes | `(value: string)` |
253
+
254
+ ##### Exposes
255
+
256
+ | Method | Description | Type |
257
+ |--------|-------------|------|
258
+ | focus | focus the radio | `() => void` |
259
+ | blur | blur the radio | `() => void` |
260
+ | alert | show alert message | `(message: string) => void` |
261
+ | removeAlertOrErrorEffect | clear alert/error state | `() => void` |
262
+
44
263
  ```vue
45
- <template>
46
- <img :src = "iconsMap['eye_icon']" />
264
+ <!-- Radio Group -->
265
+ <Radio v-model="selected" value="option1" name="group1" label="Option 1" />
266
+ <Radio v-model="selected" value="option2" name="group1" label="Option 2" />
267
+
268
+ <!-- Single Radio -->
269
+ <Radio v-model="single" value="yes" label="Agree to terms" />
270
+ ```
271
+
272
+ #### SingleSelector
273
+ Dropdown selector component with filtering and search capabilities.
274
+
275
+ ##### Attributes
276
+
277
+ | Attribute | Description | Type | Default |
278
+ |-----------|-------------|------|---------|
279
+ | v-model / modelValue | binding value - the `value` property of the selected option from the options array | `string \| number \| boolean \| object \| array` | — |
280
+ | options | options array | `Array<{value: any, label: string}>` | `[]` |
281
+ | filterable | whether to enable filtering | `boolean` | `false` |
282
+ | remote_search | whether to enable remote search | `boolean` | `false` |
283
+ | disabled | whether selector is disabled | `boolean` | `false` |
284
+ | placeholder | placeholder text | `string` | `'Select'` |
285
+ | clearable | whether to show clear button | `boolean` | `false` |
286
+ | loading | loading state | `boolean` | `false` |
287
+ | loading_text | loading text | `string` | `'Loading'` |
288
+ | no_data_text | no data text | `string` | `'No data'` |
289
+
290
+ ##### Events
291
+
292
+ | Event | Description | Parameters |
293
+ |-------|-------------|------------|
294
+ | change | triggers when the binding value changes | `(value: any)` |
295
+ | remote-search | triggers when remote search is performed | `(query: string)` |
296
+ | filter-change | triggers when filter text changes | `(query: string)` |
297
+ | visible-change | triggers when dropdown visibility changes | `(visible: boolean)` |
298
+ | focus | triggers when selector is focused | `(event: FocusEvent)` |
299
+ | blur | triggers when selector is blurred | `(event: FocusEvent)` |
300
+ | clear | triggers when clear button is clicked | — |
301
+
302
+ ##### Exposes
303
+
304
+ | Method | Description | Type |
305
+ |--------|-------------|------|
306
+ | focus | focus the selector | `() => void` |
307
+ | blur | blur the selector | `() => void` |
308
+ | alert | show alert message | `(message: string) => void` |
309
+ | error | show error message | `(message: string) => void` |
310
+ | removeAlertOrErrorEffect | clear alert/error state | `() => void` |
311
+ | setDropdownContentLoading | set loading state | `(loading: boolean) => void` |
312
+
313
+ ##### Slots
314
+
315
+ | Name | Description |
316
+ |------|-------------|
317
+ | prefix | content before selector |
318
+ | suffix | content after selector |
319
+
320
+ ```vue
321
+ <SingleSelector
322
+ v-model="selected"
323
+ :options="options"
324
+ filterable
325
+ clearable
326
+ placeholder="Choose option"
327
+ >
328
+ <template #prefix>
329
+ <Icon name="search" />
47
330
  </template>
48
- <script>
49
- import { iconsMap } from "pns-component-library
50
- export default{
51
- data(){
52
- return{
53
- iconsMap,
54
- }
55
- }
331
+ </SingleSelector>
332
+ ```
333
+
334
+ ### Interactive Components
335
+
336
+ #### ResponsiveButton
337
+ Feature-rich button component with responsive behavior and multiple states.
338
+
339
+ ##### Attributes
340
+
341
+ | Attribute | Description | Type | Default |
342
+ |-----------|-------------|------|---------|
343
+ | display_name | button text | `string` | `'button'` |
344
+ | size | button size | `'xsmall' \| 'small' \| 'medium' \| 'large'` | `'small'` |
345
+ | width_type | button width type | `'fill-whole' \| 'fit-content'` | `'fit-content'` |
346
+ | build_in_theme | color theme | `'outlined-primary' \| 'filled-primary' \| 'text-primary' \| 'outlined-secondary' \| 'filled-secondary' \| 'text-secondary'` | `'outlined-primary'` |
347
+ | customized_class | custom CSS class | `string` | — |
348
+ | hold | whether button is in hold state | `boolean` | `false` |
349
+ | disabled | whether button is disabled | `boolean` | `false` |
350
+ | dropdown_options | dropdown menu options | `array` | `[]` |
351
+ | is_dropdown_option | whether this is a dropdown item | `boolean` | `false` |
352
+
353
+ ##### Events
354
+
355
+ | Event | Description | Parameters |
356
+ |-------|-------------|------------|
357
+ | click | triggers when button is clicked | `(event: MouseEvent)` |
358
+ | update-currently-hovered-dropdown-option | triggers when dropdown option is hovered | `(option: object)` |
359
+ | add-nested-dropdown-history-stack | triggers when nested dropdown is navigated | `(option: object)` |
360
+ | remove-nested-dropdown-history-stack | triggers when navigating back in nested dropdown | — |
361
+
362
+ ##### Slots
363
+
364
+ | Name | Description |
365
+ |------|-------------|
366
+ | prefix | content before button text |
367
+ | suffix | content after button text |
368
+
369
+ ```vue
370
+ <script setup>
371
+ import { iconsMap } from 'pns-component-library';
372
+ </script>
373
+ <template>
374
+ <ResponsiveButton
375
+ display_name="Save Changes"
376
+ size="medium"
377
+ build_in_theme="filled-primary"
378
+ @click="handleSave"
379
+ >
380
+ <template #prefix>
381
+ <img :src="iconsMap['eye_icon']" alt="eye" />
382
+ </template>
383
+ </ResponsiveButton>
384
+ </template>
385
+ ```
386
+
387
+ ### Feedback Components
388
+
389
+ #### SimplifiedNotification
390
+ Toast notification component for user feedback.
391
+
392
+ ##### Attributes
393
+
394
+ | Attribute | Description | Type | Default |
395
+ |-----------|-------------|------|---------|
396
+ | content | notification content | `string` | — |
397
+ | build_in_theme | color theme | `'blue' \| 'red' \| 'yellow' \| 'green'` | `'blue'` |
398
+ | title | notification title | `string` | — |
399
+ | customized_alert_icon_src | custom icon URL | `string` | — |
400
+ | customized_class | custom CSS class | `string` | — |
401
+ | with_close_btn | whether to show close button | `boolean` | `true` |
402
+ | mounted_programmatically | whether mounted programmatically | `boolean` | `false` |
403
+ | hide_after | auto-hide after milliseconds (0 = never) | `number` | `0` |
404
+
405
+ ##### Events
406
+
407
+ | Event | Description | Parameters |
408
+ |-------|-------------|------------|
409
+ | close | triggers when notification is closed | — |
410
+
411
+ ##### Slots
412
+
413
+ | Name | Description |
414
+ |------|-------------|
415
+ | content | custom notification content |
416
+
417
+ ```vue
418
+ <SimplifiedNotification
419
+ title="Success"
420
+ content="Operation completed successfully"
421
+ build_in_theme="green"
422
+ :hide_after="3000"
423
+ @close="handleClose"
424
+ />
425
+ ```
426
+
427
+ #### WholePageErrorPopup
428
+ Full-page error overlay for critical error states.
429
+
430
+ ##### Attributes
431
+
432
+ | Attribute | Description | Type | Default |
433
+ |-----------|-------------|------|---------|
434
+ | title | error title | `string` | `'Error'` |
435
+ | content | error message | `string` | `'An error has occured'` |
436
+
437
+ ##### Events
438
+
439
+ | Event | Description | Parameters |
440
+ |-------|-------------|------------|
441
+ | retry | triggers when retry button is clicked | — |
442
+
443
+ ##### Slots
444
+
445
+ | Name | Description |
446
+ |------|-------------|
447
+ | content | custom error content |
448
+
449
+ ```vue
450
+ <WholePageErrorPopup
451
+ title="Connection Error"
452
+ content="Unable to connect to server. Please check your internet connection."
453
+ @retry="handleRetry"
454
+ />
455
+ ```
456
+
457
+ ## 🔧 Composables
458
+
459
+ ### FloatingNotification
460
+ Programmatically create floating notifications.
461
+
462
+ ##### Parameters
463
+
464
+ | Parameter | Description | Type | Default |
465
+ |-----------|-------------|------|---------|
466
+ | type | notification type | `'success' \| 'error' \| 'warning' \| 'info'` | `'info'` |
467
+ | message | notification message | `string` | — |
468
+ | duration | auto-hide duration in milliseconds | `number` | `3000` |
469
+ | title | notification title | `string` | — |
470
+
471
+ ##### Returns
472
+
473
+ | Type | Description |
474
+ |------|-------------|
475
+ | `void` | Creates and displays the notification |
476
+
477
+ ```javascript
478
+ import { FloatingNotification } from 'pns-component-library/composables'
479
+
480
+ // Show notification
481
+ FloatingNotification({
482
+ type: 'success',
483
+ message: 'Operation completed!',
484
+ duration: 3000
485
+ })
486
+ ```
487
+
488
+ ### useWindowSize
489
+ Reactive window size tracking composable.
490
+
491
+ ##### Returns
492
+
493
+ | Property | Description | Type |
494
+ |----------|-------------|------|
495
+ | width | current window width | `Ref<number>` |
496
+ | height | current window height | `Ref<number>` |
497
+
498
+ ```javascript
499
+ import { useWindowSize } from 'pns-component-library/composables'
500
+
501
+ const { width, height } = useWindowSize()
502
+
503
+ // Use in template or computed
504
+ watchEffect(() => {
505
+ console.log(`Window size: ${width.value}x${height.value}`)
506
+ })
507
+ ```
508
+
509
+ ## 🎨 Directives
510
+
511
+ ### v-component-loading
512
+ Add loading state to any component with overlay spinner.
513
+
514
+ ##### Usage
515
+
516
+ | Binding | Description | Type |
517
+ |---------|-------------|------|
518
+ | value | whether to show loading state | `boolean` |
519
+
520
+ ```vue
521
+ <template>
522
+ <div v-component-loading="isLoading">
523
+ Content here
524
+ </div>
525
+ </template>
526
+
527
+ <script setup>
528
+ import { ref } from 'vue'
529
+ const isLoading = ref(false)
530
+
531
+ // Toggle loading
532
+ const startLoading = () => {
533
+ isLoading.value = true
534
+ setTimeout(() => {
535
+ isLoading.value = false
536
+ }, 2000)
537
+ }
538
+ </script>
539
+ ```
540
+
541
+ ### v-whole-page-loading
542
+ Full-page loading overlay that covers the entire viewport.
543
+
544
+ ##### Usage
545
+
546
+ | Binding | Description | Type |
547
+ |---------|-------------|------|
548
+ | value | whether to show full-page loading | `boolean` |
549
+
550
+ ```vue
551
+ <template>
552
+ <div v-whole-page-loading="isPageLoading">
553
+ App content
554
+ </div>
555
+ </template>
556
+
557
+ <script setup>
558
+ import { ref } from 'vue'
559
+ const isPageLoading = ref(false)
560
+
561
+ // Show page loading
562
+ const loadPage = async () => {
563
+ isPageLoading.value = true
564
+ try {
565
+ await fetchData()
566
+ } finally {
567
+ isPageLoading.value = false
56
568
  }
57
- </script>
58
- ```
59
- ### All icons' names
60
- - black_arrow_down
61
- - black_arrow_to_right
62
- - black_calendar_icon
63
- - black_magnifying_glass_icon
64
- - cyan_arrow_down
65
- - dark_blue_arrow_down
66
- - dark_gray_arrow_down
67
- - eye_icon
68
- - eye_with_cross_icon
69
- - green_notification_icon
70
- - grey_arrow_down
71
- - grey_arrow_to_right
72
- - info_blue_icon
73
- - input_clear_icon
74
- - notification_cross_icon
75
- - red_notification_icon
76
- - red_sad_face
77
- - white_arrow_down
78
- - yellow_notification_icon
569
+ }
570
+ </script>
571
+ ```
572
+
573
+ ## 🎨 Theming
574
+
575
+ The library provides multiple built-in themes:
576
+
577
+ - **Green** (default) - Primary green theme
578
+ - **Primary Blue** - Professional blue theme
579
+ - **Secondary Blue** - Light blue accent theme
580
+ - **Red** - Error/warning red theme
581
+
582
+ ```vue
583
+ <Checkbox built_in_theme="primary-blue" />
584
+ <ResponsiveButton built_in_theme="outlined-primary" />
585
+ ```
586
+
587
+ ## 📱 Responsive Design
588
+
589
+ Components automatically adapt to different screen sizes:
590
+
591
+ - **Desktop**: Full feature set with optimal spacing
592
+ - **Mobile**: Compact layouts with touch-friendly interactions
593
+ - **Breakpoint**: 767px for mobile/desktop switching
594
+
595
+ ## ♿ Accessibility
596
+
597
+ All components include:
598
+
599
+ - ARIA attributes for screen readers
600
+ - Keyboard navigation support
601
+ - Focus management
602
+ - High contrast support
603
+ - Semantic HTML structure
604
+
605
+ ## 🛠 Development
606
+
607
+ ### Project Setup
608
+
609
+ ```bash
610
+ npm install
611
+ ```
612
+
613
+ ### Development Server
614
+
615
+ ```bash
616
+ npm run dev
617
+ ```
618
+
619
+ ### Build for Production
620
+
621
+ ```bash
622
+ npm run build
623
+ ```
624
+
625
+ ### Component Documentation
626
+
627
+ Visit the demo pages to see all components in action with interactive examples.
628
+
629
+ ## 📄 License
630
+
631
+ MIT License - see LICENSE file for details.
632
+
633
+ ## 🤝 Contributing
634
+
635
+ 1. Fork the repository
636
+ 2. Create a feature branch
637
+ 3. Make your changes
638
+ 4. Add tests and documentation
639
+ 5. Submit a pull request
640
+
641
+ ## 📞 Support
642
+
643
+ For questions and support, please open an issue on the GitHub repository.
644
+
645
+ ---
646
+
647
+ Built with ❤️ using Vue 3 and modern web technologies.
79
648