@velkymx/vibeui 0.8.2 → 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 (200) hide show
  1. package/AGENTS.md +44 -0
  2. package/README.md +245 -58
  3. package/dist/purify.es-1D-VntFD.mjs +679 -0
  4. package/dist/src/App.vue.d.ts +3 -0
  5. package/dist/src/components/HelloWorld.vue.d.ts +6 -0
  6. package/dist/src/components/VibeAccordion.vue.d.ts +89 -0
  7. package/dist/{components → src/components}/VibeAlert.vue.d.ts +24 -30
  8. package/dist/src/components/VibeAutocomplete.vue.d.ts +74 -0
  9. package/dist/{components → src/components}/VibeBadge.vue.d.ts +10 -18
  10. package/dist/src/components/VibeBreadcrumb.vue.d.ts +51 -0
  11. package/dist/{components → src/components}/VibeButton.vue.d.ts +16 -20
  12. package/dist/{components → src/components}/VibeButtonGroup.vue.d.ts +10 -18
  13. package/dist/{components → src/components}/VibeCard.vue.d.ts +51 -24
  14. package/dist/{components → src/components}/VibeCarousel.vue.d.ts +27 -49
  15. package/dist/src/components/VibeChartBar.vue.d.ts +61 -0
  16. package/dist/src/components/VibeChartLine.vue.d.ts +70 -0
  17. package/dist/src/components/VibeChartPie.vue.d.ts +34 -0
  18. package/dist/{components → src/components}/VibeCloseButton.vue.d.ts +9 -7
  19. package/dist/{components → src/components}/VibeCol.vue.d.ts +9 -17
  20. package/dist/src/components/VibeCollapse.vue.d.ts +76 -0
  21. package/dist/src/components/VibeContainer.vue.d.ts +36 -0
  22. package/dist/src/components/VibeDataTable.vue.d.ts +131 -0
  23. package/dist/src/components/VibeDatePicker.vue.d.ts +120 -0
  24. package/dist/src/components/VibeDraggable.vue.d.ts +77 -0
  25. package/dist/{components → src/components}/VibeDropdown.vue.d.ts +46 -43
  26. package/dist/src/components/VibeDroppable.vue.d.ts +70 -0
  27. package/dist/src/components/VibeFileInput.vue.d.ts +127 -0
  28. package/dist/{components → src/components}/VibeFormCheckbox.vue.d.ts +30 -22
  29. package/dist/{components → src/components}/VibeFormDatepicker.vue.d.ts +27 -30
  30. package/dist/{components → src/components}/VibeFormGroup.vue.d.ts +11 -14
  31. package/dist/{components → src/components}/VibeFormInput.vue.d.ts +22 -23
  32. package/dist/{components → src/components}/VibeFormRadio.vue.d.ts +23 -24
  33. package/dist/{components → src/components}/VibeFormSelect.vue.d.ts +28 -34
  34. package/dist/{components → src/components}/VibeFormSpinbutton.vue.d.ts +31 -34
  35. package/dist/{components → src/components}/VibeFormSwitch.vue.d.ts +22 -23
  36. package/dist/{components → src/components}/VibeFormTextarea.vue.d.ts +24 -25
  37. package/dist/{components → src/components}/VibeFormWysiwyg.vue.d.ts +24 -23
  38. package/dist/src/components/VibeHero.vue.d.ts +108 -0
  39. package/dist/{components → src/components}/VibeIcon.vue.d.ts +27 -7
  40. package/dist/{components → src/components}/VibeInputGroup.vue.d.ts +11 -13
  41. package/dist/{components → src/components}/VibeLink.vue.d.ts +14 -18
  42. package/dist/{components → src/components}/VibeListGroup.vue.d.ts +26 -22
  43. package/dist/{components → src/components}/VibeModal.vue.d.ts +29 -45
  44. package/dist/{components → src/components}/VibeNav.vue.d.ts +40 -20
  45. package/dist/{components → src/components}/VibeNavbar.vue.d.ts +12 -19
  46. package/dist/src/components/VibeNavbarBrand.vue.d.ts +34 -0
  47. package/dist/src/components/VibeNavbarNav.vue.d.ts +80 -0
  48. package/dist/{components → src/components}/VibeNavbarToggle.vue.d.ts +7 -5
  49. package/dist/{components → src/components}/VibeOffcanvas.vue.d.ts +28 -43
  50. package/dist/{components → src/components}/VibePagination.vue.d.ts +38 -29
  51. package/dist/{components → src/components}/VibePlaceholder.vue.d.ts +9 -17
  52. package/dist/src/components/VibePopover.vue.d.ts +72 -0
  53. package/dist/src/components/VibeProgress.vue.d.ts +37 -0
  54. package/dist/src/components/VibeResizable.vue.d.ts +140 -0
  55. package/dist/{components → src/components}/VibeRow.vue.d.ts +9 -17
  56. package/dist/src/components/VibeScrollspy.vue.d.ts +89 -0
  57. package/dist/src/components/VibeSkeleton.vue.d.ts +54 -0
  58. package/dist/src/components/VibeSlider.vue.d.ts +77 -0
  59. package/dist/src/components/VibeSortable.vue.d.ts +59 -0
  60. package/dist/{components → src/components}/VibeSpinner.vue.d.ts +4 -7
  61. package/dist/src/components/VibeStepper.vue.d.ts +133 -0
  62. package/dist/src/components/VibeTab.vue.d.ts +41 -0
  63. package/dist/src/components/VibeTabContent.vue.d.ts +56 -0
  64. package/dist/src/components/VibeTabs.vue.d.ts +78 -0
  65. package/dist/{components → src/components}/VibeToast.vue.d.ts +40 -42
  66. package/dist/src/components/VibeToastHost.vue.d.ts +16 -0
  67. package/dist/src/components/VibeTooltip.vue.d.ts +63 -0
  68. package/dist/src/components/chart/chartColors.d.ts +3 -0
  69. package/dist/src/components/chart/chartResize.d.ts +2 -0
  70. package/dist/src/components/chart/chartTooltip.d.ts +2 -0
  71. package/dist/src/components/chart/chartTypes.d.ts +6 -0
  72. package/dist/src/components/chart/drawBar.d.ts +5 -0
  73. package/dist/src/components/chart/drawLine.d.ts +18 -0
  74. package/dist/src/components/chart/drawPie.d.ts +4 -0
  75. package/dist/src/components/dndStore.d.ts +7 -0
  76. package/dist/{components → src/components}/index.d.ts +18 -1
  77. package/dist/{composables → src/composables}/useBreakpoints.d.ts +1 -0
  78. package/dist/{composables → src/composables}/useColorMode.d.ts +1 -1
  79. package/dist/src/composables/useForm.d.ts +22 -0
  80. package/dist/src/composables/useId.d.ts +2 -0
  81. package/dist/src/composables/usePosition.d.ts +20 -0
  82. package/dist/src/composables/useToast.d.ts +52 -0
  83. package/dist/src/directives/vTooltip.d.ts +24 -0
  84. package/dist/{index.d.ts → src/index.d.ts} +6 -0
  85. package/dist/src/injectionKeys.d.ts +28 -0
  86. package/dist/{types.d.ts → src/types.d.ts} +32 -6
  87. package/dist/src/utils/safeCss.d.ts +16 -0
  88. package/dist/src/utils/safeHref.d.ts +7 -0
  89. package/dist/src/utils/sanitizeHtml.d.ts +13 -0
  90. package/dist/vibeui.css +2 -1
  91. package/dist/vibeui.es.js +8351 -3508
  92. package/dist/vibeui.umd.js +3 -1
  93. package/docs/README.md +85 -13
  94. package/docs/components/advanced/popover.md +27 -52
  95. package/docs/components/advanced/scrollspy.md +57 -15
  96. package/docs/components/advanced/tooltip.md +26 -47
  97. package/docs/components/card/card.md +9 -1
  98. package/docs/components/charts/chart-bar.md +129 -0
  99. package/docs/components/charts/chart-line.md +136 -0
  100. package/docs/components/charts/chart-pie.md +102 -0
  101. package/docs/components/core/alert.md +27 -5
  102. package/docs/components/core/badge.md +0 -6
  103. package/docs/components/core/button-group.md +0 -6
  104. package/docs/components/core/button.md +1 -1
  105. package/docs/components/core/close-button.md +5 -5
  106. package/docs/components/core/link.md +60 -17
  107. package/docs/components/core/placeholder.md +0 -6
  108. package/docs/components/core/skeleton.md +40 -0
  109. package/docs/components/core/spinner.md +0 -6
  110. package/docs/components/data/datatable.md +29 -10
  111. package/docs/components/hero/hero.md +64 -0
  112. package/docs/components/interactive/accordion.md +29 -0
  113. package/docs/components/interactive/carousel.md +19 -4
  114. package/docs/components/interactive/collapse.md +3 -2
  115. package/docs/components/interactive/date-picker.md +78 -0
  116. package/docs/components/interactive/draggable.md +91 -0
  117. package/docs/components/interactive/dropdown.md +54 -18
  118. package/docs/components/interactive/modal.md +15 -20
  119. package/docs/components/interactive/offcanvas.md +4 -1
  120. package/docs/components/interactive/resizable.md +73 -0
  121. package/docs/components/interactive/slider.md +57 -0
  122. package/docs/components/interactive/sortable.md +52 -0
  123. package/docs/components/interactive/stepper.md +83 -0
  124. package/docs/components/interactive/tabs.md +66 -0
  125. package/docs/components/interactive/toast.md +93 -30
  126. package/docs/components/layout/col.md +0 -6
  127. package/docs/components/layout/container.md +0 -6
  128. package/docs/components/layout/row.md +0 -6
  129. package/docs/components/list/list-group.md +4 -0
  130. package/docs/components/navigation/breadcrumb.md +4 -0
  131. package/docs/components/navigation/nav.md +64 -1
  132. package/docs/components/navigation/navbar.md +2 -0
  133. package/docs/components/navigation/pagination.md +16 -0
  134. package/docs/components/progress/progress.md +7 -1
  135. package/docs/composables/color-mode.md +44 -6
  136. package/docs/composables/use-form.md +78 -0
  137. package/docs/composables/use-position.md +68 -0
  138. package/docs/composables/use-toast.md +91 -0
  139. package/docs/directives/v-tooltip.md +58 -0
  140. package/docs/forms/README.md +60 -61
  141. package/docs/forms/autocomplete.md +96 -0
  142. package/docs/forms/file-input.md +97 -0
  143. package/docs/forms/form-checkbox.md +75 -25
  144. package/docs/forms/form-datepicker.md +58 -28
  145. package/docs/forms/form-group.md +79 -44
  146. package/docs/forms/form-input.md +88 -31
  147. package/docs/forms/form-radio.md +60 -36
  148. package/docs/forms/form-select.md +75 -31
  149. package/docs/forms/form-spinbutton.md +72 -33
  150. package/docs/forms/form-switch.md +53 -25
  151. package/docs/forms/form-textarea.md +60 -29
  152. package/docs/forms/form-wysiwyg.md +73 -38
  153. package/docs/forms/input-group.md +53 -34
  154. package/docs/forms/validation.md +135 -521
  155. package/docs/getting-started/starter-template.md +136 -0
  156. package/docs/superpowers/plans/2026-05-12-vibe-charts.md +1999 -0
  157. package/docs/superpowers/plans/2026-05-29-e2e-browser-testing.md +615 -0
  158. package/docs/superpowers/specs/2026-05-12-charting-design.md +185 -0
  159. package/docs/superpowers/specs/2026-05-29-e2e-browser-testing-design.md +168 -0
  160. package/docs/utilities/position.md +82 -0
  161. package/docs/versioning.md +57 -0
  162. package/examples/README.md +201 -0
  163. package/examples/album.html +262 -0
  164. package/examples/blog.html +166 -0
  165. package/examples/carousel.html +114 -0
  166. package/examples/checkout.html +275 -0
  167. package/examples/cover.html +89 -0
  168. package/examples/dashboard.html +156 -0
  169. package/examples/index.html +129 -0
  170. package/examples/jumbotron.html +56 -0
  171. package/examples/mobile-dashboard.html +138 -0
  172. package/examples/pricing.html +172 -0
  173. package/examples/product.html +145 -0
  174. package/examples/sidebars.html +166 -0
  175. package/examples/sign-in.html +119 -0
  176. package/examples/starter.html +314 -0
  177. package/examples/sticky-footer-navbar.html +70 -0
  178. package/examples/sticky-footer.html +60 -0
  179. package/examples/test-simple.html +91 -0
  180. package/llms.txt +407 -47
  181. package/package.json +42 -13
  182. package/dist/App.vue.d.ts +0 -2
  183. package/dist/components/HelloWorld.vue.d.ts +0 -5
  184. package/dist/components/VibeAccordion.vue.d.ts +0 -90
  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/VibeTabContent.vue.d.ts +0 -74
  195. package/dist/components/VibeTooltip.vue.d.ts +0 -86
  196. package/dist/composables/useId.d.ts +0 -5
  197. package/dist/types/index.d.ts +0 -6
  198. /package/dist/{composables → src/composables}/useBackButton.d.ts +0 -0
  199. /package/dist/{composables → src/composables}/useFormValidation.d.ts +0 -0
  200. /package/dist/{main.d.ts → src/main.d.ts} +0 -0
