@velkymx/vibeui 0.9.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (205) hide show
  1. package/AGENTS.md +44 -0
  2. package/CLAUDE.md +48 -0
  3. package/README.md +222 -197
  4. package/dist/purify.es-1D-VntFD.mjs +679 -0
  5. package/dist/src/App.vue.d.ts +3 -0
  6. package/dist/src/components/HelloWorld.vue.d.ts +6 -0
  7. package/dist/src/components/VibeAccordion.vue.d.ts +89 -0
  8. package/dist/{components → src/components}/VibeAlert.vue.d.ts +18 -34
  9. package/dist/src/components/VibeAutocomplete.vue.d.ts +74 -0
  10. package/dist/{components → src/components}/VibeBadge.vue.d.ts +10 -18
  11. package/dist/src/components/VibeBreadcrumb.vue.d.ts +51 -0
  12. package/dist/{components → src/components}/VibeButton.vue.d.ts +13 -17
  13. package/dist/{components → src/components}/VibeButtonGroup.vue.d.ts +10 -18
  14. package/dist/{components → src/components}/VibeCard.vue.d.ts +51 -24
  15. package/dist/{components → src/components}/VibeCarousel.vue.d.ts +27 -49
  16. package/dist/src/components/VibeChartBar.vue.d.ts +61 -0
  17. package/dist/src/components/VibeChartLine.vue.d.ts +70 -0
  18. package/dist/src/components/VibeChartPie.vue.d.ts +34 -0
  19. package/dist/{components → src/components}/VibeCloseButton.vue.d.ts +9 -7
  20. package/dist/{components → src/components}/VibeCol.vue.d.ts +9 -17
  21. package/dist/src/components/VibeCollapse.vue.d.ts +76 -0
  22. package/dist/src/components/VibeContainer.vue.d.ts +36 -0
  23. package/dist/src/components/VibeDataTable.vue.d.ts +131 -0
  24. package/dist/{components → src/components}/VibeDatePicker.vue.d.ts +4 -6
  25. package/dist/{components → src/components}/VibeDraggable.vue.d.ts +9 -13
  26. package/dist/{components → src/components}/VibeDropdown.vue.d.ts +46 -43
  27. package/dist/{components → src/components}/VibeDroppable.vue.d.ts +9 -13
  28. package/dist/{components → src/components}/VibeFileInput.vue.d.ts +14 -20
  29. package/dist/{components → src/components}/VibeFormCheckbox.vue.d.ts +29 -21
  30. package/dist/{components → src/components}/VibeFormDatepicker.vue.d.ts +26 -29
  31. package/dist/{components → src/components}/VibeFormGroup.vue.d.ts +10 -13
  32. package/dist/{components → src/components}/VibeFormInput.vue.d.ts +22 -23
  33. package/dist/{components → src/components}/VibeFormRadio.vue.d.ts +22 -23
  34. package/dist/{components → src/components}/VibeFormSelect.vue.d.ts +27 -33
  35. package/dist/{components → src/components}/VibeFormSpinbutton.vue.d.ts +30 -33
  36. package/dist/{components → src/components}/VibeFormSwitch.vue.d.ts +21 -22
  37. package/dist/{components → src/components}/VibeFormTextarea.vue.d.ts +23 -24
  38. package/dist/{components → src/components}/VibeFormWysiwyg.vue.d.ts +22 -21
  39. package/dist/src/components/VibeHero.vue.d.ts +108 -0
  40. package/dist/{components → src/components}/VibeIcon.vue.d.ts +27 -7
  41. package/dist/{components → src/components}/VibeInputGroup.vue.d.ts +11 -13
  42. package/dist/{components → src/components}/VibeLink.vue.d.ts +14 -18
  43. package/dist/{components → src/components}/VibeListGroup.vue.d.ts +26 -22
  44. package/dist/{components → src/components}/VibeModal.vue.d.ts +29 -45
  45. package/dist/{components → src/components}/VibeNav.vue.d.ts +40 -20
  46. package/dist/{components → src/components}/VibeNavbar.vue.d.ts +12 -19
  47. package/dist/src/components/VibeNavbarBrand.vue.d.ts +34 -0
  48. package/dist/src/components/VibeNavbarNav.vue.d.ts +80 -0
  49. package/dist/{components → src/components}/VibeNavbarToggle.vue.d.ts +7 -5
  50. package/dist/{components → src/components}/VibeOffcanvas.vue.d.ts +28 -43
  51. package/dist/{components → src/components}/VibePagination.vue.d.ts +38 -29
  52. package/dist/{components → src/components}/VibePlaceholder.vue.d.ts +9 -17
  53. package/dist/src/components/VibePopover.vue.d.ts +72 -0
  54. package/dist/src/components/VibeProgress.vue.d.ts +37 -0
  55. package/dist/{components → src/components}/VibeResizable.vue.d.ts +10 -14
  56. package/dist/{components → src/components}/VibeRow.vue.d.ts +9 -17
  57. package/dist/src/components/VibeScrollspy.vue.d.ts +89 -0
  58. package/dist/{components → src/components}/VibeSkeleton.vue.d.ts +2 -1
  59. package/dist/{components → src/components}/VibeSlider.vue.d.ts +4 -5
  60. package/dist/src/components/VibeSortable.vue.d.ts +59 -0
  61. package/dist/{components → src/components}/VibeSpinner.vue.d.ts +4 -7
  62. package/dist/{components → src/components}/VibeStepper.vue.d.ts +32 -31
  63. package/dist/{components → src/components}/VibeTab.vue.d.ts +7 -11
  64. package/dist/src/components/VibeTabContent.vue.d.ts +56 -0
  65. package/dist/{components → src/components}/VibeTabs.vue.d.ts +9 -13
  66. package/dist/{components → src/components}/VibeToast.vue.d.ts +31 -44
  67. package/dist/{components → src/components}/VibeToastHost.vue.d.ts +2 -1
  68. package/dist/src/components/VibeTooltip.vue.d.ts +63 -0
  69. package/dist/src/components/chart/chartColors.d.ts +3 -0
  70. package/dist/src/components/chart/chartResize.d.ts +2 -0
  71. package/dist/src/components/chart/chartTooltip.d.ts +2 -0
  72. package/dist/src/components/chart/chartTypes.d.ts +6 -0
  73. package/dist/src/components/chart/drawBar.d.ts +5 -0
  74. package/dist/src/components/chart/drawLine.d.ts +18 -0
  75. package/dist/src/components/chart/drawPie.d.ts +4 -0
  76. package/dist/{components → src/components}/dndStore.d.ts +1 -0
  77. package/dist/{components → src/components}/index.d.ts +5 -1
  78. package/dist/{composables → src/composables}/useBreakpoints.d.ts +1 -0
  79. package/dist/{composables → src/composables}/useColorMode.d.ts +1 -1
  80. package/dist/src/composables/useId.d.ts +2 -0
  81. package/dist/{directives → src/directives}/vTooltip.d.ts +2 -3
  82. package/dist/src/injectionKeys.d.ts +28 -0
  83. package/dist/{types.d.ts → src/types.d.ts} +21 -2
  84. package/dist/src/utils/safeCss.d.ts +16 -0
  85. package/dist/src/utils/safeHref.d.ts +7 -0
  86. package/dist/src/utils/sanitizeHtml.d.ts +13 -0
  87. package/dist/vibeui.css +2 -1
  88. package/dist/vibeui.es.js +8269 -5855
  89. package/dist/vibeui.umd.js +3 -1
  90. package/docs/README.md +225 -0
  91. package/docs/components/advanced/popover.md +125 -0
  92. package/docs/components/advanced/scrollspy.md +106 -0
  93. package/docs/components/advanced/tooltip.md +90 -0
  94. package/docs/components/card/card.md +223 -0
  95. package/docs/components/charts/chart-bar.md +129 -0
  96. package/docs/components/charts/chart-line.md +136 -0
  97. package/docs/components/charts/chart-pie.md +102 -0
  98. package/docs/components/core/alert.md +94 -0
  99. package/docs/components/core/badge.md +75 -0
  100. package/docs/components/core/button-group.md +99 -0
  101. package/docs/components/core/button.md +127 -0
  102. package/docs/components/core/close-button.md +82 -0
  103. package/docs/components/core/link.md +79 -0
  104. package/docs/components/core/placeholder.md +129 -0
  105. package/docs/components/core/skeleton.md +40 -0
  106. package/docs/components/core/spinner.md +103 -0
  107. package/docs/components/data/datatable.md +435 -0
  108. package/docs/components/hero/hero.md +64 -0
  109. package/docs/components/interactive/accordion.md +121 -0
  110. package/docs/components/interactive/carousel.md +112 -0
  111. package/docs/components/interactive/collapse.md +106 -0
  112. package/docs/components/interactive/date-picker.md +78 -0
  113. package/docs/components/interactive/draggable.md +91 -0
  114. package/docs/components/interactive/dropdown.md +129 -0
  115. package/docs/components/interactive/modal.md +143 -0
  116. package/docs/components/interactive/offcanvas.md +92 -0
  117. package/docs/components/interactive/resizable.md +73 -0
  118. package/docs/components/interactive/slider.md +57 -0
  119. package/docs/components/interactive/sortable.md +52 -0
  120. package/docs/components/interactive/stepper.md +83 -0
  121. package/docs/components/interactive/tabs.md +66 -0
  122. package/docs/components/interactive/toast.md +177 -0
  123. package/docs/components/layout/col.md +117 -0
  124. package/docs/components/layout/container.md +53 -0
  125. package/docs/components/layout/row.md +107 -0
  126. package/docs/components/list/list-group.md +225 -0
  127. package/docs/components/navigation/breadcrumb.md +120 -0
  128. package/docs/components/navigation/nav.md +151 -0
  129. package/docs/components/navigation/navbar.md +108 -0
  130. package/docs/components/navigation/pagination.md +162 -0
  131. package/docs/components/progress/progress.md +188 -0
  132. package/docs/composables/back-button.md +28 -0
  133. package/docs/composables/breakpoints.md +54 -0
  134. package/docs/composables/color-mode.md +179 -0
  135. package/docs/composables/use-form.md +78 -0
  136. package/docs/composables/use-position.md +68 -0
  137. package/docs/composables/use-toast.md +91 -0
  138. package/docs/directives/v-tooltip.md +58 -0
  139. package/docs/forms/README.md +87 -0
  140. package/docs/forms/autocomplete.md +96 -0
  141. package/docs/forms/file-input.md +97 -0
  142. package/docs/forms/form-checkbox.md +100 -0
  143. package/docs/forms/form-datepicker.md +80 -0
  144. package/docs/forms/form-group.md +115 -0
  145. package/docs/forms/form-input.md +112 -0
  146. package/docs/forms/form-radio.md +82 -0
  147. package/docs/forms/form-select.md +98 -0
  148. package/docs/forms/form-spinbutton.md +94 -0
  149. package/docs/forms/form-switch.md +75 -0
  150. package/docs/forms/form-textarea.md +82 -0
  151. package/docs/forms/form-wysiwyg.md +99 -0
  152. package/docs/forms/input-group.md +70 -0
  153. package/docs/forms/validation.md +213 -0
  154. package/docs/getting-started/starter-template.md +136 -0
  155. package/docs/superpowers/plans/2026-05-12-vibe-charts.md +1999 -0
  156. package/docs/superpowers/plans/2026-05-29-e2e-browser-testing.md +615 -0
  157. package/docs/superpowers/specs/2026-05-12-charting-design.md +185 -0
  158. package/docs/superpowers/specs/2026-05-29-e2e-browser-testing-design.md +168 -0
  159. package/docs/utilities/position.md +82 -0
  160. package/docs/versioning.md +57 -0
  161. package/examples/README.md +201 -0
  162. package/examples/album.html +262 -0
  163. package/examples/blog.html +166 -0
  164. package/examples/carousel.html +114 -0
  165. package/examples/checkout.html +275 -0
  166. package/examples/cover.html +89 -0
  167. package/examples/dashboard.html +156 -0
  168. package/examples/index.html +129 -0
  169. package/examples/jumbotron.html +56 -0
  170. package/examples/mobile-dashboard.html +138 -0
  171. package/examples/pricing.html +172 -0
  172. package/examples/product.html +145 -0
  173. package/examples/sidebars.html +166 -0
  174. package/examples/sign-in.html +119 -0
  175. package/examples/starter.html +314 -0
  176. package/examples/sticky-footer-navbar.html +70 -0
  177. package/examples/sticky-footer.html +60 -0
  178. package/examples/test-simple.html +91 -0
  179. package/llms.txt +782 -0
  180. package/package.json +44 -14
  181. package/dist/App.vue.d.ts +0 -2
  182. package/dist/components/HelloWorld.vue.d.ts +0 -5
  183. package/dist/components/VibeAccordion.vue.d.ts +0 -90
  184. package/dist/components/VibeAutocomplete.vue.d.ts +0 -46
  185. package/dist/components/VibeBreadcrumb.vue.d.ts +0 -47
  186. package/dist/components/VibeCollapse.vue.d.ts +0 -84
  187. package/dist/components/VibeContainer.vue.d.ts +0 -44
  188. package/dist/components/VibeDataTable.vue.d.ts +0 -76
  189. package/dist/components/VibeNavbarBrand.vue.d.ts +0 -42
  190. package/dist/components/VibeNavbarNav.vue.d.ts +0 -61
  191. package/dist/components/VibePopover.vue.d.ts +0 -95
  192. package/dist/components/VibeProgress.vue.d.ts +0 -45
  193. package/dist/components/VibeScrollspy.vue.d.ts +0 -77
  194. package/dist/components/VibeSortable.vue.d.ts +0 -40
  195. package/dist/components/VibeTabContent.vue.d.ts +0 -74
  196. package/dist/components/VibeTooltip.vue.d.ts +0 -86
  197. package/dist/composables/useId.d.ts +0 -5
  198. package/dist/types/index.d.ts +0 -6
  199. /package/dist/{composables → src/composables}/useBackButton.d.ts +0 -0
  200. /package/dist/{composables → src/composables}/useForm.d.ts +0 -0
  201. /package/dist/{composables → src/composables}/useFormValidation.d.ts +0 -0
  202. /package/dist/{composables → src/composables}/usePosition.d.ts +0 -0
  203. /package/dist/{composables → src/composables}/useToast.d.ts +0 -0
  204. /package/dist/{index.d.ts → src/index.d.ts} +0 -0
  205. /package/dist/{main.d.ts → src/main.d.ts} +0 -0
