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