@@ -19,8 +19,22 @@ Powerful data table component with search, sorting, and pagination - similar to
19
19
 
20
20
  | Prop | Type | Default | Description |
21
21
  |------|------|---------|-------------|
22
- | `items` | `Array<any>` | `[]` | Array of data objects to display |
23
- | `columns` | `DataTableColumn[]` | Required | Column definitions |
22
+ | `items` | `T[]` | `[]` | Array of data objects to display |
23
+ | `columns` | `DataTableColumn<T>[]` | Required | Column definitions |
24
+ | `rowKey` | `String` | `'id'` | Property name used as the unique key for each row. **Recommended** — set it to a unique field in your data (e.g. `'id'`, `'uuid'`) so Vue tracks rows correctly across sorting, filtering, and pagination. Falls back to a positional key (with a DEV warning) when missing. |
25
+
26
+ > **Typing tip**: `DataTableColumn` is generic over your row type. For full slot-prop / formatter typing, annotate the column array:
27
+ >
28
+ > ```ts
29
+ > import type { DataTableColumn } from '@velkymx/vibeui'
30
+ > interface User { id: number; name: string; active: boolean }
31
+ > const columns: DataTableColumn<User>[] = [
32
+ > { key: 'name', label: 'Name' }, // key narrowed to keyof User
33
+ > { key: 'active', label: 'Active', formatter: v => v ? 'Yes' : 'No' }
34
+ > ]
35
+ > ```
36
+ >
37
+ > Without the annotation, `T` defaults to `Record<string, unknown>` and slot props arrive as `unknown`.
24
38
 
