@velkymx/vibeui 0.9.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +44 -0
- package/CLAUDE.md +48 -0
- package/README.md +222 -197
- package/dist/App.vue.d.ts +2 -1
- package/dist/components/HelloWorld.vue.d.ts +2 -1
- package/dist/components/VibeAccordion.vue.d.ts +39 -40
- package/dist/components/VibeAlert.vue.d.ts +21 -37
- package/dist/components/VibeAutocomplete.vue.d.ts +59 -31
- package/dist/components/VibeBadge.vue.d.ts +10 -18
- package/dist/components/VibeBreadcrumb.vue.d.ts +25 -21
- package/dist/components/VibeButton.vue.d.ts +13 -17
- package/dist/components/VibeButtonGroup.vue.d.ts +11 -19
- package/dist/components/VibeCard.vue.d.ts +51 -24
- package/dist/components/VibeCarousel.vue.d.ts +27 -49
- package/dist/components/VibeChartBar.vue.d.ts +61 -0
- package/dist/components/VibeChartLine.vue.d.ts +70 -0
- package/dist/components/VibeChartPie.vue.d.ts +34 -0
- package/dist/components/VibeCloseButton.vue.d.ts +9 -7
- package/dist/components/VibeCol.vue.d.ts +9 -17
- package/dist/components/VibeCollapse.vue.d.ts +24 -32
- package/dist/components/VibeContainer.vue.d.ts +9 -17
- package/dist/components/VibeDataTable.vue.d.ts +122 -63
- package/dist/components/VibeDatePicker.vue.d.ts +4 -6
- package/dist/components/VibeDraggable.vue.d.ts +9 -13
- package/dist/components/VibeDropdown.vue.d.ts +46 -43
- package/dist/components/VibeDroppable.vue.d.ts +9 -13
- package/dist/components/VibeFileInput.vue.d.ts +14 -20
- package/dist/components/VibeFormCheckbox.vue.d.ts +30 -22
- package/dist/components/VibeFormDatepicker.vue.d.ts +27 -30
- package/dist/components/VibeFormErrorSummary.vue.d.ts +17 -0
- package/dist/components/VibeFormGroup.vue.d.ts +10 -13
- package/dist/components/VibeFormInput.vue.d.ts +60 -25
- package/dist/components/VibeFormRadio.vue.d.ts +23 -24
- package/dist/components/VibeFormSelect.vue.d.ts +28 -34
- package/dist/components/VibeFormSpinbutton.vue.d.ts +31 -34
- package/dist/components/VibeFormSwitch.vue.d.ts +22 -23
- package/dist/components/VibeFormTextarea.vue.d.ts +24 -25
- package/dist/components/VibeFormWysiwyg.vue.d.ts +23 -22
- package/dist/components/VibeHero.vue.d.ts +117 -0
- package/dist/components/VibeIcon.vue.d.ts +27 -7
- package/dist/components/VibeInputGroup.vue.d.ts +12 -14
- package/dist/components/VibeLink.vue.d.ts +14 -18
- package/dist/components/VibeListGroup.vue.d.ts +26 -22
- package/dist/components/VibeModal.vue.d.ts +47 -45
- package/dist/components/VibeNav.vue.d.ts +40 -20
- package/dist/components/VibeNavbar.vue.d.ts +12 -19
- package/dist/components/VibeNavbarBrand.vue.d.ts +9 -17
- package/dist/components/VibeNavbarNav.vue.d.ts +52 -33
- package/dist/components/VibeNavbarToggle.vue.d.ts +7 -5
- package/dist/components/VibeOffcanvas.vue.d.ts +28 -43
- package/dist/components/VibePagination.vue.d.ts +38 -29
- package/dist/components/VibePlaceholder.vue.d.ts +9 -17
- package/dist/components/VibePopover.vue.d.ts +17 -40
- package/dist/components/VibeProgress.vue.d.ts +13 -21
- package/dist/components/VibeResizable.vue.d.ts +10 -14
- package/dist/components/VibeRow.vue.d.ts +9 -17
- package/dist/components/VibeScrollspy.vue.d.ts +37 -25
- package/dist/components/VibeSkeleton.vue.d.ts +2 -1
- package/dist/components/VibeSlider.vue.d.ts +4 -5
- package/dist/components/VibeSortable.vue.d.ts +37 -18
- package/dist/components/VibeSpinner.vue.d.ts +4 -7
- package/dist/components/VibeStepper.vue.d.ts +32 -31
- package/dist/components/VibeTab.vue.d.ts +7 -11
- package/dist/components/VibeTabContent.vue.d.ts +13 -31
- package/dist/components/VibeTabs.vue.d.ts +9 -13
- package/dist/components/VibeToast.vue.d.ts +31 -44
- package/dist/components/VibeToastHost.vue.d.ts +2 -1
- package/dist/components/VibeTooltip.vue.d.ts +16 -39
- package/dist/components/chart/chartColors.d.ts +3 -0
- package/dist/components/chart/chartResize.d.ts +2 -0
- package/dist/components/chart/chartTooltip.d.ts +2 -0
- package/dist/components/chart/chartTypes.d.ts +6 -0
- package/dist/components/chart/drawBar.d.ts +5 -0
- package/dist/components/chart/drawLine.d.ts +18 -0
- package/dist/components/chart/drawPie.d.ts +4 -0
- package/dist/components/dndStore.d.ts +1 -0
- package/dist/components/index.d.ts +6 -1
- package/dist/composables/useBreakpoints.d.ts +1 -0
- package/dist/composables/useColorMode.d.ts +1 -1
- package/dist/composables/useId.d.ts +1 -4
- package/dist/directives/vTooltip.d.ts +2 -3
- package/dist/injectionKeys.d.ts +34 -0
- package/dist/purify.es-eYghtX9j.mjs +735 -0
- package/dist/types.d.ts +23 -2
- package/dist/utils/routeKey.d.ts +11 -0
- package/dist/utils/safeCss.d.ts +16 -0
- package/dist/utils/safeHref.d.ts +7 -0
- package/dist/utils/sanitizeHtml.d.ts +13 -0
- package/dist/vibeui.css +2 -1
- package/dist/vibeui.es.js +8527 -5855
- package/dist/vibeui.umd.js +3 -1
- package/docs/README.md +225 -0
- package/docs/components/advanced/popover.md +125 -0
- package/docs/components/advanced/scrollspy.md +106 -0
- package/docs/components/advanced/tooltip.md +90 -0
- package/docs/components/card/card.md +223 -0
- package/docs/components/charts/chart-bar.md +129 -0
- package/docs/components/charts/chart-line.md +136 -0
- package/docs/components/charts/chart-pie.md +102 -0
- package/docs/components/core/alert.md +94 -0
- package/docs/components/core/badge.md +75 -0
- package/docs/components/core/button-group.md +99 -0
- package/docs/components/core/button.md +145 -0
- package/docs/components/core/close-button.md +82 -0
- package/docs/components/core/link.md +79 -0
- package/docs/components/core/placeholder.md +129 -0
- package/docs/components/core/skeleton.md +40 -0
- package/docs/components/core/spinner.md +103 -0
- package/docs/components/data/datatable.md +437 -0
- package/docs/components/hero/hero.md +65 -0
- package/docs/components/interactive/accordion.md +121 -0
- package/docs/components/interactive/carousel.md +112 -0
- package/docs/components/interactive/collapse.md +106 -0
- package/docs/components/interactive/date-picker.md +78 -0
- package/docs/components/interactive/draggable.md +91 -0
- package/docs/components/interactive/dropdown.md +129 -0
- package/docs/components/interactive/modal.md +151 -0
- package/docs/components/interactive/offcanvas.md +92 -0
- package/docs/components/interactive/resizable.md +73 -0
- package/docs/components/interactive/slider.md +57 -0
- package/docs/components/interactive/sortable.md +52 -0
- package/docs/components/interactive/stepper.md +83 -0
- package/docs/components/interactive/tabs.md +66 -0
- package/docs/components/interactive/toast.md +177 -0
- package/docs/components/layout/col.md +117 -0
- package/docs/components/layout/container.md +53 -0
- package/docs/components/layout/row.md +107 -0
- package/docs/components/list/list-group.md +225 -0
- package/docs/components/navigation/breadcrumb.md +120 -0
- package/docs/components/navigation/nav.md +151 -0
- package/docs/components/navigation/navbar.md +114 -0
- package/docs/components/navigation/pagination.md +163 -0
- package/docs/components/progress/progress.md +188 -0
- package/docs/composables/back-button.md +28 -0
- package/docs/composables/breakpoints.md +54 -0
- package/docs/composables/color-mode.md +179 -0
- package/docs/composables/use-form.md +78 -0
- package/docs/composables/use-position.md +68 -0
- package/docs/composables/use-toast.md +91 -0
- package/docs/directives/v-tooltip.md +58 -0
- package/docs/forms/README.md +87 -0
- package/docs/forms/autocomplete.md +96 -0
- package/docs/forms/file-input.md +97 -0
- package/docs/forms/form-checkbox.md +100 -0
- package/docs/forms/form-datepicker.md +80 -0
- package/docs/forms/form-error-summary.md +96 -0
- package/docs/forms/form-group.md +118 -0
- package/docs/forms/form-input.md +140 -0
- package/docs/forms/form-radio.md +82 -0
- package/docs/forms/form-select.md +98 -0
- package/docs/forms/form-spinbutton.md +94 -0
- package/docs/forms/form-switch.md +75 -0
- package/docs/forms/form-textarea.md +82 -0
- package/docs/forms/form-wysiwyg.md +99 -0
- package/docs/forms/input-group.md +70 -0
- package/docs/forms/validation.md +213 -0
- package/docs/getting-started/starter-template.md +136 -0
- package/docs/superpowers/plans/2026-05-12-vibe-charts.md +1999 -0
- package/docs/superpowers/plans/2026-05-29-e2e-browser-testing.md +615 -0
- package/docs/superpowers/specs/2026-05-12-charting-design.md +185 -0
- package/docs/superpowers/specs/2026-05-29-e2e-browser-testing-design.md +168 -0
- package/docs/utilities/position.md +82 -0
- package/docs/versioning.md +57 -0
- package/examples/README.md +201 -0
- package/examples/album.html +262 -0
- package/examples/blog.html +166 -0
- package/examples/carousel.html +114 -0
- package/examples/checkout.html +275 -0
- package/examples/cover.html +89 -0
- package/examples/dashboard.html +156 -0
- package/examples/index.html +129 -0
- package/examples/jumbotron.html +56 -0
- package/examples/mobile-dashboard.html +138 -0
- package/examples/pricing.html +172 -0
- package/examples/product.html +145 -0
- package/examples/sidebars.html +166 -0
- package/examples/sign-in.html +119 -0
- package/examples/starter.html +314 -0
- package/examples/sticky-footer-navbar.html +70 -0
- package/examples/sticky-footer.html +60 -0
- package/examples/test-simple.html +91 -0
- package/llms.txt +801 -0
- package/package.json +44 -14
- package/dist/types/index.d.ts +0 -6
package/llms.txt
ADDED
|
@@ -0,0 +1,801 @@
|
|
|
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 and accessibility
|
|
406
|
+
|
|
407
|
+
`VibeFormGroup` uses Vue's `provide`/`inject` to share a generated ID with child inputs. `id`, `for`, and `aria-describedby` are all wired automatically — no manual ids needed:
|
|
408
|
+
|
|
409
|
+
```vue
|
|
410
|
+
<!-- required prop renders red * + visually-hidden "required" for screen readers -->
|
|
411
|
+
<!-- invalid-feedback has role="alert" — errors announced without focus -->
|
|
412
|
+
<VibeFormGroup label="Email Address" required :validation-state="err ? 'invalid' : null" :validation-message="err">
|
|
413
|
+
<VibeFormInput v-model="email" type="email" />
|
|
414
|
+
</VibeFormGroup>
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
For multi-field forms, place `<VibeFormErrorSummary :errors="errors" @focus="focusField" />` at the top of the form. It renders a `role="alert"` block listing all non-empty errors with focus links (WCAG 3.3.1).
|
|
418
|
+
|
|
419
|
+
### VibeFormInput — password, autocomplete, and mobile keyboard
|
|
420
|
+
|
|
421
|
+
```vue
|
|
422
|
+
<!-- show-toggle: eye button to reveal password -->
|
|
423
|
+
<!-- show-password-strength: 4-segment strength bar with aria-live -->
|
|
424
|
+
<!-- autocomplete: typed enum — "current-password", "new-password", "email", etc. -->
|
|
425
|
+
<!-- inputmode: auto-detected (number→decimal, email→email, tel→tel, url→url, search→search) -->
|
|
426
|
+
<VibeFormInput
|
|
427
|
+
v-model="password"
|
|
428
|
+
type="password"
|
|
429
|
+
autocomplete="new-password"
|
|
430
|
+
show-toggle
|
|
431
|
+
show-password-strength
|
|
432
|
+
/>
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
### Programmatic control (template ref)
|
|
436
|
+
|
|
437
|
+
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:
|
|
438
|
+
|
|
439
|
+
- `VibeModal` / `VibeOffcanvas` / `VibeToast` → `show()`, `hide()`
|
|
440
|
+
- `VibeDropdown` → `show()`, `hide()`, `toggle()`
|
|
441
|
+
- `VibeCarousel` / `VibeAccordion` / `VibeNav` / `VibeScrollspy` → `refresh()`
|
|
442
|
+
|
|
443
|
+
```vue
|
|
444
|
+
<template>
|
|
445
|
+
<VibeModal ref="modal" v-model="show" title="Advanced" />
|
|
446
|
+
</template>
|
|
447
|
+
|
|
448
|
+
<script setup>
|
|
449
|
+
const modal = useTemplateRef('modal')
|
|
450
|
+
modal.value?.show() // or modal.value?.hide()
|
|
451
|
+
</script>
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
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.
|
|
455
|
+
|
|
456
|
+
### Teleportation
|
|
457
|
+
|
|
458
|
+
`VibeModal`, `VibeOffcanvas`, and `VibeToast` render via `<Teleport to="body">` by default. This prevents CSS stacking context issues. No configuration needed.
|
|
459
|
+
|
|
460
|
+
### Toast service
|
|
461
|
+
|
|
462
|
+
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`.
|
|
463
|
+
|
|
464
|
+
```vue
|
|
465
|
+
<!-- App.vue -->
|
|
466
|
+
<template>
|
|
467
|
+
<RouterView />
|
|
468
|
+
<VibeToastHost />
|
|
469
|
+
</template>
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
```ts
|
|
473
|
+
import { useToast } from '@velkymx/vibeui'
|
|
474
|
+
const toast = useToast()
|
|
475
|
+
toast.success('Saved')
|
|
476
|
+
toast.error('Network error', { delay: 8000 })
|
|
477
|
+
const sticky = toast.warn('Long export…', { autohide: false })
|
|
478
|
+
toast.dismiss(sticky.id)
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
The component-form `<VibeToast v-model="show">` still works for app-local toasts.
|
|
482
|
+
|
|
483
|
+
### Drag & drop
|
|
484
|
+
|
|
485
|
+
Two layers depending on use case:
|
|
486
|
+
|
|
487
|
+
- **Single-list reorder** → `<VibeSortable v-model="items">` with a scoped slot per item.
|
|
488
|
+
- **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.
|
|
489
|
+
|
|
490
|
+
A document-level `dragend` / `drop` listener installed by `setActiveDrag` clears stale payloads if a drag aborts (escape, page nav, browser bug).
|
|
491
|
+
|
|
492
|
+
### Calendar popover
|
|
493
|
+
|
|
494
|
+
`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.
|
|
495
|
+
|
|
496
|
+
```vue
|
|
497
|
+
<VibeDatePicker v-model="due" :min="'2026-01-01'" :max="'2026-12-31'" />
|
|
498
|
+
<VibeDatePicker v-model="window" range />
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
### Floating UI anchoring
|
|
502
|
+
|
|
503
|
+
For ad-hoc anchoring when CSS position utilities aren't enough. Wraps `@floating-ui/dom` with a natural-language anchor-point API.
|
|
504
|
+
|
|
505
|
+
```ts
|
|
506
|
+
const { x, y, placement, update, stop } = usePosition(targetRef, anchorRef, () => ({
|
|
507
|
+
my: 'top center',
|
|
508
|
+
at: 'bottom center',
|
|
509
|
+
offset: [0, 8],
|
|
510
|
+
collision: 'flip+shift'
|
|
511
|
+
}))
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
Pass options as a getter to keep `my` / `at` reactive.
|
|
515
|
+
|
|
516
|
+
---
|
|
517
|
+
|
|
518
|
+
## Composables
|
|
519
|
+
|
|
520
|
+
### useColorMode
|
|
521
|
+
|
|
522
|
+
Singleton. Sets `data-bs-theme` on `<html>`. Persists to `localStorage`.
|
|
523
|
+
|
|
524
|
+
```ts
|
|
525
|
+
const { colorMode, setColorMode, toggleColorMode, initColorMode } = useColorMode()
|
|
526
|
+
|
|
527
|
+
// colorMode: Ref<'light' | 'dark' | 'auto'>
|
|
528
|
+
// Call initColorMode() once in main.ts — subsequent calls are no-ops
|
|
529
|
+
// toggleColorMode() cycles light → dark → auto → light
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
Full docs: `docs/composables/color-mode.md`
|
|
533
|
+
|
|
534
|
+
### useFormValidation
|
|
535
|
+
|
|
536
|
+
Per-field validation with reactive state.
|
|
537
|
+
|
|
538
|
+
```ts
|
|
539
|
+
const email = useFormValidation('')
|
|
540
|
+
|
|
541
|
+
// email.value — Ref<string>
|
|
542
|
+
// email.validationState — Ref<'valid' | 'invalid' | null>
|
|
543
|
+
// email.validationMessage — Ref<string>
|
|
544
|
+
// email.isValid / email.isInvalid — computed booleans
|
|
545
|
+
// email.isDirty / email.isTouched / email.isValidating — Ref<boolean>
|
|
546
|
+
// await email.validate(rules)
|
|
547
|
+
// email.reset()
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
### validators (built-in)
|
|
551
|
+
|
|
552
|
+
```ts
|
|
553
|
+
import { validators } from '@velkymx/vibeui'
|
|
554
|
+
|
|
555
|
+
validators.required(message?)
|
|
556
|
+
validators.email(message?)
|
|
557
|
+
validators.minLength(n, message?)
|
|
558
|
+
validators.maxLength(n, message?)
|
|
559
|
+
validators.min(n, message?)
|
|
560
|
+
validators.max(n, message?)
|
|
561
|
+
validators.pattern(regex, message?)
|
|
562
|
+
validators.url(message?)
|
|
563
|
+
validators.async(fn: (value) => Promise<boolean | string>)
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
Validators return `true` on pass, or a string error message on failure.
|
|
567
|
+
Full docs: `docs/forms/validation.md`
|
|
568
|
+
|
|
569
|
+
### useId
|
|
570
|
+
|
|
571
|
+
Generates a unique, SSR-safe ID string. Used internally by form components.
|
|
572
|
+
|
|
573
|
+
```ts
|
|
574
|
+
const id = useId('prefix') // → 'prefix-3-7'
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
### useForm
|
|
578
|
+
|
|
579
|
+
Multi-field form state with reactive `fields`, `errors`, `touched` maps. `fields` is a Vue `reactive` so its properties auto-unwrap in templates.
|
|
580
|
+
|
|
581
|
+
```ts
|
|
582
|
+
const { fields, errors, touched, isDirty, isValid, validate, validateField, reset, markAllTouched, setField, values }
|
|
583
|
+
= useForm({ name: '', email: '', age: 0 })
|
|
584
|
+
|
|
585
|
+
const result = await validate({
|
|
586
|
+
name: [validators.required('Name is required')],
|
|
587
|
+
email: [validators.required(), validators.email()],
|
|
588
|
+
age: [validators.min(18, 'Must be 18+')]
|
|
589
|
+
})
|
|
590
|
+
// result.valid: boolean
|
|
591
|
+
// result.errors: Partial<Record<K, string>>
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
`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.
|
|
595
|
+
|
|
596
|
+
`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.
|
|
597
|
+
|
|
598
|
+
For **single-field** validation, prefer `useFormValidation` (older API) and destructure at the call site to keep templates clean.
|
|
599
|
+
|
|
600
|
+
### useToast
|
|
601
|
+
|
|
602
|
+
```ts
|
|
603
|
+
const { show, success, error, warn, info, dismiss, clear, toasts } = useToast()
|
|
604
|
+
|
|
605
|
+
success(body, { id?, title?, placement?, autohide?, delay? })
|
|
606
|
+
toasts // DeepReadonly<ToastSpec[]>
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
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.
|
|
610
|
+
|
|
611
|
+
### usePosition
|
|
612
|
+
|
|
613
|
+
```ts
|
|
614
|
+
const { x, y, placement, update, stop } = usePosition(target, anchor, options)
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
`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.
|
|
618
|
+
|
|
619
|
+
The composable owns `position` / `left` / `top` on the target element while alive. `stop()` (and `onBeforeUnmount`) restore the previous inline values.
|
|
620
|
+
|
|
621
|
+
### useBreakpoints
|
|
622
|
+
|
|
623
|
+
```ts
|
|
624
|
+
const { isXs, isSm, isMd, isLg, isXl, isXxl, isMobile, isTablet } = useBreakpoints()
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
Reactive booleans driven by `window.matchMedia` against Bootstrap's standard breakpoints.
|
|
628
|
+
|
|
629
|
+
### useBackButton
|
|
630
|
+
|
|
631
|
+
Hooks Android's hardware back button (Capacitor / Cordova) into Vue components — `VibeModal` and `VibeOffcanvas` register their close handlers automatically.
|
|
632
|
+
|
|
633
|
+
---
|
|
634
|
+
|
|
635
|
+
## Directives
|
|
636
|
+
|
|
637
|
+
### v-vibe-tooltip
|
|
638
|
+
|
|
639
|
+
Inline tooltip directive registered globally by `app.use(VibeUI)`.
|
|
640
|
+
|
|
641
|
+
```vue
|
|
642
|
+
<button v-vibe-tooltip="'Save changes'">Save</button>
|
|
643
|
+
<span v-vibe-tooltip="{ title: 'Open in new tab', placement: 'right', html: true }">↗</span>
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
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`.
|
|
647
|
+
|
|
648
|
+
For component-form usage with rich slot content, use `<VibeTooltip>`.
|
|
649
|
+
|
|
650
|
+
---
|
|
651
|
+
|
|
652
|
+
## Testing
|
|
653
|
+
|
|
654
|
+
Two Vitest projects share one Vite pipeline:
|
|
655
|
+
- **`unit`** — `@vue/test-utils` + happy-dom, with **Bootstrap JS mocked** (`tests/mocks/bootstrap.ts`, aliased only in this project). Fast.
|
|
656
|
+
- **`browser`** — real headless Chromium (Playwright provider) running the **real** Bootstrap JS + Quill — the integration paths happy-dom can't reach.
|
|
657
|
+
|
|
658
|
+
```bash
|
|
659
|
+
npm run test:run # unit suite (what CI runs)
|
|
660
|
+
npm run test:browser # browser suite, real Chromium
|
|
661
|
+
npm run test:all # both projects
|
|
662
|
+
npm run test:examples # Playwright: load every examples/*.html and assert it mounts (needs CDN network)
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
So unit tests never trigger real Bootstrap JS; the browser tests deliberately do.
|
|
666
|
+
|
|
667
|
+
**Pattern for component tests:**
|
|
668
|
+
```ts
|
|
669
|
+
import { describe, it, expect } from 'vitest'
|
|
670
|
+
import { mount } from '@vue/test-utils'
|
|
671
|
+
import VibeButton from '../../src/components/VibeButton.vue'
|
|
672
|
+
|
|
673
|
+
it('applies variant class', () => {
|
|
674
|
+
const wrapper = mount(VibeButton, { props: { variant: 'danger' } })
|
|
675
|
+
expect(wrapper.classes()).toContain('btn-danger')
|
|
676
|
+
})
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
**Pattern for composable tests:**
|
|
680
|
+
- Use `_resetColorMode()` (exported from `useColorMode.ts`, not from the package index) to reset singleton state between tests.
|
|
681
|
+
- Use `vi.stubGlobal` + `try/finally` for environment mocking (e.g. SSR simulation).
|
|
682
|
+
|
|
683
|
+
---
|
|
684
|
+
|
|
685
|
+
## Build
|
|
686
|
+
|
|
687
|
+
```bash
|
|
688
|
+
npm run build # outputs to dist/ as ESM + UMD + type declarations
|
|
689
|
+
npm run dev # Vite dev server for the src/App.vue playground
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
Build output:
|
|
693
|
+
- `dist/vibeui.es.js` — ESM for bundlers
|
|
694
|
+
- `dist/vibeui.umd.js` — UMD for CDN / script tag usage
|
|
695
|
+
- `dist/vibeui.d.ts` — TypeScript declarations
|
|
696
|
+
|
|
697
|
+
Vue and Bootstrap are **external** — not bundled. Consumers provide their own. `@floating-ui/dom` is a runtime dependency (used by `usePosition`).
|
|
698
|
+
|
|
699
|
+
### Smoke testing
|
|
700
|
+
|
|
701
|
+
```bash
|
|
702
|
+
npm run smoke # rebuilds the lib + serves smoke/ on :8001
|
|
703
|
+
```
|
|
704
|
+
|
|
705
|
+
`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).
|
|
706
|
+
|
|
707
|
+
---
|
|
708
|
+
|
|
709
|
+
## Adding a New Component
|
|
710
|
+
|
|
711
|
+
1. Create `src/components/VibeFoo.vue`
|
|
712
|
+
2. Follow the pattern: `defineProps` with typed props, `computed` for class assembly, `<component :is="tag">` if the element type is configurable
|
|
713
|
+
3. Export from `src/components/index.ts` (named export + `app.component(...)` registration)
|
|
714
|
+
4. Export from `src/index.ts` (re-export via `export * from './components'` covers it automatically)
|
|
715
|
+
5. Add types to `src/types.ts` if new shared types are needed
|
|
716
|
+
6. Add tests in `tests/components/VibeFoo.test.ts`
|
|
717
|
+
7. Add docs in `docs/components/{category}/foo.md`
|
|
718
|
+
|
|
719
|
+
If the component uses Bootstrap JS:
|
|
720
|
+
- Dynamically import: `const { Foo } = await import('bootstrap')`
|
|
721
|
+
- Initialize in `onMounted`, dispose in `onBeforeUnmount`
|
|
722
|
+
- Watch functional props and call `instance.dispose()` + reinitialize on change
|
|
723
|
+
- 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`)
|
|
724
|
+
|
|
725
|
+
---
|
|
726
|
+
|
|
727
|
+
## Canvas Charts
|
|
728
|
+
|
|
729
|
+
Three chart components drawn directly on `<canvas>` — no Chart.js or other dependency.
|
|
730
|
+
|
|
731
|
+
```vue
|
|
732
|
+
<VibeChartLine :data="data" smooth fill legend="top" />
|
|
733
|
+
<VibeChartBar :data="data" stacked />
|
|
734
|
+
<VibeChartPie :data="data" />
|
|
735
|
+
```
|
|
736
|
+
|
|
737
|
+
All three share the same `ChartData` shape:
|
|
738
|
+
|
|
739
|
+
```ts
|
|
740
|
+
const data: ChartData = {
|
|
741
|
+
labels: ['Jan', 'Feb', 'Mar'],
|
|
742
|
+
datasets: [
|
|
743
|
+
{ label: 'Revenue', data: [100, 150, 130], color: '#0d6efd' } // color optional
|
|
744
|
+
]
|
|
745
|
+
}
|
|
746
|
+
```
|
|
747
|
+
|
|
748
|
+
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.
|
|
749
|
+
|
|
750
|
+
| Prop | Line | Bar | Pie | Description |
|
|
751
|
+
|------|------|-----|-----|-------------|
|
|
752
|
+
| `data` | ✓ | ✓ | ✓ | `ChartData` — required |
|
|
753
|
+
| `legend` | ✓ | ✓ | ✓ | `'top' \| 'bottom' \| 'none'` (default `'bottom'`) |
|
|
754
|
+
| `height` | ✓ | ✓ | ✓ | Number/String px or `'auto'` (default). Line/Bar → 16:9, Pie → 1:1 |
|
|
755
|
+
| `showAxes` | ✓ | ✓ | — | Show X/Y axes (default `true`) |
|
|
756
|
+
| `showGrid` | ✓ | ✓ | — | Show grid lines (default `true`) |
|
|
757
|
+
| `smooth` | ✓ | — | — | Bezier curves instead of straight lines |
|
|
758
|
+
| `fill` | ✓ | — | — | Fill area under line |
|
|
759
|
+
| `stacked` | — | ✓ | — | Stack datasets instead of grouping |
|
|
760
|
+
|
|
761
|
+
All charts are responsive via `ResizeObserver`, render at device pixel ratio for crisp HiDPI output, and show hover tooltips on data points/slices/bars.
|
|
762
|
+
|
|
763
|
+
---
|
|
764
|
+
|
|
765
|
+
## What VibeUI Does NOT Do
|
|
766
|
+
|
|
767
|
+
- Does not bundle Bootstrap CSS or JS
|
|
768
|
+
- Does not require Bootstrap Icons (optional peer dep for `VibeIcon`)
|
|
769
|
+
- Does not require Vue Router (optional peer dep for `href`/`to` props on `VibeButton`)
|
|
770
|
+
- Does not require QuillJS (optional peer dep for `VibeFormWysiwyg`)
|
|
771
|
+
- Does not provide a theme system beyond Bootstrap's `data-bs-theme` color modes
|
|
772
|
+
|
|
773
|
+
## Documentation Index
|
|
774
|
+
|
|
775
|
+
Per-component and per-API reference. **Read the relevant page before building with a component.**
|
|
776
|
+
|
|
777
|
+
**Start here:** [docs/README.md](docs/README.md) · [Getting started / starter template](docs/getting-started/starter-template.md) · [Versioning & stability policy](docs/versioning.md)
|
|
778
|
+
|
|
779
|
+
**Layout:** [VibeContainer](docs/components/layout/container.md) · [VibeRow](docs/components/layout/row.md) · [VibeCol](docs/components/layout/col.md)
|
|
780
|
+
|
|
781
|
+
**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)
|
|
782
|
+
|
|
783
|
+
**Card / Hero / List:** [VibeCard](docs/components/card/card.md) · [VibeHero](docs/components/hero/hero.md) · [VibeListGroup](docs/components/list/list-group.md)
|
|
784
|
+
|
|
785
|
+
**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)
|
|
786
|
+
|
|
787
|
+
**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)
|
|
788
|
+
|
|
789
|
+
**Advanced:** [VibePopover](docs/components/advanced/popover.md) · [VibeScrollspy](docs/components/advanced/scrollspy.md) · [VibeTooltip](docs/components/advanced/tooltip.md)
|
|
790
|
+
|
|
791
|
+
**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)
|
|
792
|
+
|
|
793
|
+
**Progress:** [VibeProgress](docs/components/progress/progress.md)
|
|
794
|
+
|
|
795
|
+
**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) · [VibeFormErrorSummary](docs/forms/form-error-summary.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)
|
|
796
|
+
|
|
797
|
+
**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)
|
|
798
|
+
|
|
799
|
+
**Directives:** [v-vibe-tooltip](docs/directives/v-tooltip.md)
|
|
800
|
+
|
|
801
|
+
**Utilities:** [Position utility classes](docs/utilities/position.md)
|