@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
package/llms.txt ADDED
@@ -0,0 +1,782 @@
1
+ # VibeUI
2
+
3
+ A Vue 3 component library that wraps Bootstrap 5.3. Every Bootstrap component is exposed as a typed Vue component with reactive props, v-model support, automatic Bootstrap JS lifecycle management, and zero manual DOM work required.
4
+
5
+ **Package:** `@velkymx/vibeui`
6
+ **Stack:** Vue 3 (Composition API) + Bootstrap 5.3 + TypeScript + Vite
7
+ **Current version:** 1.0.0
8
+
9
+ ---
10
+
11
+ ## Key Mental Model
12
+
13
+ VibeUI is a thin but complete abstraction layer over Bootstrap. It does three things:
14
+
15
+ 1. **Wraps Bootstrap CSS** — components apply the correct Bootstrap classes based on Vue props.
16
+ 2. **Abstracts Bootstrap JS** — interactive components (`Modal`, `Offcanvas`, `Toast`, `Tooltip`, `Popover`, `Accordion`, `Carousel`, `Collapse`, `Dropdown`) initialize, configure, and destroy their own Bootstrap JS instances via `onMounted`/`onBeforeUnmount`. You never touch `new bootstrap.Modal(el)`.
17
+ 3. **Adds Vue reactivity** — props drive Bootstrap re-initialization, `v-model` syncs visibility state, and Bootstrap's native events are re-emitted as Vue events.
18
+
19
+ ---
20
+
21
+ ## Building With VibeUI — start here
22
+
23
+ This is the whole workflow: **load → use → leverage**. Follow it and you won't fight the library. (Detailed reference sections follow; the per-component docs are linked in the Documentation Index at the end — read a component's doc before using it.)
24
+
25
+ ### 1. Load
26
+
27
+ ```bash
28
+ npm install @velkymx/vibeui bootstrap
29
+ ```
30
+ ```ts
31
+ import { createApp } from 'vue'
32
+ import VibeUI from '@velkymx/vibeui'
33
+ import 'bootstrap/dist/css/bootstrap.min.css' // YOU import the CSS
34
+ createApp(App).use(VibeUI).mount('#app') // registers every component + the v-vibe-tooltip directive
35
+ ```
36
+
37
+ - **Import Bootstrap CSS yourself. Never import Bootstrap JS** — VibeUI loads it internally, on demand, per component.
38
+ - `app.use(VibeUI)` registers everything globally. Or import by name for tree-shaking **and** full template type-checking: `import { VibeButton, VibeModal } from '@velkymx/vibeui'`.
39
+ - **Non-bundler / UMD / CDN pages:** the internal `import('bootstrap')` is a bare specifier, so add an import map or interactive components silently fail:
40
+ `<script type="importmap">{ "imports": { "bootstrap": "https://esm.sh/bootstrap@5.3" } }</script>`
41
+
42
+ ### 2. Use — props, `v-model`, `:items`, slots
43
+
44
+ - **Style with props:** `variant`, `size`, `outline` (boolean). For one-offs pass `class`/`style` — they merge onto the component root.
45
+ - **Visibility is `v-model`:** `<VibeModal v-model="open" />` (also Offcanvas / Toast / Collapse / Alert). Do not call Bootstrap `show`/`hide`.
46
+ - **Data-driven components take arrays — never hand-write the Bootstrap markup:**
47
+ - `:items` → `VibeNav`, `VibeDropdown`, `VibeListGroup`, `VibeBreadcrumb`, `VibeNavbarNav`
48
+ - `:items` (`CarouselItem[]`, each needs `src`) → `VibeCarousel`
49
+ - `:options` → `VibeFormSelect`
50
+ - `:bars` → `VibeProgress`
51
+ - `:columns` + `:items` → `VibeDataTable`
52
+ - **Custom content uses slots:** `#item` (Nav/Dropdown/ListGroup), `#header`/`#footer`/`#image` (Card), `cell(<key>)` (DataTable), default slot for body.
53
+ - **Imperative actions use the exposed methods via a template ref:** `modal.value?.show()` / `.hide()` / `.toggle()` / `.refresh()`. Drive the component — never reach around it to Bootstrap.
54
+
55
+ ### 3. Leverage
56
+
57
+ - **Forms:** wrap controls in `<VibeFormGroup label="…">` (auto-links label + id + a11y). Validate with `useForm` + `validators` (`required`/`email`/`minLength`/…) and bind `:validation-state` / `:validation-message` per field.
58
+ - **Composables:** `useToast` (global toasts; mount `<VibeToastHost/>` once), `useColorMode` (light/dark/auto), `useBreakpoints` (`isMobile`/`isTablet`/…), `useForm`, `usePosition`.
59
+ - **Layout & sections:** `VibeContainer` / `VibeRow` (`:gutters`) / `VibeCol` (`:cols`/`:md`/…/`:order`); `VibeHero` for banner/hero sections; `VibeCard` (with `headerClass`/`bodyClass`/`#image`).
60
+ - **Copy whole patterns** from the full-page templates in `examples/` (Album, Pricing, Checkout, Sign-in, Dashboard, Sidebars, Cover, Product, Carousel, Blog).
61
+
62
+ ### Read the doc first
63
+
64
+ A component's exact props/events/slots live in its doc page (Documentation Index, bottom). **Read it before building — do not guess props or reverse-engineer from `dist/`.** The documented API is the source of truth.
65
+
66
+ ### Common mistakes — do NOT do these
67
+
68
+ - ❌ `variant="outline-secondary"` → ✅ `variant="secondary" outline` (outline is a boolean prop)
69
+ - ❌ `<VibeIcon name="house" />` → ✅ `<VibeIcon icon="house" />`
70
+ - ❌ `<VibeCarousel :slides="…" />` → ✅ `<VibeCarousel :items="…" />` (each `CarouselItem` needs `src`)
71
+ - ❌ `<vibe-card-body>`, `<vibe-list-group-item>`, `<vibe-nav-item>`, `<vibe-dropdown-item>`, `<vibe-progress-bar>` — **none of these exist.** Use the default / `#item` slot and `:items` / `:bars`.
72
+ - ❌ hand-written `<ul class="nav">` / `<div class="card">` → ✅ `<VibeNav :items>` / `<VibeCard>`
73
+ - ❌ `import 'bootstrap/dist/js/…'` or `new bootstrap.Modal()` → ✅ never; use props, `v-model`, and exposed methods
74
+ - ❌ UMD/CDN page with no `bootstrap` import map → interactive components silently fall back / fail
75
+ - ❌ guessing a prop name → ✅ open the component's doc page first
76
+
77
+ ---
78
+
79
+ ## Project Structure
80
+
81
+ ```
82
+ vibeui/
83
+ ├── src/
84
+ │ ├── index.ts # Package entry point — exports everything
85
+ │ ├── types.ts # All shared TypeScript types
86
+ │ ├── components/
87
+ │ │ ├── index.ts # Component registration + Vue plugin
88
+ │ │ ├── VibeAlert.vue
89
+ │ │ ├── VibeButton.vue # Renders as <button>, <a>, or <router-link>; variant="link"
90
+ │ │ ├── VibeBadge.vue
91
+ │ │ ├── VibeButtonGroup.vue
92
+ │ │ ├── VibeCard.vue # headerClass/bodyClass/footerClass + #image slot
93
+ │ │ ├── VibeHero.vue # hero/banner section (variant/bordered/bgImage/gradient)
94
+ │ │ ├── VibeCloseButton.vue
95
+ │ │ ├── VibeSpinner.vue
96
+ │ │ ├── VibePlaceholder.vue
97
+ │ │ ├── VibeSkeleton.vue # text/rect/circle/card loading placeholder
98
+ │ │ ├── VibeContainer.vue # Layout
99
+ │ │ ├── VibeRow.vue
100
+ │ │ ├── VibeCol.vue
101
+ │ │ ├── VibeBreadcrumb.vue # Data-driven: accepts items[]
102
+ │ │ ├── VibeNav.vue
103
+ │ │ ├── VibeNavbar.vue
104
+ │ │ ├── VibeNavbarBrand.vue
105
+ │ │ ├── VibeNavbarToggle.vue
106
+ │ │ ├── VibeNavbarNav.vue
107
+ │ │ ├── VibePagination.vue # v-model:currentPage
108
+ │ │ ├── VibeTabContent.vue
109
+ │ │ ├── VibeTabs.vue # tab container w/ content switching
110
+ │ │ ├── VibeTab.vue # child of VibeTabs
111
+ │ │ ├── VibeListGroup.vue # Data-driven: accepts items[]
112
+ │ │ ├── VibeProgress.vue # Data-driven: accepts bars[]
113
+ │ │ ├── VibeStepper.vue # multi-step wizard w/ guards
114
+ │ │ ├── VibeAccordion.vue # Bootstrap JS — auto init/destroy
115
+ │ │ ├── VibeCollapse.vue # Bootstrap JS — v-model
116
+ │ │ ├── VibeDropdown.vue # Bootstrap JS — data-driven items[]
117
+ │ │ ├── VibeModal.vue # Bootstrap JS — v-model, Teleport, size xl/fullscreen
118
+ │ │ ├── VibeOffcanvas.vue # Bootstrap JS — v-model, Teleport to body
119
+ │ │ ├── VibeToast.vue # Bootstrap JS — v-model OR no-container mode for host
120
+ │ │ ├── VibeToastHost.vue # hosts queued toasts from useToast()
121
+ │ │ ├── VibeCarousel.vue # Bootstrap JS — v-model (active slide index)
122
+ │ │ ├── VibeTooltip.vue # Bootstrap JS — reactive re-init on prop change
123
+ │ │ ├── VibePopover.vue # Bootstrap JS — reactive re-init on prop change
124
+ │ │ ├── VibeScrollspy.vue
125
+ │ │ ├── VibeIcon.vue # Renders Bootstrap Icons (bi-*)
126
+ │ │ ├── VibeDataTable.vue # Generic <T>: sortable, searchable, paginated
127
+ │ │ ├── VibeDatePicker.vue # calendar popover, single + range, keyboard nav
128
+ │ │ ├── VibeAutocomplete.vue # search-as-you-type, sync or async source
129
+ │ │ ├── VibeFileInput.vue # file picker w/ drag-drop, accept enforcement
130
+ │ │ ├── VibeSlider.vue # single + range slider, keyboard, track click
131
+ │ │ ├── VibeResizable.vue # corner / edge handle resizing
132
+ │ │ ├── VibeSortable.vue # drag-to-reorder list (single-list)
133
+ │ │ ├── VibeDraggable.vue # free-drag source, pairs with VibeDroppable
134
+ │ │ ├── VibeDroppable.vue # drop zone, group / acceptGroups matching
135
+ │ │ ├── dndStore.ts # shared payload store between drag/drop
136
+ │ │ ├── VibeFormInput.vue
137
+ │ │ ├── VibeFormSelect.vue # typed primitive option values
138
+ │ │ ├── VibeFormTextarea.vue
139
+ │ │ ├── VibeFormCheckbox.vue
140
+ │ │ ├── VibeFormRadio.vue
141
+ │ │ ├── VibeFormSwitch.vue
142
+ │ │ ├── VibeFormSpinbutton.vue
143
+ │ │ ├── VibeFormDatepicker.vue # Native <input type=date> wrapper
144
+ │ │ ├── VibeFormGroup.vue # Context-aware — auto-links label to child input
145
+ │ │ ├── VibeInputGroup.vue # prepend/append slots; pairs with noWrapper inputs
146
+ │ │ ├── VibeLink.vue
147
+ │ │ ├── VibeFormWysiwyg.vue # Quill 2.x — toolbar string presets
148
+ │ │ ├── VibeChartLine.vue # canvas line chart, smooth/fill, Bootstrap color cycling
149
+ │ │ ├── VibeChartBar.vue # canvas bar chart, grouped + stacked
150
+ │ │ └── VibeChartPie.vue # canvas pie chart, Bootstrap color cycling
151
+ │ ├── composables/
152
+ │ │ ├── useId.ts # Generates unique IDs (SSR-safe)
153
+ │ │ ├── useColorMode.ts # Bootstrap color mode management (singleton)
154
+ │ │ ├── useFormValidation.ts # Field-level validation + built-in validators
155
+ │ │ ├── useForm.ts # multi-field form state w/ deep-clone reset
156
+ │ │ ├── useToast.ts # global toast service (use w/ <VibeToastHost />)
157
+ │ │ ├── usePosition.ts # floating-ui anchor positioning
158
+ │ │ ├── useBackButton.ts # Android hardware back button
159
+ │ │ └── useBreakpoints.ts # programmatic breakpoint detection
160
+ │ └── directives/
161
+ │ └── vTooltip.ts # v-vibe-tooltip directive
162
+ ├── tests/
163
+ │ ├── components/ # Component tests (Vitest + @vue/test-utils)
164
+ │ ├── composables/ # Composable tests
165
+ │ ├── directives/ # Directive tests
166
+ │ └── mocks/bootstrap.ts # Aliased in vite.config.ts test block
167
+ ├── docs/
168
+ │ ├── README.md # Docs index — start here
169
+ │ ├── components/ # Per-component reference docs
170
+ │ ├── forms/ # Form + validation docs
171
+ │ ├── directives/ # v-vibe-tooltip
172
+ │ ├── utilities/position.md # Bootstrap position-* class reference
173
+ │ └── composables/
174
+ │ ├── color-mode.md
175
+ │ ├── breakpoints.md
176
+ │ ├── back-button.md
177
+ │ ├── use-form.md
178
+ │ ├── use-position.md
179
+ │ └── use-toast.md
180
+ ├── examples/ # Standalone HTML demo pages (load ../dist + CDNs; run `npm run build` first, or `npm run examples`)
181
+ ├── smoke/ # manual smoke test page
182
+ │ ├── index.html # `npm run smoke` builds + serves on :8001
183
+ │ └── dist/ # gitignored; populated by `npm run smoke`
184
+ ├── vite.config.ts # Also contains Vitest config
185
+ └── llms.txt # This file
186
+ ```
187
+
188
+ ---
189
+
190
+ ## Installation & Setup
191
+
192
+ ```bash
193
+ npm install @velkymx/vibeui bootstrap
194
+ ```
195
+
196
+ ```ts
197
+ // main.ts
198
+ import { createApp } from 'vue'
199
+ import VibeUI, { useColorMode } from '@velkymx/vibeui'
200
+ import 'bootstrap/dist/css/bootstrap.min.css'
201
+ import App from './App.vue'
202
+
203
+ // Restore saved light/dark/auto preference before first render
204
+ const { initColorMode } = useColorMode()
205
+ initColorMode()
206
+
207
+ createApp(App).use(VibeUI).mount('#app')
208
+ ```
209
+
210
+ Bootstrap CSS must be imported by the consumer. VibeUI does **not** bundle it.
211
+ Bootstrap JS is handled internally via dynamic import — do **not** import `bootstrap/dist/js/bootstrap.bundle.min.js` yourself.
212
+
213
+ ---
214
+
215
+ ## Exports
216
+
217
+ Everything is exported from the package root:
218
+
219
+ ```ts
220
+ // Plugin (registers all components + the v-vibe-tooltip directive)
221
+ import VibeUI from '@velkymx/vibeui'
222
+ app.use(VibeUI)
223
+
224
+ // Individual components
225
+ import {
226
+ VibeButton, VibeAlert, VibeModal, VibeOffcanvas,
227
+ VibeCard, VibeHero,
228
+ VibeFileInput, VibeSkeleton, VibeDatePicker, VibeAutocomplete,
229
+ VibeTabs, VibeTab, VibeStepper,
230
+ VibeSortable, VibeDraggable, VibeDroppable,
231
+ VibeResizable, VibeSlider,
232
+ VibeToast, VibeToastHost,
233
+ VibeChartLine, VibeChartBar, VibeChartPie
234
+ /* ... */
235
+ } from '@velkymx/vibeui'
236
+
237
+ // Composables
238
+ import {
239
+ useColorMode, useFormValidation, validators,
240
+ useId, useBreakpoints, useBackButton,
241
+ useForm, useToast, usePosition, resetToastStoreForSSR
242
+ } from '@velkymx/vibeui'
243
+
244
+ // Directives (also registered globally by app.use(VibeUI))
245
+ import { vTooltip } from '@velkymx/vibeui'
246
+
247
+ // Types
248
+ import type {
249
+ Variant, ButtonVariant, ColorMode, Size,
250
+ DataTableColumn, DataTableCellSlotProps,
251
+ FormSelectOption, FormSelectOptionValue,
252
+ ToastSpec, ToastShowOptions, UseToastReturn,
253
+ UsePositionOptions, UsePositionReturn,
254
+ ChartData, ChartDataset, ChartLegendPosition
255
+ } from '@velkymx/vibeui'
256
+ ```
257
+
258
+ ---
259
+
260
+ ## Shared Types (`src/types.ts`)
261
+
262
+ These types are used across all components. Always import from `@velkymx/vibeui`.
263
+
264
+ ```ts
265
+ type Variant = 'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'info' | 'light' | 'dark'
266
+ type ButtonVariant = Variant | 'link'
267
+ type Size = 'sm' | 'lg'
268
+ type ColorMode = 'light' | 'dark' | 'auto'
269
+ type ButtonType = 'button' | 'submit' | 'reset'
270
+ type Tag = 'div' | 'span' | 'section' | 'article' | 'nav' | 'aside' | 'header' | 'footer' | 'main'
271
+ type TooltipPlacement = 'top' | 'bottom' | 'start' | 'end' // also OffcanvasPlacement, NavbarPosition, ToastPlacement exist
272
+ type ToastPlacement = 'top-start' | 'top-center' | 'top-end' | 'middle-...' | 'bottom-...'
273
+ type Direction = 'up' | 'down' | 'start' | 'end'
274
+ type SpinnerType = 'border' | 'grow'
275
+ type InputType = 'text' | 'email' | 'password' | 'number' | 'tel' | 'url' | 'search' | 'date' | 'time' | ...
276
+ type ValidationState = 'valid' | 'invalid' | null
277
+
278
+ // Data-driven component item shapes
279
+ interface BreadcrumbItem { text, href?, to?, active? }
280
+ interface NavItem { text, href?, to?, target?, active?, disabled?, children? }
281
+ // target?: string — tab panel selector (e.g. '#panel-id') for tabs/pills mode with router-link items
282
+ interface DropdownItem { text?, href?, to?, active?, disabled?, divider?, header? }
283
+ interface ListGroupItem { text, href?, to?, active?, disabled?, variant? }
284
+ interface AccordionItem { id, title, content, show? }
285
+ interface CarouselItem { src, alt?, caption?, captionText?, active?, interval? }
286
+ interface ProgressBar { value, max?, variant?, striped?, animated?, label?, showValue? }
287
+
288
+ // Generic over the row type T (default Record<string, unknown>)
289
+ interface DataTableColumn<T> { key: keyof T & string, label, sortable?, searchable?,
290
+ formatter?: (value: T[keyof T], row: T) => string | number, ... }
291
+ interface DataTableCellSlotProps<T> { item: T, value: T[keyof T], index: number }
292
+
293
+ // Form select primitive — accepts null / undefined / boolean
294
+ type FormSelectOptionValue = string | number | boolean | null | undefined
295
+ interface FormSelectOption { value: FormSelectOptionValue, text, disabled? }
296
+
297
+ // charts
298
+ interface ChartDataset { label: string, data: number[], color?: string }
299
+ interface ChartData { labels: string[], datasets: ChartDataset[] }
300
+ type ChartLegendPosition = 'top' | 'bottom' | 'none'
301
+
302
+ // stepper / toast / position
303
+ interface StepperStep { label, description?, disabled?, icon? }
304
+ interface ToastSpec { id, title?, body, variant?, placement?, autohide?, delay? }
305
+ interface ToastShowOptions { id?, title?, variant?, placement?, autohide?, delay? }
306
+ interface UsePositionOptions { my?, at?, offset?, collision?, autoUpdate?, strategy? }
307
+ ```
308
+
309
+ ---
310
+
311
+ ## Layout Components
312
+
313
+ ### VibeContainer
314
+
315
+ Wraps Bootstrap's `.container`, `.container-fluid`, and `.container-{breakpoint}`.
316
+
317
+ | Prop | Type | Default | Description |
318
+ |------|------|---------|-------------|
319
+ | `fluid` | `boolean \| ContainerType` | `false` | `false` = fixed, `true` = fluid, or breakpoint string |
320
+ | `tag` | `Tag` | `'div'` | HTML element |
321
+
322
+ ```vue
323
+ <VibeContainer>Fixed width</VibeContainer>
324
+ <VibeContainer fluid>Full width</VibeContainer>
325
+ <VibeContainer fluid="md">Fluid until md breakpoint</VibeContainer>
326
+ ```
327
+
328
+ ### VibeRow
329
+
330
+ Bootstrap grid row with gutters, row-columns, and alignment.
331
+
332
+ | Prop | Type | Default | Description |
333
+ |------|------|---------|-------------|
334
+ | `tag` | `Tag` | `'div'` | HTML element |
335
+ | `gutters` | `GutterSize` | — | Both-axis gutter (`0`-`5`) |
336
+ | `guttersX` / `guttersY` | `GutterSize` | — | Per-axis gutter |
337
+ | `gutters{X\|Y}{Sm\|Md\|Lg\|Xl\|Xxl}` | `GutterSize` | — | Responsive gutters |
338
+ | `rowCols` | `RowColsSize` | — | Columns per row (`1`-`6`) |
339
+ | `rowCols{Sm\|Md\|Lg\|Xl\|Xxl}` | `RowColsSize` | — | Responsive row-cols |
340
+ | `alignItems` | `AlignItems` | — | Vertical alignment |
341
+ | `justifyContent` | `JustifyContent` | — | Horizontal distribution |
342
+
343
+ Types: `GutterSize = 0-5`, `RowColsSize = 1-6`, `AlignItems = 'start' | 'center' | 'end' | 'baseline' | 'stretch'`, `JustifyContent = 'start' | 'center' | 'end' | 'around' | 'between' | 'evenly'`
344
+
345
+ ### VibeCol
346
+
347
+ Bootstrap grid column with responsive sizing, offsets, ordering, and alignment.
348
+
349
+ | Prop | Type | Default | Description |
350
+ |------|------|---------|-------------|
351
+ | `tag` | `Tag` | `'div'` | HTML element |
352
+ | `cols` | `ColSize \| boolean` | — | Base width (`1`-`12`, `'auto'`, or `true`) |
353
+ | `sm` / `md` / `lg` / `xl` / `xxl` | `ColSize \| boolean` | — | Responsive width |
354
+ | `offset` | `Number` | — | Base offset (`0`-`11`) |
355
+ | `offset{Sm\|Md\|Lg\|Xl\|Xxl}` | `Number` | — | Responsive offsets |
356
+ | `order` | `OrderValue` | — | Visual order |
357
+ | `order{Sm\|Md\|Lg\|Xl\|Xxl}` | `OrderValue` | — | Responsive order |
358
+ | `alignSelf` | `AlignItems` | — | Individual vertical alignment |
359
+
360
+ Types: `ColSize = 1-12 | 'auto'`, `OrderValue = 0-5 | 'first' | 'last'`
361
+
362
+ When no sizing props are provided, `VibeCol` defaults to equal-width (`col` class).
363
+
364
+ ```vue
365
+ <VibeContainer>
366
+ <VibeRow :gutters="3">
367
+ <VibeCol :cols="12" :md="6" :lg="4">Responsive</VibeCol>
368
+ <VibeCol :cols="12" :md="6" :lg="4">Responsive</VibeCol>
369
+ <VibeCol :cols="12" :md="12" :lg="4">Responsive</VibeCol>
370
+ </VibeRow>
371
+ </VibeContainer>
372
+ ```
373
+
374
+ ---
375
+
376
+ ## Core Patterns
377
+
378
+ ### v-model for visibility
379
+
380
+ All interactive components (Modal, Offcanvas, Toast, Collapse, Alert) use `v-model` to control open/closed state:
381
+
382
+ ```vue
383
+ <script setup>
384
+ const show = ref(false)
385
+ </script>
386
+
387
+ <template>
388
+ <VibeButton @click="show = true">Open</VibeButton>
389
+ <VibeModal v-model="show" title="Hello">Content</VibeModal>
390
+ </template>
391
+ ```
392
+
393
+ ### Data-driven components
394
+
395
+ Components like `VibeBreadcrumb`, `VibeListGroup`, `VibeDropdown`, `VibeAccordion`, `VibeCarousel`, and `VibeProgress` accept an `items` or `bars` array prop rather than manual slot markup:
396
+
397
+ ```vue
398
+ <VibeBreadcrumb :items="[
399
+ { text: 'Home', href: '/' },
400
+ { text: 'Products', href: '/products' },
401
+ { text: 'Details', active: true }
402
+ ]" />
403
+ ```
404
+
405
+ ### VibeFormGroup — automatic label linking
406
+
407
+ `VibeFormGroup` uses Vue's `provide`/`inject` to share a generated ID with child inputs. This means `id` and `label-for` are optional — the group handles accessibility automatically:
408
+
409
+ ```vue
410
+ <VibeFormGroup label="Email Address">
411
+ <VibeFormInput v-model="email" type="email" />
412
+ <!-- label is automatically linked to input, no id needed -->
413
+ </VibeFormGroup>
414
+ ```
415
+
416
+ ### Programmatic control (template ref)
417
+
418
+ Visibility is normally driven by `v-model` — prefer that. When you must trigger an action imperatively (e.g. open a modal from a non-template handler), interactive components expose **safe methods** via a template ref. Use these, not the underlying Bootstrap object:
419
+
420
+ - `VibeModal` / `VibeOffcanvas` / `VibeToast` → `show()`, `hide()`
421
+ - `VibeDropdown` → `show()`, `hide()`, `toggle()`
422
+ - `VibeCarousel` / `VibeAccordion` / `VibeNav` / `VibeScrollspy` → `refresh()`
423
+
424
+ ```vue
425
+ <template>
426
+ <VibeModal ref="modal" v-model="show" title="Advanced" />
427
+ </template>
428
+
429
+ <script setup>
430
+ const modal = useTemplateRef('modal')
431
+ modal.value?.show() // or modal.value?.hide()
432
+ </script>
433
+ ```
434
+
435
+ Drive every component through its props, `v-model`, slots, and these exposed methods. Do not reach around the component to Bootstrap's own JS — VibeUI owns that lifecycle, and bypassing it desyncs component state.
436
+
437
+ ### Teleportation
438
+
439
+ `VibeModal`, `VibeOffcanvas`, and `VibeToast` render via `<Teleport to="body">` by default. This prevents CSS stacking context issues. No configuration needed.
440
+
441
+ ### Toast service
442
+
443
+ Mount `<VibeToastHost />` once at the app root, then dispatch from anywhere via the `useToast` composable. Toasts at the same placement stack inside one shared `.toast-container`.
444
+
445
+ ```vue
446
+ <!-- App.vue -->
447
+ <template>
448
+ <RouterView />
449
+ <VibeToastHost />
450
+ </template>
451
+ ```
452
+
453
+ ```ts
454
+ import { useToast } from '@velkymx/vibeui'
455
+ const toast = useToast()
456
+ toast.success('Saved')
457
+ toast.error('Network error', { delay: 8000 })
458
+ const sticky = toast.warn('Long export…', { autohide: false })
459
+ toast.dismiss(sticky.id)
460
+ ```
461
+
462
+ The component-form `<VibeToast v-model="show">` still works for app-local toasts.
463
+
464
+ ### Drag & drop
465
+
466
+ Two layers depending on use case:
467
+
468
+ - **Single-list reorder** → `<VibeSortable v-model="items">` with a scoped slot per item.
469
+ - **Cross-list moves** (kanban, builders) → `<VibeDraggable :payload>` paired with `<VibeDroppable @drop>`. Match by `group` (default `'default'`) or `acceptGroups` whitelist. Payloads pass through a shared module store, so they can be typed objects, refs, or non-serializable values — no DataTransfer string round-trip.
470
+
471
+ A document-level `dragend` / `drop` listener installed by `setActiveDrag` clears stale payloads if a drag aborts (escape, page nav, browser bug).
472
+
473
+ ### Calendar popover
474
+
475
+ `VibeDatePicker` is a fully custom calendar (distinct from `VibeFormDatepicker` which wraps `<input type=date>`). ISO `YYYY-MM-DD` model, `range` mode emits `[start, end]`, full keyboard nav (arrows / PageUp+Down / Home+End / Enter / Escape), click-outside-to-close.
476
+
477
+ ```vue
478
+ <VibeDatePicker v-model="due" :min="'2026-01-01'" :max="'2026-12-31'" />
479
+ <VibeDatePicker v-model="window" range />
480
+ ```
481
+
482
+ ### Floating UI anchoring
483
+
484
+ For ad-hoc anchoring when CSS position utilities aren't enough. Wraps `@floating-ui/dom` with a natural-language anchor-point API.
485
+
486
+ ```ts
487
+ const { x, y, placement, update, stop } = usePosition(targetRef, anchorRef, () => ({
488
+ my: 'top center',
489
+ at: 'bottom center',
490
+ offset: [0, 8],
491
+ collision: 'flip+shift'
492
+ }))
493
+ ```
494
+
495
+ Pass options as a getter to keep `my` / `at` reactive.
496
+
497
+ ---
498
+
499
+ ## Composables
500
+
501
+ ### useColorMode
502
+
503
+ Singleton. Sets `data-bs-theme` on `<html>`. Persists to `localStorage`.
504
+
505
+ ```ts
506
+ const { colorMode, setColorMode, toggleColorMode, initColorMode } = useColorMode()
507
+
508
+ // colorMode: Ref<'light' | 'dark' | 'auto'>
509
+ // Call initColorMode() once in main.ts — subsequent calls are no-ops
510
+ // toggleColorMode() cycles light → dark → auto → light
511
+ ```
512
+
513
+ Full docs: `docs/composables/color-mode.md`
514
+
515
+ ### useFormValidation
516
+
517
+ Per-field validation with reactive state.
518
+
519
+ ```ts
520
+ const email = useFormValidation('')
521
+
522
+ // email.value — Ref<string>
523
+ // email.validationState — Ref<'valid' | 'invalid' | null>
524
+ // email.validationMessage — Ref<string>
525
+ // email.isValid / email.isInvalid — computed booleans
526
+ // email.isDirty / email.isTouched / email.isValidating — Ref<boolean>
527
+ // await email.validate(rules)
528
+ // email.reset()
529
+ ```
530
+
531
+ ### validators (built-in)
532
+
533
+ ```ts
534
+ import { validators } from '@velkymx/vibeui'
535
+
536
+ validators.required(message?)
537
+ validators.email(message?)
538
+ validators.minLength(n, message?)
539
+ validators.maxLength(n, message?)
540
+ validators.min(n, message?)
541
+ validators.max(n, message?)
542
+ validators.pattern(regex, message?)
543
+ validators.url(message?)
544
+ validators.async(fn: (value) => Promise<boolean | string>)
545
+ ```
546
+
547
+ Validators return `true` on pass, or a string error message on failure.
548
+ Full docs: `docs/forms/validation.md`
549
+
550
+ ### useId
551
+
552
+ Generates a unique, SSR-safe ID string. Used internally by form components.
553
+
554
+ ```ts
555
+ const id = useId('prefix') // → 'prefix-3-7'
556
+ ```
557
+
558
+ ### useForm
559
+
560
+ Multi-field form state with reactive `fields`, `errors`, `touched` maps. `fields` is a Vue `reactive` so its properties auto-unwrap in templates.
561
+
562
+ ```ts
563
+ const { fields, errors, touched, isDirty, isValid, validate, validateField, reset, markAllTouched, setField, values }
564
+ = useForm({ name: '', email: '', age: 0 })
565
+
566
+ const result = await validate({
567
+ name: [validators.required('Name is required')],
568
+ email: [validators.required(), validators.email()],
569
+ age: [validators.min(18, 'Must be 18+')]
570
+ })
571
+ // result.valid: boolean
572
+ // result.errors: Partial<Record<K, string>>
573
+ ```
574
+
575
+ `isValid` is `false` until the first `validate()` or `validateField()` call; then `true` if no field has an error. Use this to guard submit buttons without showing errors on a fresh form.
576
+
577
+ `reset` deep-clones the initial values back in (works for object / array fields). Dirty tracking uses structural compare (`JSON.stringify`) so non-primitive fields don't read as permanently dirty.
578
+
579
+ For **single-field** validation, prefer `useFormValidation` (older API) and destructure at the call site to keep templates clean.
580
+
581
+ ### useToast
582
+
583
+ ```ts
584
+ const { show, success, error, warn, info, dismiss, clear, toasts } = useToast()
585
+
586
+ success(body, { id?, title?, placement?, autohide?, delay? })
587
+ toasts // DeepReadonly<ToastSpec[]>
588
+ ```
589
+
590
+ The store is a module singleton (per app per JS realm). For SSR, call `resetToastStoreForSSR()` from the server entry's per-request reset hook. Multi-app browser usage shares the same queue — use the component form for app-local notifications.
591
+
592
+ ### usePosition
593
+
594
+ ```ts
595
+ const { x, y, placement, update, stop } = usePosition(target, anchor, options)
596
+ ```
597
+
598
+ `options` accepts a plain object (static) **or** a getter `() => UsePositionOptions` (reactive). The getter is re-evaluated on every `update()` and is part of the watch dependency set.
599
+
600
+ The composable owns `position` / `left` / `top` on the target element while alive. `stop()` (and `onBeforeUnmount`) restore the previous inline values.
601
+
602
+ ### useBreakpoints
603
+
604
+ ```ts
605
+ const { isXs, isSm, isMd, isLg, isXl, isXxl, isMobile, isTablet } = useBreakpoints()
606
+ ```
607
+
608
+ Reactive booleans driven by `window.matchMedia` against Bootstrap's standard breakpoints.
609
+
610
+ ### useBackButton
611
+
612
+ Hooks Android's hardware back button (Capacitor / Cordova) into Vue components — `VibeModal` and `VibeOffcanvas` register their close handlers automatically.
613
+
614
+ ---
615
+
616
+ ## Directives
617
+
618
+ ### v-vibe-tooltip
619
+
620
+ Inline tooltip directive registered globally by `app.use(VibeUI)`.
621
+
622
+ ```vue
623
+ <button v-vibe-tooltip="'Save changes'">Save</button>
624
+ <span v-vibe-tooltip="{ title: 'Open in new tab', placement: 'right', html: true }">↗</span>
625
+ ```
626
+
627
+ Re-creates the underlying Bootstrap Tooltip when `placement` / `trigger` / `html` change; title-only changes use the cheap `setContent` path. Touch devices auto-swap `hover focus` → `click`.
628
+
629
+ For component-form usage with rich slot content, use `<VibeTooltip>`.
630
+
631
+ ---
632
+
633
+ ## Testing
634
+
635
+ Two Vitest projects share one Vite pipeline:
636
+ - **`unit`** — `@vue/test-utils` + happy-dom, with **Bootstrap JS mocked** (`tests/mocks/bootstrap.ts`, aliased only in this project). Fast.
637
+ - **`browser`** — real headless Chromium (Playwright provider) running the **real** Bootstrap JS + Quill — the integration paths happy-dom can't reach.
638
+
639
+ ```bash
640
+ npm run test:run # unit suite (what CI runs)
641
+ npm run test:browser # browser suite, real Chromium
642
+ npm run test:all # both projects
643
+ npm run test:examples # Playwright: load every examples/*.html and assert it mounts (needs CDN network)
644
+ ```
645
+
646
+ So unit tests never trigger real Bootstrap JS; the browser tests deliberately do.
647
+
648
+ **Pattern for component tests:**
649
+ ```ts
650
+ import { describe, it, expect } from 'vitest'
651
+ import { mount } from '@vue/test-utils'
652
+ import VibeButton from '../../src/components/VibeButton.vue'
653
+
654
+ it('applies variant class', () => {
655
+ const wrapper = mount(VibeButton, { props: { variant: 'danger' } })
656
+ expect(wrapper.classes()).toContain('btn-danger')
657
+ })
658
+ ```
659
+
660
+ **Pattern for composable tests:**
661
+ - Use `_resetColorMode()` (exported from `useColorMode.ts`, not from the package index) to reset singleton state between tests.
662
+ - Use `vi.stubGlobal` + `try/finally` for environment mocking (e.g. SSR simulation).
663
+
664
+ ---
665
+
666
+ ## Build
667
+
668
+ ```bash
669
+ npm run build # outputs to dist/ as ESM + UMD + type declarations
670
+ npm run dev # Vite dev server for the src/App.vue playground
671
+ ```
672
+
673
+ Build output:
674
+ - `dist/vibeui.es.js` — ESM for bundlers
675
+ - `dist/vibeui.umd.js` — UMD for CDN / script tag usage
676
+ - `dist/vibeui.d.ts` — TypeScript declarations
677
+
678
+ Vue and Bootstrap are **external** — not bundled. Consumers provide their own. `@floating-ui/dom` is a runtime dependency (used by `usePosition`).
679
+
680
+ ### Smoke testing
681
+
682
+ ```bash
683
+ npm run smoke # rebuilds the lib + serves smoke/ on :8001
684
+ ```
685
+
686
+ `smoke/index.html` mounts every component with realistic data and a status badge for runtime errors. Bootstrap, Quill, and Vue are loaded from CDN via an importmap (Bootstrap and Quill via `esm.sh` so transitive deps like `@popperjs/core` resolve automatically).
687
+
688
+ ---
689
+
690
+ ## Adding a New Component
691
+
692
+ 1. Create `src/components/VibeFoo.vue`
693
+ 2. Follow the pattern: `defineProps` with typed props, `computed` for class assembly, `<component :is="tag">` if the element type is configurable
694
+ 3. Export from `src/components/index.ts` (named export + `app.component(...)` registration)
695
+ 4. Export from `src/index.ts` (re-export via `export * from './components'` covers it automatically)
696
+ 5. Add types to `src/types.ts` if new shared types are needed
697
+ 6. Add tests in `tests/components/VibeFoo.test.ts`
698
+ 7. Add docs in `docs/components/{category}/foo.md`
699
+
700
+ If the component uses Bootstrap JS:
701
+ - Dynamically import: `const { Foo } = await import('bootstrap')`
702
+ - Initialize in `onMounted`, dispose in `onBeforeUnmount`
703
+ - Watch functional props and call `instance.dispose()` + reinitialize on change
704
+ - Expose safe imperative methods (`show()` / `hide()` / `refresh()`) via `defineExpose`. Follow the existing convention of also exposing the raw instance only under the `_unsafe_bsInstance` name (escape hatch — never a plain `bsInstance`)
705
+
706
+ ---
707
+
708
+ ## Canvas Charts
709
+
710
+ Three chart components drawn directly on `<canvas>` — no Chart.js or other dependency.
711
+
712
+ ```vue
713
+ <VibeChartLine :data="data" smooth fill legend="top" />
714
+ <VibeChartBar :data="data" stacked />
715
+ <VibeChartPie :data="data" />
716
+ ```
717
+
718
+ All three share the same `ChartData` shape:
719
+
720
+ ```ts
721
+ const data: ChartData = {
722
+ labels: ['Jan', 'Feb', 'Mar'],
723
+ datasets: [
724
+ { label: 'Revenue', data: [100, 150, 130], color: '#0d6efd' } // color optional
725
+ ]
726
+ }
727
+ ```
728
+
729
+ When `color` is omitted, colors cycle through Bootstrap CSS variables (`--bs-primary`, `--bs-success`, `--bs-danger`, ...) from the component's computed style — automatically matching your Bootstrap theme and dark mode.
730
+
731
+ | Prop | Line | Bar | Pie | Description |
732
+ |------|------|-----|-----|-------------|
733
+ | `data` | ✓ | ✓ | ✓ | `ChartData` — required |
734
+ | `legend` | ✓ | ✓ | ✓ | `'top' \| 'bottom' \| 'none'` (default `'bottom'`) |
735
+ | `height` | ✓ | ✓ | ✓ | Number/String px or `'auto'` (default). Line/Bar → 16:9, Pie → 1:1 |
736
+ | `showAxes` | ✓ | ✓ | — | Show X/Y axes (default `true`) |
737
+ | `showGrid` | ✓ | ✓ | — | Show grid lines (default `true`) |
738
+ | `smooth` | ✓ | — | — | Bezier curves instead of straight lines |
739
+ | `fill` | ✓ | — | — | Fill area under line |
740
+ | `stacked` | — | ✓ | — | Stack datasets instead of grouping |
741
+
742
+ All charts are responsive via `ResizeObserver`, render at device pixel ratio for crisp HiDPI output, and show hover tooltips on data points/slices/bars.
743
+
744
+ ---
745
+
746
+ ## What VibeUI Does NOT Do
747
+
748
+ - Does not bundle Bootstrap CSS or JS
749
+ - Does not require Bootstrap Icons (optional peer dep for `VibeIcon`)
750
+ - Does not require Vue Router (optional peer dep for `href`/`to` props on `VibeButton`)
751
+ - Does not require QuillJS (optional peer dep for `VibeFormWysiwyg`)
752
+ - Does not provide a theme system beyond Bootstrap's `data-bs-theme` color modes
753
+
754
+ ## Documentation Index
755
+
756
+ Per-component and per-API reference. **Read the relevant page before building with a component.**
757
+
758
+ **Start here:** [docs/README.md](docs/README.md) · [Getting started / starter template](docs/getting-started/starter-template.md) · [Versioning & stability policy](docs/versioning.md)
759
+
760
+ **Layout:** [VibeContainer](docs/components/layout/container.md) · [VibeRow](docs/components/layout/row.md) · [VibeCol](docs/components/layout/col.md)
761
+
762
+ **Core:** [VibeAlert](docs/components/core/alert.md) · [VibeBadge](docs/components/core/badge.md) · [VibeButton](docs/components/core/button.md) · [VibeButtonGroup](docs/components/core/button-group.md) · [VibeCloseButton](docs/components/core/close-button.md) · [VibeLink](docs/components/core/link.md) · [VibePlaceholder](docs/components/core/placeholder.md) · [VibeSkeleton](docs/components/core/skeleton.md) · [VibeSpinner](docs/components/core/spinner.md)
763
+
764
+ **Card / Hero / List:** [VibeCard](docs/components/card/card.md) · [VibeHero](docs/components/hero/hero.md) · [VibeListGroup](docs/components/list/list-group.md)
765
+
766
+ **Navigation:** [VibeBreadcrumb](docs/components/navigation/breadcrumb.md) · [VibeNav](docs/components/navigation/nav.md) · [VibeNavbar](docs/components/navigation/navbar.md) · [VibePagination](docs/components/navigation/pagination.md)
767
+
768
+ **Interactive:** [VibeAccordion](docs/components/interactive/accordion.md) · [VibeCarousel](docs/components/interactive/carousel.md) · [VibeCollapse](docs/components/interactive/collapse.md) · [VibeDatePicker](docs/components/interactive/date-picker.md) · [VibeDraggable / VibeDroppable](docs/components/interactive/draggable.md) · [VibeDropdown](docs/components/interactive/dropdown.md) · [VibeModal](docs/components/interactive/modal.md) · [VibeOffcanvas](docs/components/interactive/offcanvas.md) · [VibeResizable](docs/components/interactive/resizable.md) · [VibeSlider](docs/components/interactive/slider.md) · [VibeSortable](docs/components/interactive/sortable.md) · [VibeStepper](docs/components/interactive/stepper.md) · [VibeTabs / VibeTab](docs/components/interactive/tabs.md) · [VibeToast](docs/components/interactive/toast.md)
769
+
770
+ **Advanced:** [VibePopover](docs/components/advanced/popover.md) · [VibeScrollspy](docs/components/advanced/scrollspy.md) · [VibeTooltip](docs/components/advanced/tooltip.md)
771
+
772
+ **Data & Charts:** [VibeDataTable](docs/components/data/datatable.md) · [VibeChartBar](docs/components/charts/chart-bar.md) · [VibeChartLine](docs/components/charts/chart-line.md) · [VibeChartPie](docs/components/charts/chart-pie.md)
773
+
774
+ **Progress:** [VibeProgress](docs/components/progress/progress.md)
775
+
776
+ **Forms:** [overview](docs/forms/README.md) · [VibeAutocomplete](docs/forms/autocomplete.md) · [VibeFileInput](docs/forms/file-input.md) · [VibeFormCheckbox](docs/forms/form-checkbox.md) · [VibeFormDatepicker](docs/forms/form-datepicker.md) · [VibeFormGroup](docs/forms/form-group.md) · [VibeFormInput](docs/forms/form-input.md) · [VibeFormRadio](docs/forms/form-radio.md) · [VibeFormSelect](docs/forms/form-select.md) · [VibeFormSpinbutton](docs/forms/form-spinbutton.md) · [VibeFormSwitch](docs/forms/form-switch.md) · [VibeFormTextarea](docs/forms/form-textarea.md) · [VibeFormWysiwyg](docs/forms/form-wysiwyg.md) · [VibeInputGroup](docs/forms/input-group.md) · [Validation & useForm](docs/forms/validation.md)
777
+
778
+ **Composables:** [useBackButton](docs/composables/back-button.md) · [useBreakpoints](docs/composables/breakpoints.md) · [useColorMode](docs/composables/color-mode.md) · [useForm](docs/composables/use-form.md) · [usePosition](docs/composables/use-position.md) · [useToast](docs/composables/use-toast.md)
779
+
780
+ **Directives:** [v-vibe-tooltip](docs/directives/v-tooltip.md)
781
+
782
+ **Utilities:** [Position utility classes](docs/utilities/position.md)