25
39
  ### Table Styling Props
26
40
 
@@ -89,11 +103,13 @@ interface DataTableColumn {
89
103
  formatter?: (value: any, row: any) => string | number // Custom formatter
90
104
  class?: string // CSS class for td
91
105
  headerClass?: string // CSS class for th
92
- thStyle?: Record<string, string> // Inline styles for th
93
- tdStyle?: Record<string, string> // Inline styles for td
106
+ thStyle?: Record<string, string> // Inline styles for th (sanitized — see note)
107
+ tdStyle?: Record<string, string> // Inline styles for td (sanitized — see note)
94
108
  }
95
109
  ```
96
110
 
111
+ > **`thStyle` / `tdStyle` are sanitized.** Both objects are filtered against a safe CSS-property allowlist before being applied, as a defense against CSS injection (data exfiltration / UI spoofing) when column config comes from an API or untrusted source. Properties outside the allowlist are dropped.
112
+
97
113
  ## Events
98
114
 
99
115
  | Event | Payload | Description |
@@ -102,7 +118,7 @@ interface DataTableColumn {
102
118
  | `update:perPage` | `Number` | Emitted when per-page changes |
103
119
  | `update:sortBy` | `String` | Emitted when sort column changes |
104
120
  | `update:sortDesc` | `Boolean` | Emitted when sort direction changes |
105
- | `row-clicked` | `(item, index)` | Emitted when row is clicked |
121
+ | `row-clicked` | `(item, globalIndex)` | Emitted when a row is clicked. `globalIndex` is the index within the full filtered/sorted dataset, not the current page. Only emitted when a `@row-clicked` listener is attached (rows show a pointer cursor in that case). |
106
122
 
107
123
  ## Slots
108
124
 
@@ -133,10 +149,12 @@ const items = [
133
149
  </script>
134
150
 
135
151
  <template>
136
- <VibeDataTable :columns="columns" :items="items" />
152
+ <VibeDataTable :columns="columns" :items="items" row-key="id" />
137
153
  </template>
138
154
  ```