@@ -0,0 +1,188 @@
1
+ # VibeProgress
2
+
3
+ Data-driven progress bar component supporting single or multiple bars.
4
+
5
+ ## Props
6
+
7
+ | Prop | Type | Default | Description |
8
+ |------|------|---------|-------------|
9
+ | `height` | `String` | `undefined` | Custom height (e.g., `'20px'`, `'2rem'`). Validated as a CSS length; invalid values are ignored |
10
+ | `bars` | `ProgressBar[]` | Required | Array of progress bars to display |
11
+
12
+ ### ProgressBar Interface
13
+
14
+ ```typescript
15
+ interface ProgressBar {
16
+ value: number
17
+ max?: number
18
+ variant?: Variant
19
+ striped?: boolean
20
+ animated?: boolean
21
+ label?: string
22
+ showValue?: boolean
23
+ }
24
+ ```
25
+
26
+ ## Slots
27
+
28
+ | Slot | Scope | Description |
29
+ |------|-------|-------------|
30
+ | `label` | `{ bar, index }` | Custom label rendering for each bar |
31
+
32
+ ## Usage
33
+
34
+ ### Basic Progress
35
+
36
+ ```vue
37
+ <template>
38
+ <VibeProgress :bars="[{ value: 25 }]" />
39
+ </template>
40
+ ```
41
+
42
+ ### With Label
43
+
44
+ ```vue
45
+ <template>
46
+ <VibeProgress :bars="[{ value: 75, showValue: true }]" />
47
+ </template>
48
+ ```
49
+
50
+ ### Custom Height
51
+
52
+ ```vue
53
+ <template>
54
+ <VibeProgress height="20px" :bars="[{ value: 50 }]" />
55
+ </template>
56
+ ```
57
+
58
+ ### Colored Progress Bars
59
+
60
+ ```vue
61
+ <template>
62
+ <div class="d-flex flex-column gap-2">
63
+ <VibeProgress :bars="[{ value: 25, variant: 'success' }]" />
64
+ <VibeProgress :bars="[{ value: 50, variant: 'info' }]" />
65
+ <VibeProgress :bars="[{ value: 75, variant: 'warning' }]" />
66
+ <VibeProgress :bars="[{ value: 100, variant: 'danger' }]" />
67
+ </div>
68
+ </template>
69
+ ```
70
+
71
+ ### Striped and Animated
72
+
73
+ ```vue
74
+ <template>
75
+ <div class="d-flex flex-column gap-2">
76
+ <!-- Striped -->
77
+ <VibeProgress :bars="[{ value: 60, variant: 'success', striped: true }]" />
78
+
79
+ <!-- Animated stripes -->
80
+ <VibeProgress :bars="[{ value: 75, variant: 'info', animated: true }]" />
81
+ </div>
82
+ </template>
83
+ ```
84
+
85
+ ### Multiple Bars
86
+
87
+ Stack multiple progress bars in a single component:
88
+
89
+ ```vue
90
+ <template>
91
+ <VibeProgress :bars="progressBars" />
92
+ </template>
93
+
94
+ <script setup>
95
+ const progressBars = [
96
+ { value: 15, variant: 'success' },
97
+ { value: 30, variant: 'warning' },
98
+ { value: 20, variant: 'danger' }
99
+ ]
100
+ </script>
101
+ ```
102
+
103
+ ### Dynamic Progress
104
+
105
+ ```vue
106
+ <script setup>
107
+ import { ref, onMounted } from 'vue'
108
+
109
+ const progress = ref(0)
110
+
111
+ onMounted(() => {
112
+ const interval = setInterval(() => {
113
+ if (progress.value < 100) {
114
+ progress.value += 10
115
+ } else {
116
+ clearInterval(interval)
117
+ }
118
+ }, 500)
119
+ })
120
+ </script>
121
+
122
+ <template>
123
+ <VibeProgress :bars="[{
124
+ value: progress,
125
+ variant: 'primary',
126
+ showValue: true
127
+ }]" />
128
+ </template>
129
+ ```
130
+
131
+ ### Custom Label Rendering
132
+
133
+ Use the `label` scoped slot for custom labels:
134
+
135
+ ```vue
136
+ <template>
137
+ <VibeProgress :bars="progressBars">
138
+ <template #label="{ bar }">
139
+ <strong>{{ bar.value }}% Complete</strong>
140
+ </template>
141
+ </VibeProgress>
142
+ </template>
143
+
144
+ <script setup>
145
+ const progressBars = [{ value: 75, variant: 'success' }]
146
+ </script>
147
+ ```
148
+
149
+ ### Complex Example
150
+
151
+ ```vue
152
+ <script setup>
153
+ import { ref } from 'vue'
154
+
155
+ const tasks = ref([
156
+ { name: 'Design', value: 100, variant: 'success' },
157
+ { name: 'Development', value: 75, variant: 'primary', animated: true },
158
+ { name: 'Testing', value: 50, variant: 'warning', striped: true },
159
+ { name: 'Deployment', value: 0, variant: 'secondary' }
160
+ ])
161
+ </script>
162
+
163
+ <template>
164
+ <div class="d-flex flex-column gap-3">
165
+ <div v-for="task in tasks" :key="task.name">
166
+ <div class="d-flex justify-content-between mb-1">
167
+ <span>{{ task.name }}</span>
168
+ <span>{{ task.value }}%</span>
169
+ </div>
170
+ <VibeProgress :bars="[task]" />
171
+ </div>
172
+ </div>
173
+ </template>
174
+ ```
175
+
176
+ ## Important Notes
177
+
178
+ **Validated height:** The `height` prop is validated as a CSS length string before being applied (CSS injection defense). Invalid values are ignored and the default Bootstrap height is used.
179
+
180
+ **Safe values:** Each bar's percentage is clamped to 0–100. A `value` of `NaN` (or a non-finite number) renders safely as 0%. A `max` of `0` or less also renders as 0%.
181
+
182
+ ## Bootstrap CSS Classes
183
+
184
+ - `.progress`
185
+ - `.progress-bar`
186
+ - `.bg-{variant}`
187
+ - `.progress-bar-striped`
188
+ - `.progress-bar-animated`
@@ -0,0 +1,28 @@
1
+ # useBackButton
2
+
3
+ A composable to handle the Android hardware back button in hybrid mobile environments (like Capacitor or WebView apps).
4
+
5
+ ## Basic Usage
6
+
7
+ ```javascript
8
+ import { useBackButton } from '@velkymx/vibeui'
9
+
10
+ // Automatically closes this logic when the back button is pressed
11
+ useBackButton(() => {
12
+ console.log('Back button pressed!')
13
+ closeMyOverlay()
14
+ })
15
+ ```
16
+
17
+ ## How it Works
18
+ - It listens for the native `backbutton` event emitted by hybrid wrappers.
19
+ - It maintains an internal stack of "closeable" actions.
20
+ - When the back button is pressed, it executes the most recently registered action and prevents the default app-exit behavior.
21
+ - It automatically handles cleanup when the component using it is unmounted.
22
+
23
+ ## Integrated Components
24
+ The following VibeUI components have `useBackButton` integrated automatically:
25
+ - `VibeModal`
26
+ - `VibeOffcanvas`
27
+
28
+ You don't need to manually use this composable if you are using these components; they will "just work" on Android devices.
@@ -0,0 +1,54 @@
1
+ # useBreakpoints
2
+
3
+ A composable for programmatic breakpoint detection using standard Bootstrap grid sizes.
4
+
5
+ ## Basic Usage
6
+
7
+ ```javascript
8
+ import { useBreakpoints } from '@velkymx/vibeui'
9
+
10
+ const { isMobile, isTablet, isLg, isXxl } = useBreakpoints()
11
+ ```
12
+
13
+ ## API
14
+
15
+ | Property | Type | Description |
16
+ |---|---|---|
17
+ | `isXs` | `ComputedRef<boolean>` | `true` if width < 576px |
18
+ | `isSm` | `Ref<boolean>` | `true` if width >= 576px |
19
+ | `isMd` | `Ref<boolean>` | `true` if width >= 768px |
20
+ | `isLg` | `Ref<boolean>` | `true` if width >= 992px |
21
+ | `isXl` | `Ref<boolean>` | `true` if width >= 1200px |
22
+ | `isXxl` | `Ref<boolean>` | `true` if width >= 1400px |
23
+ | `isMobile` | `ComputedRef<boolean>` | `true` if screen is `xs` or `sm` (width < 768px) |
24
+ | `isTablet` | `ComputedRef<boolean>` | `true` if screen is exactly `md` (768px - 991px) |
25
+
26
+ ## Performance
27
+ This composable uses `window.matchMedia` listeners instead of `resize` events, making it highly performant and efficient. It automatically cleans up listeners when the component is unmounted.
28
+
29
+ ## Example: Adaptive Layout
30
+
31
+ ```vue
32
+ <template>
33
+ <div>
34
+ <!-- Show offcanvas navigation on mobile, standard nav on desktop -->
35
+ <VibeOffcanvas v-if="isMobile" v-model="showSidebar">
36
+ <MySidebarContent />
37
+ </VibeOffcanvas>
38
+
39
+ <VibeRow v-else>
40
+ <VibeCol cols="3">
41
+ <MySidebarContent />
42
+ </VibeCol>
43
+ <VibeCol cols="9">
44
+ <slot />
45
+ </VibeCol>
46
+ </VibeRow>
47
+ </div>
48
+ </template>
49
+
50
+ <script setup>
51
+ import { useBreakpoints } from '@velkymx/vibeui'
52
+ const { isMobile } = useBreakpoints()
53
+ </script>
54
+ ```
@@ -0,0 +1,179 @@
1
+ # useColorMode
2
+
3
+ Manages Bootstrap 5.3 color modes (`light`, `dark`, `auto`) for the entire app. Sets the `data-bs-theme` attribute on `<html>`, persists the user's preference to `localStorage`, and provides a reactive ref that components can watch.
4
+
5
+ **Singleton** — every call to `useColorMode()` returns the same shared state. There is one active color mode per app.
6
+
7
+ ## Import
8
+
9
+ ```ts
10
+ import { useColorMode } from '@velkymx/vibeui'
11
+ ```
12
+
13
+ ## Return Values
14
+
15
+ | Name | Type | Description |
16
+ |---|---|---|
17
+ | `colorMode` | `Ref<ColorMode>` | Reactive current mode. Bind to template or watch for changes. |
18
+ | `setColorMode(mode)` | `(mode: ColorMode) => void` | Set a specific mode. Persists to `localStorage` and updates `<html data-bs-theme>`. |
19
+ | `toggleColorMode()` | `() => void` | Cycle through `light → dark → auto → light`. |
20
+ | `initColorMode()` | `() => void` | Restore the saved preference from `localStorage` and attach the system-theme listener. **Call once at app startup, before mount.** Subsequent calls are no-ops while already initialized. |
21
+ | `clearColorMode()` | `() => void` | Reset to `auto`, remove the `localStorage` key, and **keep the system listener attached** so auto mode stays reactive to OS theme changes. |
22
+ | `onColorModeChange(cb)` | `(cb: (mode: ColorMode) => void) => () => void` | Register a callback that fires whenever the mode changes. Returns an **unsubscribe function** — call it to remove the callback and avoid leaks. Useful for hybrid app status bars. |
23
+
24
+ ## Type
25
+
26
+ ```ts
27
+ type ColorMode = 'light' | 'dark' | 'auto'
28
+ ```
29
+
30
+ | Value | Behavior |
31
+ |---|---|
32
+ | `'light'` | Forces light theme regardless of OS setting |
33
+ | `'dark'` | Forces dark theme regardless of OS setting |
34
+ | `'auto'` | Follows the OS `prefers-color-scheme` media query |
35
+
36
+ ## Setup
37
+
38
+ Call `initColorMode()` once in `main.ts` before mounting the app. This restores the user's last saved preference.
39
+
40
+ ```ts
41
+ // main.ts
42
+ import { createApp } from 'vue'
43
+ import VibeUI, { useColorMode } from '@velkymx/vibeui'
44
+ import 'bootstrap/dist/css/bootstrap.min.css'
45
+ import App from './App.vue'
46
+
47
+ const { initColorMode } = useColorMode()
48
+ initColorMode()
49
+
50
+ createApp(App).use(VibeUI).mount('#app')
51
+ ```
52
+
53
+ ## Usage
54
+
55
+ ### Toggle button
56
+
57
+ The most common use case — a button that cycles through all three modes.
58
+
59
+ ```vue
60
+ <script setup lang="ts">
61
+ import { useColorMode } from '@velkymx/vibeui'
62
+
63
+ const { colorMode, toggleColorMode } = useColorMode()
64
+ </script>
65
+
66
+ <template>
67
+ <VibeButton variant="secondary" @click="toggleColorMode">
68
+ Theme: {{ colorMode }}
69
+ </VibeButton>
70
+ </template>
71
+ ```
72
+
73
+ ### Explicit mode switcher
74
+
75
+ A select or set of buttons that lets the user pick a specific mode.
76
+
77
+ ```vue
78
+ <script setup lang="ts">
79
+ import { useColorMode } from '@velkymx/vibeui'
80
+ import type { ColorMode } from '@velkymx/vibeui'
81
+
82
+ const { colorMode, setColorMode } = useColorMode()
83
+
84
+ const options: { label: string; value: ColorMode }[] = [
85
+ { label: 'Light', value: 'light' },
86
+ { label: 'Dark', value: 'dark' },
87
+ { label: 'System', value: 'auto' },
88
+ ]
89
+ </script>
90
+
91
+ <template>
92
+ <div class="btn-group">
93
+ <VibeButton
94
+ v-for="option in options"
95
+ :key="option.value"
96
+ :variant="colorMode === option.value ? 'primary' : 'secondary'"
97
+ :outline="colorMode !== option.value"
98
+ @click="setColorMode(option.value)"
99
+ >
100
+ {{ option.label }}
101
+ </VibeButton>
102
+ </div>
103
+ </template>
104
+ ```
105
+
106
+ ### Watching for changes
107
+
108
+ React to color mode changes anywhere in the app — for example, to swap a chart theme.
109
+
110
+ ```vue
111
+ <script setup lang="ts">
112
+ import { watch } from 'vue'
113
+ import { useColorMode } from '@velkymx/vibeui'
114
+
115
+ const { colorMode } = useColorMode()
116
+
117
+ watch(colorMode, (mode) => {
118
+ console.log('Color mode changed to:', mode)
119
+ // e.g. update a third-party chart library's theme
120
+ })
121
+ </script>
122
+ ```
123
+
124
+ ### Subscribing outside a component
125
+
126
+ `onColorModeChange` is for non-reactive sinks (a hybrid app status bar, an analytics call). It returns an unsubscribe function — call it on teardown.
127
+
128
+ ```vue
129
+ <script setup lang="ts">
130
+ import { onUnmounted } from 'vue'
131
+ import { useColorMode } from '@velkymx/vibeui'
132
+
133
+ const { onColorModeChange } = useColorMode()
134
+
135
+ const off = onColorModeChange((mode) => {
136
+ // e.g. sync the native status bar color in a Capacitor app
137
+ })
138
+
139
+ onUnmounted(off)
140
+ </script>
141
+ ```
142
+
143
+ ### Resetting to system default
144
+
145
+ `clearColorMode` removes the saved preference and returns to `auto`. The OS theme listener stays attached, so the app keeps tracking system theme changes afterward.
146
+
147
+ ```vue
148
+ <script setup lang="ts">
149
+ import { useColorMode } from '@velkymx/vibeui'
150
+
151
+ const { clearColorMode } = useColorMode()
152
+ </script>
153
+
154
+ <template>
155
+ <VibeButton variant="secondary" outline @click="clearColorMode">
156
+ Use system default
157
+ </VibeButton>
158
+ </template>
159
+ ```
160
+
161
+ ### Scoping color mode to a single component
162
+
163
+ Bootstrap's `data-bs-theme` attribute works on any element, not just `<html>`. For cases where one section of the page should always be dark regardless of the app-level setting, apply it directly in the template instead of using the composable.
164
+
165
+ ```vue
166
+ <template>
167
+ <div data-bs-theme="dark">
168
+ <!-- This section is always dark -->
169
+ <VibeCard>Always dark card</VibeCard>
170
+ </div>
171
+ </template>
172
+ ```
173
+ ## Behavior Notes
174
+
175
+ - **Persistence** — the selected mode is stored in `localStorage` under the key `vibe-color-mode`. It survives page reloads.
176
+ - **System Theme Reactivity** — when the mode is set to `'auto'`, VibeUI automatically listens for OS-level theme changes (via `matchMedia`) and updates the DOM immediately without a page reload.
177
+ - **SSR-safe** — all `document` and `localStorage` access is guarded. The composable is safe to import in server-rendered environments; applying the theme simply does nothing when `document` is undefined. On the client, `initColorMode()` is the real init path that reads storage and attaches the listener.
178
+ - **Invalid values** — `setColorMode` silently ignores any value that is not `'light'`, `'dark'`, or `'auto'`. No error is thrown.
179
+ - **`initColorMode` is idempotent** — while initialized, repeat calls are no-ops and do not overwrite changes the user made after init. Calling `clearColorMode()` un-initializes the singleton, so a later `initColorMode()` can re-read storage.
@@ -0,0 +1,78 @@
1
+ # useForm
2
+
3
+ Multi-field form composable for managing groups of related fields with reactive values, errors, and touched state. For single-field validation use `useFormValidation` instead.
4
+
5
+ ## Quick start
6
+
7
+ ```ts
8
+ import { useForm, validators } from '@velkymx/vibeui'
9
+
10
+ const { fields, errors, touched, isDirty, isValid, validate, reset, markTouched } = useForm({
11
+ name: '',
12
+ email: '',
13
+ age: 0
14
+ })
15
+
16
+ const submit = async () => {
17
+ const result = await validate({
18
+ name: [validators.required('Name is required')],
19
+ email: [validators.required(), validators.email()]
20
+ })
21
+ if (!result.valid) return
22
+ // submit fields...
23
+ }
24
+ ```
25
+
26
+ ## Why a separate composable?
27
+
28
+ `useFormValidation` returns `{ value: Ref<T>, ... }` for a single field. In TypeScript templates the nested `value` ref does not auto-unwrap deeply — to keep template type-checking clean you must destructure once at script level (`const { value: name } = useFormValidation('')`).
29
+
30
+ For multi-field forms, that destructure pattern multiplied per field becomes noisy. `useForm` returns a single reactive `fields` object: properties are auto-unwrapped reactively, so `{{ fields.name }}` works directly in templates with full TypeScript inference.
31
+
32
+ ## Returned API
33
+
34
+ | Key | Type | Description |
35
+ |---|---|---|
36
+ | `fields` | `T` (reactive) | Mutable field values. `fields.name = 'x'` updates state. |
37
+ | `errors` | `Record<keyof T, string>` (reactive) | Per-field error message. Empty string = no error. |
38
+ | `touched` | `Record<keyof T, boolean>` (reactive) | Per-field touched flag. |
39
+ | `isDirty` | `ComputedRef<boolean>` | True if any field differs from initial. |
40
+ | `isValid` | `ComputedRef<boolean>` | `false` until the first `validate()` or `validateField()` call; then `true` if no field has an error. |
41
+ | `values` | `ComputedRef<T>` | Snapshot of current field values. |
42
+ | `validate(rules)` | `(rules) => Promise<{ valid, errors }>` | Run rules; populates `errors` map. |
43
+ | `validateField(key, rule)` | `(key, rule) => Promise<string>` | Run rules for one field. |
44
+ | `reset()` | `() => void` | Restore initial values, clear errors/touched. |
45
+ | `markTouched(key)` | `(key) => void` | Mark single field touched. |
46
+ | `markAllTouched()` | `() => void` | Mark every field touched (use on submit). |
47
+ | `setField(key, value)` | `(key, value) => void` | Programmatic field update. |
48
+
49
+ ## Validation rules
50
+
51
+ `validate(rules)` accepts a record mapping field keys to either a `ValidatorFunction` or a `ValidationRule[]` array. Reuse the built-in validators or pass arbitrary functions.
52
+
53
+ ```ts
54
+ const result = await validate({
55
+ email: [validators.required(), validators.email()],
56
+ age: [validators.min(18, 'Must be 18+')],
57
+ bio: (value) => typeof value === 'string' && value.length <= 500
58
+ })
59
+ ```
60
+
61
+ ## Single-field destructure pattern
62
+
63
+ If you only have one field, prefer `useFormValidation` and destructure at the call site:
64
+
65
+ ```ts
66
+ import { useFormValidation } from '@velkymx/vibeui'
67
+
68
+ const { value: name, validationState, validationMessage, validate } = useFormValidation('')
69
+ ```
70
+
71
+ In the template, top-level refs auto-unwrap:
72
+
73
+ ```vue
74
+ <input v-model="name" />
75
+ <span v-if="validationState === 'invalid'">{{ validationMessage }}</span>
76
+ ```
77
+
78
+ Avoid `const form = useFormValidation('')` followed by `{{ form.value }}` in templates — the nested ref type does not unwrap cleanly under Volar.
@@ -0,0 +1,68 @@
1
+ # usePosition
2
+
3
+ JS positioning helper for anchoring one element relative to another. Wraps `@floating-ui/dom` with a natural-language anchor-point API similar to jQuery UI's `.position()`.
4
+
5
+ Use this when CSS position utilities (`docs/utilities/position.md`) aren't enough — typically dynamic menus, callouts, picker overlays, drag ghosts.
6
+
7
+ ## Quick start
8
+
9
+ ```vue
10
+ <script setup>
11
+ import { ref } from 'vue'
12
+ import { usePosition } from '@velkymx/vibeui'
13
+
14
+ const anchor = ref(null)
15
+ const target = ref(null)
16
+
17
+ const { x, y, placement, update, stop } = usePosition(target, anchor, {
18
+ my: 'top center',
19
+ at: 'bottom center',
20
+ offset: [0, 8],
21
+ collision: 'flip+shift'
22
+ })
23
+ </script>
24
+
25
+ <template>
26
+ <button ref="anchor">Anchor</button>
27
+ <div ref="target">I float below the button.</div>
28
+ </template>
29
+ ```
30
+
31
+ The composable applies `position`, `left`, and `top` styles directly to the target element.
32
+
33
+ ## Anchor points
34
+
35
+ Both `my` and `at` accept any of the 9 natural-language points:
36
+
37
+ ```
38
+ top start | top center | top end
39
+ center start | center center | center end
40
+ bottom start | bottom center | bottom end
41
+ ```
42
+
43
+ `my` is the point on the target that is being placed; `at` is the point on the anchor that the target is aligned to.
44
+
45
+ ## Options
46
+
47
+ | Option | Type | Default | Description |
48
+ |---|---|---|---|
49
+ | `my` | anchor point | `'top center'` | Point on target |
50
+ | `at` | anchor point | `'bottom center'` | Point on anchor |
51
+ | `offset` | `[skid, distance]` | `[0, 0]` | Tweak placement |
52
+ | `collision` | `'flip' \| 'shift' \| 'flip+shift' \| 'none'` | `'flip+shift'` | Viewport handling |
53
+ | `autoUpdate` | `boolean` | `true` | Reposition on scroll/resize/mutation |
54
+ | `strategy` | `'absolute' \| 'fixed'` | `'absolute'` | CSS positioning strategy |
55
+
56
+ ## Returns
57
+
58
+ | Key | Type | Description |
59
+ |---|---|---|
60
+ | `x` | `Ref<number>` | Latest left coordinate |
61
+ | `y` | `Ref<number>` | Latest top coordinate |
62
+ | `placement` | `Ref<Placement>` | Resolved placement (post-flip) |
63
+ | `update()` | `() => Promise<void>` | Recompute manually |
64
+ | `stop()` | `() => void` | Tear down `autoUpdate` listeners |
65
+
66
+ ## Internal use
67
+
68
+ `VibeDropdown`, `VibeTooltip`, `VibePopover`, `VibeDatePicker`, and `VibeAutocomplete` currently use Bootstrap's bundled Popper or component-local positioning rather than `usePosition`.
@@ -0,0 +1,91 @@
1
+ # useToast
2
+
3
+ Global toast service. Call `useToast()` from anywhere and dispatch toast messages without managing a `<VibeToast>` component instance per call site. A single `<VibeToastHost />` mounted at the root renders the queue.
4
+
5
+ ## Setup
6
+
7
+ Mount `<VibeToastHost />` once in your app shell:
8
+
9
+ ```vue
10
+ <!-- App.vue -->
11
+ <template>
12
+ <RouterView />
13
+ <VibeToastHost />
14
+ </template>
15
+ ```
16
+
17
+ ## API
18
+
19
+ ```ts
20
+ import { useToast } from '@velkymx/vibeui'
21
+
22
+ const toast = useToast()
23
+ ```
24
+
25
+ | Method | Variant | Notes |
26
+ |--------|---------|-------|
27
+ | `show(body, options?)` | `options.variant` | Generic |
28
+ | `success(body, options?)` | `success` | |
29
+ | `error(body, options?)` | `danger` | |
30
+ | `warn(body, options?)` | `warning` | |
31
+ | `info(body, options?)` | `info` | |
32
+ | `dismiss(id)` | — | Remove a specific toast. Returns `true` if found. |
33
+ | `clear()` | — | Remove all toasts |
34
+ | `toasts` | — | Read-only reactive list |
35
+
36
+ ### Options
37
+
38
+ ```ts
39
+ interface ToastShowOptions {
40
+ id?: string // override auto-generated id
41
+ title?: string
42
+ variant?: Variant
43
+ placement?: ToastPlacement // 'top-end' (default) etc.
44
+ autohide?: boolean // default true
45
+ delay?: number // ms, default 5000
46
+ }
47
+ ```
48
+
49
+ Each call returns the resolved `ToastSpec`, including the assigned `id` if you didn't provide one. Use it later to `dismiss(id)`.
50
+
51
+ ## Examples
52
+
53
+ ```ts
54
+ const toast = useToast()
55
+
56
+ toast.success('Saved successfully')
57
+ toast.error('Could not save: network error', { delay: 8000 })
58
+
59
+ const sticky = toast.warn('Long-running export…', { autohide: false })
60
+ // later
61
+ toast.dismiss(sticky.id)
62
+ ```
63
+
64
+ ### Programmatic clear on route change
65
+
66
+ ```ts
67
+ import { useToast } from '@velkymx/vibeui'
68
+ import { useRouter } from 'vue-router'
69
+
70
+ const toast = useToast()
71
+ const router = useRouter()
72
+ router.afterEach(() => toast.clear())
73
+ ```
74
+
75
+ ## Coexistence with `<VibeToast>`
76
+
77
+ The component-form `<VibeToast v-model="show">` still works — useful when a toast lives logically inside a single view. `useToast` is for app-wide notifications dispatched from composables, async handlers, or non-component code.
78
+
79
+ > Mixing both in the same app produces two `.toast-container`s at the same placement (one from `<VibeToastHost>`, one per standalone `<VibeToast>`), which Bootstrap stacks side-by-side rather than vertically. If that's visually unwanted, pick one form per app.
80
+
81
+ ## SSR
82
+
83
+ The toast queue is a module-level singleton. In SSR runtimes the same store is reused across requests; call `resetToastStoreForSSR()` from your server entry's per-request reset hook to avoid leaking one request's pending toasts into another's render output.
84
+
85
+ ```ts
86
+ import { resetToastStoreForSSR } from '@velkymx/vibeui'
87
+
88
+ // In your server's request handler:
89
+ resetToastStoreForSSR()
90
+ const app = createSSRApp(/* ... */)
91
+ ```