139
155
 
156
+ > Always pass `row-key` pointing at a unique field (here `id`) so Vue keys rows stably across sorting, filtering, and pagination.
157
+
140
158
  ### With Bootstrap Styling
141
159
 
142
160
  ```vue
@@ -232,8 +250,8 @@ const perPage = ref(25)
232
250
  const sortBy = ref('name')
233
251
  const sortDesc = ref(false)
234
252
 
235
- const handleRowClick = (item, index) => {
236
- console.log('Clicked row:', item, index)
253
+ const handleRowClick = (item, globalIndex) => {
254
+ console.log('Clicked row:', item, globalIndex)
237
255
  }
238
256
  </script>
239
257
 
@@ -241,6 +259,7 @@ const handleRowClick = (item, index) => {
241
259
  <VibeDataTable
242
260
  :columns="columns"
243
261
  :items="items"
262
+ row-key="id"
244
263
  v-model:current-page="currentPage"
245
264
  v-model:per-page="perPage"
246
265
  v-model:sort-by="sortBy"
@@ -410,7 +429,7 @@ const columns = [
410
429
  ## Tips
411
430
 
412
431
  1. **Large Datasets**: For datasets with 1000+ rows, consider server-side pagination
413
- 2. **Performance**: Use `:key` on items for better Vue reactivity
432
+ 2. **Stable keys**: Set the `row-key` prop to a unique field in your data for correct behavior during sorting/filtering and best reactivity
414
433
  3. **Search Debounce**: Adjust `searchDebounce` prop for performance with large datasets
415
- 4. **Custom Styling**: Use column `class`, `headerClass`, `thStyle`, `tdStyle` for styling
434
+ 4. **Custom Styling**: Use column `class`, `headerClass`, `thStyle`, `tdStyle` for styling (style objects are sanitized to a safe property allowlist)
416
435
  5. **Slots**: Use slots for complex cell rendering (badges, buttons, images, etc.)
@@ -0,0 +1,64 @@
1
+ # VibeHero
2
+
3
+ A hero / banner section. Renders a `<section>` with consistent padding, an inner container, and content alignment — covering the Bootstrap 5.3 hero patterns (centered, two-column, dark, bordered) plus full-bleed background image / gradient heroes.
4
+
5
+ ```vue
6
+ <VibeHero variant="dark" text-variant="light">
7
+ <h1 class="display-4 fw-bold">Build faster with VibeUI</h1>
8
+ <p class="lead">A modern Vue 3 component library for Bootstrap 5.3.</p>
9
+ <VibeButton variant="primary" size="lg">Get started</VibeButton>
10
+ </VibeHero>
11
+ ```
12
+
13
+ ## Props
14
+
15
+ | Prop | Type | Default | Description |
16
+ |------|------|---------|-------------|
17
+ | `variant` | `Variant` | `undefined` | Background color → `bg-{variant}` (same vocabulary as `VibeCard`) |
18
+ | `textVariant` | `Variant` | `undefined` | Text color → `text-{variant}` |
19
+ | `border` | `Variant` | `undefined` | Adds `border border-{variant} rounded-3` (the bordered-hero pattern) |
20
+ | `align` | `'start' \| 'center' \| 'end'` | `'center'` | Content alignment → `text-{align}` |
21
+ | `fluid` | `Boolean \| ContainerType` | `false` | Inner container width (same vocabulary as `VibeContainer`) |
22
+ | `minHeight` | `String` | `undefined` | Sanitized via `safeLength`; when set, content is vertically centered |
23
+ | `bgImage` | `String` | `undefined` | Full-bleed background image URL; sanitized via `safeHref` (blocks `javascript:`/`data:`), rendered `cover`/`center` |
24
+ | `overlay` | `Boolean \| String` | `false` | Darkening layer over `bgImage` for legibility — `true` for a default dark scrim, or a CSS color (sanitized via `safeColor`) |
25
+ | `gradient` | `String` | `undefined` | CSS gradient background (e.g. `linear-gradient(135deg, #667eea, #764ba2)`); validated to `*-gradient(...)` only — no `url()`/`expression()` |
26
+ | `tag` | `Tag` | `'section'` | Root element tag |
27
+
28
+ ## Slots
29
+
30
+ | Slot | Description |
31
+ |------|-------------|
32
+ | `default` | Hero content. You supply the inner layout (a centered column, or a `VibeRow`/`VibeCol` split). |
33
+
34
+ ## Examples
35
+
36
+ **Centered (Bootstrap "centered hero")**
37
+
38
+ ```vue
39
+ <VibeHero>
40
+ <VibeCol :lg="7" class="mx-auto">
41
+ <h1 class="display-3 fw-bold">Photo Gallery</h1>
42
+ <p class="lead">Curated photography from around the world.</p>
43
+ </VibeCol>
44
+ </VibeHero>
45
+ ```
46
+
47
+ **Two-column with gradient**
48
+
49
+ ```vue
50
+ <VibeHero gradient="linear-gradient(135deg, #667eea 0%, #764ba2 100%)" align="start" class="text-white">
51
+ <VibeRow class="align-items-center">
52
+ <VibeCol :lg="6"><h1 class="display-3 fw-bold">Headline</h1></VibeCol>
53
+ <VibeCol :lg="6"><img src="/demo.png" class="img-fluid rounded-4 shadow-lg"></VibeCol>
54
+ </VibeRow>
55
+ </VibeHero>
56
+ ```
57
+
58
+ **Full-bleed image with overlay**
59
+
60
+ ```vue
61
+ <VibeHero bg-image="/banner.jpg" overlay min-height="400px" class="text-white">
62
+ <h1 class="display-3 fw-bold">Welcome</h1>
63
+ </VibeHero>
64
+ ```
@@ -31,6 +31,7 @@ interface AccordionItem {
31
31
  | `shown` | `id` | Emitted when an item is fully shown |
32
32
  | `hide` | `id` | Emitted when an item starts hiding |
33
33
  | `hidden` | `id` | Emitted when an item is fully hidden |
34
+ | `component-error` | `ComponentError` | Emitted if Bootstrap JS fails to load |
34
35
 
35
36
  ## Slots
36
37
 
@@ -75,12 +76,40 @@ Items stay open when another item is opened:
75
76
  </template>
76
77
  ```
77
78
 
79
+ ### Custom Title and Content (Slots)
80
+
81
+ Both the `title` and `content` slots receive `{ item, index }` so you can render rich markup while still driving the accordion from the `items` array:
82
+
83
+ ```vue
84
+ <template>
85
+ <VibeAccordion :items="accordionItems">
86
+ <template #title="{ item }">
87
+ <strong>{{ item.title }}</strong>
88
+ </template>
89
+ <template #content="{ item }">
90
+ <div v-html="item.content" />
91
+ </template>
92
+ </VibeAccordion>
93
+ </template>
94
+
95
+ <script setup>
96
+ const accordionItems = [
97
+ { id: 'one', title: 'Item #1', content: '<p>Rich <em>HTML</em> body.</p>', show: true },
98
+ { id: 'two', title: 'Item #2', content: '<p>Second body.</p>' }
99
+ ]
100
+ </script>
101
+ ```
102
+
78
103
  ## Important Notes
79
104
 
80
105
  **Automatic Initialization:** This component automatically initializes Bootstrap's Collapse functionality for each item when it is mounted, provided that Bootstrap's JavaScript is available in your project.
81
106
 
82
107
  **Reactivity:** The `show` property in the `AccordionItem` objects is watched. Changing it in your parent state will trigger the corresponding Bootstrap transition.
83
108
 
109
+ **Item ids:** Each `item.id` (and the accordion `id`) must contain only alphanumeric characters, hyphens, and underscores — Bootstrap's internal `querySelector` breaks on CSS-special characters. In dev mode an unsafe id logs a console warning.
110
+
111
+ **Escape Hatch:** The exposed `_unsafe_bsInstances` template ref is a `Map` of the underlying Bootstrap Collapse instances keyed by item id. It is **not** part of the stable API — calling `dispose()` or other lifecycle methods on them directly **will** break the component. To re-sync after manual DOM changes, call the exposed `refresh()` method instead.
112
+
84
113
  ## Bootstrap CSS Classes
85
114
 
86
115
  - `.accordion`
@@ -38,8 +38,9 @@ interface CarouselItem {
38
38
  | Event | Payload | Description |
39
39
  |-------|---------|-------------|
40
40
  | `update:modelValue` | `Number` | Emitted when the active slide changes |
41
- | `slide` | - | Emitted when slide transition starts |
42
- | `slid` | - | Emitted when slide transition completes |
41
+ | `slide` | `Event` | Emitted when slide transition starts (Bootstrap `slide.bs.carousel`) |
42
+ | `slid` | `Event` | Emitted when slide transition completes (Bootstrap `slid.bs.carousel`) |
43
+ | `component-error` | `ComponentError` | Emitted if Bootstrap JS fails to load |
43
44
 
44
45
  ## Slots
45
46
 
@@ -66,7 +67,10 @@ interface CarouselItem {
66
67
  <script setup>
67
68
  import { ref } from 'vue'
68
69
  const currentSlide = ref(0)
69
- const carouselItems = [...]
70
+ const carouselItems = [
71
+ { src: '/img/slide-1.jpg', caption: 'First slide', captionText: 'Intro copy' },
72
+ { src: '/img/slide-2.jpg', caption: 'Second slide' }
73
+ ]
70
74
  </script>
71
75
  ```
72
76
 
@@ -76,13 +80,24 @@ const carouselItems = [...]
76
80
  <template>
77
81
  <VibeCarousel :items="carouselItems" />
78
82
  </template>
83
+
84
+ <script setup>
85
+ const carouselItems = [
86
+ { src: '/img/slide-1.jpg', alt: 'A scenic view' },
87
+ { src: '/img/slide-2.jpg', alt: 'A city street' }
88
+ ]
89
+ </script>
79
90
  ```
80
91
 
81
92
  ## Important Notes
82
93
 
83
94
  **Automatic Initialization:** This component automatically initializes Bootstrap's Carousel functionality when it is mounted, provided that Bootstrap's JavaScript is available in your project.
84
95
 
85
- **Instance Exposure:** You can access the underlying Bootstrap instance via template ref using the `bsInstance` property.
96
+ **`item.src` is required:** Every slide is an image slide each item must provide a `src`. Captions and alt text are optional (an accessible fallback `alt` is generated from `alt` → `caption` → `captionText` → `"Carousel slide N"`).
97
+
98
+ **Empty items:** An empty `items` array is handled gracefully — the carousel simply renders no slides.
99
+
100
+ **Escape Hatch:** The exposed `_unsafe_bsInstance` template ref gives raw access to the underlying Bootstrap Carousel instance. It is **not** part of the stable API — calling `dispose()` or other lifecycle methods on it directly **will** break the component. To re-initialize after manual changes, call the exposed `refresh()` method instead.
86
101
 
87
102
  ## Bootstrap CSS Classes
88
103
 
@@ -21,6 +21,7 @@ Toggle visibility of content with smooth transitions.
21
21
  | `shown` | - | Emitted when collapse is fully shown |
22
22
  | `hide` | - | Emitted when collapse starts hiding |
23
23
  | `hidden` | - | Emitted when collapse is fully hidden |
24
+ | `component-error` | `ComponentError` | Emitted if Bootstrap JS fails to load |
24
25
 
25
26
  ## Slots
26
27
 
@@ -93,9 +94,9 @@ When used inside a `VibeNavbar`, add `is-nav` to get the `navbar-collapse` class
93
94
 
94
95
  **Automatic Initialization:** This component automatically initializes Bootstrap's Collapse functionality when it is mounted, ensuring that smooth sliding transitions are used.
95
96
 
96
- **State Synchronization:** Refactored to ensure that clicking a toggle (like `VibeNavbarToggle`) updates both Vue's internal state and Bootstrap's JavaScript instance simultaneously.
97
+ **State Synchronization:** Clicking a toggle (like `VibeNavbarToggle`) updates both Vue's `v-model` state and Bootstrap's JavaScript instance simultaneously, so the two never drift out of sync.
97
98
 
98
- **Instance Exposure:** You can access the underlying Bootstrap instance via template ref using the `bsInstance` property.
99
+ **Pre-init fallback:** Before Bootstrap JS finishes loading, an open collapse (`v-model` true) still renders visible via the `.show` class; once Bootstrap is ready it takes ownership of the class.
99
100
 
100
101
  ## Bootstrap CSS Classes
101
102
 
@@ -0,0 +1,78 @@
1
+ # VibeDatePicker
2
+
3
+ Calendar-popover datepicker with single + range mode. Distinct from `VibeFormDatepicker`, which is a thin wrapper around `<input type="date">`.
4
+
5
+ Values are ISO `YYYY-MM-DD` strings.
6
+
7
+ ## Props
8
+
9
+ | Prop | Type | Default | Description |
10
+ |------|------|---------|-------------|
11
+ | `modelValue` | `String \| null \| [String\|null, String\|null]` | `null` | ISO date(s) (v-model) |
12
+ | `range` | `Boolean` | `false` | Range selection mode |
13
+ | `min` | `String` | `undefined` | ISO lower bound |
14
+ | `max` | `String` | `undefined` | ISO upper bound |
15
+ | `disabledDates` | `String[]` | `[]` | Specific ISO dates to disable |
16
+ | `format` | `(iso) => string` | `undefined` | Display formatter for input value |
17
+ | `placeholder` | `String` | `'Select date'` | |
18
+ | `weekStart` | `Number` | `0` | 0 = Sunday, 1 = Monday |
19
+ | `id` | `String` | auto | |
20
+ | `label` | `String` | `undefined` | |
21
+ | `disabled` | `Boolean` | `false` | |
22
+
23
+ ### Events
24
+
25
+ | Event | Payload | Description |
26
+ |-------|---------|-------------|
27
+ | `update:modelValue` | `String \| [String\|null, String\|null]` | Selection committed |
28
+ | `open` | — | Popover opened |
29
+ | `close` | — | Popover closed |
30
+
31
+ ### Exposed methods
32
+
33
+ - `open()` — programmatically open the popover.
34
+ - `close()` — programmatically close.
35
+
36
+ ## Single mode
37
+
38
+ ```vue
39
+ <script setup>
40
+ import { ref } from 'vue'
41
+ const date = ref(null)
42
+ </script>
43
+
44
+ <template>
45
+ <VibeDatePicker v-model="date" label="Due date" />
46
+ </template>
47
+ ```
48
+
49
+ ## Range mode
50
+
51
+ ```vue
52
+ <VibeDatePicker
53
+ v-model="range"
54
+ range
55
+ :min="'2026-01-01'"
56
+ :max="'2026-12-31'"
57
+ :format="iso => iso.split('-').reverse().join('/')"
58
+ />
59
+ ```
60
+
61
+ The first click sets `[start, null]`. The second click adds the end date and closes the popover; if the second pick is earlier than the first, the range is auto-swapped to stay ordered.
62
+
63
+ ## Disabling dates
64
+
65
+ ```vue
66
+ <VibeDatePicker
67
+ v-model="date"
68
+ :disabled-dates="['2026-04-29', '2026-04-30']"
69
+ />
70
+ ```
71
+
72
+ ## Important Notes
73
+
74
+ **Zero-padded ISO bounds:** `min`, `max`, and every entry in `disabledDates` must be zero-padded `YYYY-MM-DD` (e.g. `2026-04-05`, not `2026-4-5`). Comparisons are lexical string comparisons, so an unpadded value compares incorrectly. In dev mode, a non-conforming `min`/`max` logs a console warning.
75
+
76
+ **Read-only input:** The text input is read-only; dates are chosen through the calendar popover, which closes on outside click or `Escape`.
77
+
78
+ **Keyboard navigation:** The grid supports arrow keys (day), `PageUp`/`PageDown` (month), `Home`/`End` (week), and `Enter`/`Space` to select.
@@ -0,0 +1,91 @@
1
+ # VibeDraggable / VibeDroppable
2
+
3
+ Generic drag-and-drop primitives for kanban boards, builders, palette UIs. Communicates payloads through a shared module-level store, so payloads can be typed objects or non-serializable values (refs, functions) — no DataTransfer string round-trip.
4
+
5
+ ## VibeDraggable Props
6
+
7
+ | Prop | Type | Default | Description |
8
+ |------|------|---------|-------------|
9
+ | `payload` | `unknown` | `undefined` | Data passed to the matching droppable on drop |
10
+ | `group` | `String` | `'default'` | Tag used to match against droppable groups |
11
+ | `disabled` | `Boolean` | `false` | Disable dragging |
12
+ | `tag` | `String` | `'div'` | Element to render |
13
+
14
+ ### Events
15
+
16
+ | Event | Payload | Description |
17
+ |-------|---------|-------------|
18
+ | `dragstart` | `{ payload, group, event }` | Emitted on dragstart |
19
+ | `dragend` | `{ payload, group, event }` | Emitted on dragend, regardless of drop success |
20
+
21
+ ### Slots
22
+
23
+ Default slot scope: `{ isDragging }`.
24
+
25
+ ## VibeDroppable Props
26
+
27
+ | Prop | Type | Default | Description |
28
+ |------|------|---------|-------------|
29
+ | `group` | `String` | `'default'` | Group accepted unless `acceptGroups` overrides |
30
+ | `acceptGroups` | `String[]` | `undefined` | Whitelist of groups that may drop here |
31
+ | `disabled` | `Boolean` | `false` | Reject all drops |
32
+ | `tag` | `String` | `'div'` | Element to render |
33
+
34
+ ### Events
35
+
36
+ | Event | Payload | Description |
37
+ |-------|---------|-------------|
38
+ | `drop` | `{ payload, group, event }` | Successful drop, only emitted when group matches |
39
+ | `dragenter` | `DragEvent` | |
40
+ | `dragleave` | `DragEvent` | |
41
+
42
+ ### Slots
43
+
44
+ Default slot scope: `{ isOver }`.
45
+
46
+ ## Examples
47
+
48
+ ### Kanban column move
49
+
50
+ ```vue
51
+ <script setup>
52
+ import { ref } from 'vue'
53
+ const todo = ref([{ id: 1, title: 'Wireframes' }])
54
+ const done = ref([])
55
+
56
+ function moveCard(card, from, to) {
57
+ from.value = from.value.filter(c => c.id !== card.id)
58
+ to.value.push(card)
59
+ }
60
+ </script>
61
+
62
+ <template>
63
+ <div class="d-flex gap-3">
64
+ <VibeDroppable group="cards" @drop="({ payload }) => moveCard(payload, todo, done)">
65
+ <template #default="{ isOver }">
66
+ <div :class="['col bg-light p-3', isOver ? 'border-primary' : '']">
67
+ <h5>To do</h5>
68
+ <VibeDraggable v-for="card in todo" :key="card.id" :payload="card" group="cards">
69
+ <div class="card mb-2 p-2">{{ card.title }}</div>
70
+ </VibeDraggable>
71
+ </div>
72
+ </template>
73
+ </VibeDroppable>
74
+
75
+ <VibeDroppable group="cards" @drop="({ payload }) => moveCard(payload, done, todo)">
76
+ <h5>Done</h5>
77
+ <VibeDraggable v-for="card in done" :key="card.id" :payload="card" group="cards">
78
+ <div class="card mb-2 p-2">{{ card.title }}</div>
79
+ </VibeDraggable>
80
+ </VibeDroppable>
81
+ </div>
82
+ </template>
83
+ ```
84
+
85
+ ### Multi-source whitelist
86
+
87
+ ```vue
88
+ <VibeDroppable :accept-groups="['todo', 'done']" group="trash">
89
+ <div class="trash-zone">Drop to delete</div>
90
+ </VibeDroppable>
91
+ ```
@@ -39,6 +39,7 @@ interface DropdownItem {
39
39
  | `shown` | - | Emitted when dropdown is fully shown |
40
40
  | `hide` | - | Emitted when dropdown starts hiding |
41
41
  | `hidden` | - | Emitted when dropdown is fully hidden |
42
+ | `component-error` | `ComponentError` | Emitted if Bootstrap JS fails to load |
42
43
 
43
44
  ## Slots
44
45
 
@@ -50,40 +51,75 @@ interface DropdownItem {
50
51
 
51
52
  ## Usage
52
53
 
53
- ### Programmatic Control
54
+ ### Basic Dropdown
54
55
 
55
56
  ```vue
56
57
  <template>
57
- <div>
58
- <VibeButton @click="$refs.myDropdown.toggle()">External Toggle</VibeButton>
59
-
60
- <VibeDropdown
61
- ref="myDropdown"
62
- text="Dropdown Menu"
63
- :items="dropdownItems"
64
- />
65
- </div>
58
+ <VibeDropdown text="Dropdown Menu" :items="dropdownItems" @item-click="onItemClick" />
66
59
  </template>
60
+
61
+ <script setup>
62
+ const dropdownItems = [
63
+ { header: true, text: 'Actions' },
64
+ { text: 'Edit', href: '#edit' },
65
+ { text: 'Duplicate', href: '#duplicate' },
66
+ { divider: true },
67
+ { text: 'Delete', href: '#delete', active: false }
68
+ ]
69
+
70
+ function onItemClick({ item, index }) {
71
+ console.log('clicked', item.text, 'at', index)
72
+ }
73
+ </script>
67
74
  ```
68
75
 
69
- ### Basic Dropdown
76
+ ### Custom Item Rendering (Slots)
77
+
78
+ The `item` and `header` slots receive `{ item, index }` so you can render rich markup while still driving the menu from the `items` array:
70
79
 
71
80
  ```vue
72
81
  <template>
73
- <VibeDropdown
74
- text="Dropdown Menu"
75
- :items="dropdownItems"
76
- />
82
+ <VibeDropdown text="Menu" :items="dropdownItems">
83
+ <template #item="{ item }">
84
+ <i :class="item.icon" class="me-2" />{{ item.text }}
85
+ </template>
86
+ </VibeDropdown>
77
87
  </template>
88
+
89
+ <script setup>
90
+ const dropdownItems = [
91
+ { text: 'Profile', href: '#', icon: 'bi bi-person' },
92
+ { text: 'Settings', href: '#', icon: 'bi bi-gear' }
93
+ ]
94
+ </script>
95
+ ```
96
+
97
+ ### Programmatic Control
98
+
99
+ ```vue
100
+ <template>
101
+ <div>
102
+ <VibeButton @click="myDropdown.toggle()">External Toggle</VibeButton>
103
+
104
+ <VibeDropdown ref="myDropdown" text="Dropdown Menu" :items="dropdownItems" />
105
+ </div>
106
+ </template>
107
+
108
+ <script setup>
109
+ import { ref } from 'vue'
110
+ const myDropdown = ref(null)
111
+ const dropdownItems = [
112
+ { text: 'Edit', href: '#edit' },
113
+ { text: 'Delete', href: '#delete' }
114
+ ]
115
+ </script>
78
116
  ```
79
117
 
80
118
  ## Important Notes
81
119
 
82
120
  **Automatic Initialization:** This component automatically initializes Bootstrap's Dropdown functionality when it is mounted, provided that Bootstrap's JavaScript is available in your project.
83
121
 
84
- **Programmatic Methods:** You can call `show()`, `hide()`, and `toggle()` on the component instance via template refs.
85
-
86
- **Instance Exposure:** You can access the underlying Bootstrap instance via template ref using the `bsInstance` property.
122
+ **Programmatic Methods:** The component exposes `show()`, `hide()`, and `toggle()` via template refs for programmatic control.
87
123
 
88
124
  ## Bootstrap CSS Classes
89
125
 
@@ -27,6 +27,7 @@ Modal dialogs for lightboxes, user notifications, or custom content.
27
27
  | `shown` | - | Emitted when modal is fully shown |
28
28
  | `hide` | - | Emitted when modal starts hiding |
29
29
  | `hidden` | - | Emitted when modal is fully hidden |
30
+ | `component-error` | `ComponentError` | Emitted if Bootstrap JS fails to load |
30
31
 
31
32
  ## Slots
32
33
 
@@ -59,23 +60,6 @@ const showModal = ref(false)
59
60
  </script>
60
61
  ```
61
62
 
62
- ### Basic Modal (Data Attributes)
63
-
64
- ```vue
65
- <template>
66
- <div>
67
- <!-- You can still use standard Bootstrap data attributes -->
68
- <VibeButton variant="primary" data-bs-toggle="modal" data-bs-target="#exampleModal">
69
- Launch Modal
70
- </VibeButton>
71
-
72
- <VibeModal id="exampleModal" title="Modal Title">
73
- <p>Modal body text goes here.</p>
74
- </VibeModal>
75
- </div>
76
- </template>
77
- ```
78
-
79
63
  ### Sized Modals
80
64
 
81
65
  ```vue
@@ -106,15 +90,24 @@ const showModal = ref(false)
106
90
 
107
91
  ```vue
108
92
  <template>
109
- <VibeModal title="Custom Footer">
93
+ <VibeModal v-model="showModal" title="Custom Footer">
110
94
  <p>Modal with custom footer buttons.</p>
111
95
 
112
96
  <template #footer>
113
97
  <VibeButton variant="secondary" @click="showModal = false">Close</VibeButton>
114
- <VibeButton variant="primary">Save changes</VibeButton>
98
+ <VibeButton variant="primary" @click="save">Save changes</VibeButton>
115
99
  </template>
116
100
  </VibeModal>
117
101
  </template>
102
+
103
+ <script setup>
104
+ import { ref } from 'vue'
105
+ const showModal = ref(true)
106
+ function save() {
107
+ // persist changes...
108
+ showModal.value = false
109
+ }
110
+ </script>
118
111
  ```
119
112
 
120
113
  ## Important Notes
@@ -123,7 +116,9 @@ const showModal = ref(false)
123
116
 
124
117
  **Teleportation:** By default, this component teleports its DOM elements to the `<body>` to avoid stacking context issues. You can customize this with the `teleport` prop.
125
118
 
126
- **Instance Exposure:** You can access the underlying Bootstrap instance via template ref using the `bsInstance` property.
119
+ **Focus Return (WCAG 2.4.3):** When the modal closes, focus is automatically returned to the element that opened it, including for programmatically opened modals.
120
+
121
+ **Escape Hatch:** The exposed `_unsafe_bsInstance` template ref gives raw access to the underlying Bootstrap Modal instance. It is **not** part of the stable API — calling `dispose()` or other lifecycle methods on it directly **will** break the component. Prefer `v-model` and the exposed `show()` / `hide()` methods.
127
122
 
128
123
  ## Mobile Optimization
129
124
 
@@ -23,6 +23,7 @@ Hidden sidebar for navigation or additional content.
23
23
  | `shown` | - | Emitted when offcanvas is fully shown |
24
24
  | `hide` | - | Emitted when offcanvas starts hiding |
25
25
  | `hidden` | - | Emitted when offcanvas is fully hidden |
26
+ | `component-error` | `ComponentError` | Emitted if Bootstrap JS fails to load |
26
27
 
27
28
  ## Slots
28
29
 
@@ -70,7 +71,9 @@ const showOffcanvas = ref(false)
70
71
 
71
72
  **Teleportation:** By default, this component teleports its DOM elements to the `<body>` to avoid stacking context issues. You can customize this with the `teleport` prop.
72
73
 
73
- **Instance Exposure:** You can access the underlying Bootstrap instance via template ref using the `bsInstance` property.
74
+ **Focus Return (WCAG 2.4.3):** When the offcanvas closes, focus is automatically returned to the element that opened it, including for programmatically opened panels.
75
+
76
+ **Escape Hatch:** The exposed `_unsafe_bsInstance` template ref gives raw access to the underlying Bootstrap Offcanvas instance. It is **not** part of the stable API — calling `dispose()` or other lifecycle methods on it directly **will** break the component. Prefer `v-model` and the exposed `show()` / `hide()` methods.
74
77
 
75
78
  ## Mobile Optimization
76
79