@velkymx/vibeui 0.8.2 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +44 -0
- package/README.md +245 -58
- 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 +24 -30
- 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 +16 -20
- 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/src/components/VibeDatePicker.vue.d.ts +120 -0
- package/dist/src/components/VibeDraggable.vue.d.ts +77 -0
- package/dist/{components → src/components}/VibeDropdown.vue.d.ts +46 -43
- package/dist/src/components/VibeDroppable.vue.d.ts +70 -0
- package/dist/src/components/VibeFileInput.vue.d.ts +127 -0
- package/dist/{components → src/components}/VibeFormCheckbox.vue.d.ts +30 -22
- package/dist/{components → src/components}/VibeFormDatepicker.vue.d.ts +27 -30
- package/dist/{components → src/components}/VibeFormGroup.vue.d.ts +11 -14
- package/dist/{components → src/components}/VibeFormInput.vue.d.ts +22 -23
- package/dist/{components → src/components}/VibeFormRadio.vue.d.ts +23 -24
- package/dist/{components → src/components}/VibeFormSelect.vue.d.ts +28 -34
- package/dist/{components → src/components}/VibeFormSpinbutton.vue.d.ts +31 -34
- package/dist/{components → src/components}/VibeFormSwitch.vue.d.ts +22 -23
- package/dist/{components → src/components}/VibeFormTextarea.vue.d.ts +24 -25
- package/dist/{components → src/components}/VibeFormWysiwyg.vue.d.ts +24 -23
- 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/src/components/VibeResizable.vue.d.ts +140 -0
- package/dist/{components → src/components}/VibeRow.vue.d.ts +9 -17
- package/dist/src/components/VibeScrollspy.vue.d.ts +89 -0
- package/dist/src/components/VibeSkeleton.vue.d.ts +54 -0
- package/dist/src/components/VibeSlider.vue.d.ts +77 -0
- package/dist/src/components/VibeSortable.vue.d.ts +59 -0
- package/dist/{components → src/components}/VibeSpinner.vue.d.ts +4 -7
- package/dist/src/components/VibeStepper.vue.d.ts +133 -0
- package/dist/src/components/VibeTab.vue.d.ts +41 -0
- package/dist/src/components/VibeTabContent.vue.d.ts +56 -0
- package/dist/src/components/VibeTabs.vue.d.ts +78 -0
- package/dist/{components → src/components}/VibeToast.vue.d.ts +40 -42
- package/dist/src/components/VibeToastHost.vue.d.ts +16 -0
- 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/src/components/dndStore.d.ts +7 -0
- package/dist/{components → src/components}/index.d.ts +18 -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/useForm.d.ts +22 -0
- package/dist/src/composables/useId.d.ts +2 -0
- package/dist/src/composables/usePosition.d.ts +20 -0
- package/dist/src/composables/useToast.d.ts +52 -0
- package/dist/src/directives/vTooltip.d.ts +24 -0
- package/dist/{index.d.ts → src/index.d.ts} +6 -0
- package/dist/src/injectionKeys.d.ts +28 -0
- package/dist/{types.d.ts → src/types.d.ts} +32 -6
- 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 +8351 -3508
- package/dist/vibeui.umd.js +3 -1
- package/docs/README.md +85 -13
- package/docs/components/advanced/popover.md +27 -52
- package/docs/components/advanced/scrollspy.md +57 -15
- package/docs/components/advanced/tooltip.md +26 -47
- package/docs/components/card/card.md +9 -1
- 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 +27 -5
- package/docs/components/core/badge.md +0 -6
- package/docs/components/core/button-group.md +0 -6
- package/docs/components/core/button.md +1 -1
- package/docs/components/core/close-button.md +5 -5
- package/docs/components/core/link.md +60 -17
- package/docs/components/core/placeholder.md +0 -6
- package/docs/components/core/skeleton.md +40 -0
- package/docs/components/core/spinner.md +0 -6
- package/docs/components/data/datatable.md +29 -10
- package/docs/components/hero/hero.md +64 -0
- package/docs/components/interactive/accordion.md +29 -0
- package/docs/components/interactive/carousel.md +19 -4
- package/docs/components/interactive/collapse.md +3 -2
- package/docs/components/interactive/date-picker.md +78 -0
- package/docs/components/interactive/draggable.md +91 -0
- package/docs/components/interactive/dropdown.md +54 -18
- package/docs/components/interactive/modal.md +15 -20
- package/docs/components/interactive/offcanvas.md +4 -1
- 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 +93 -30
- package/docs/components/layout/col.md +0 -6
- package/docs/components/layout/container.md +0 -6
- package/docs/components/layout/row.md +0 -6
- package/docs/components/list/list-group.md +4 -0
- package/docs/components/navigation/breadcrumb.md +4 -0
- package/docs/components/navigation/nav.md +64 -1
- package/docs/components/navigation/navbar.md +2 -0
- package/docs/components/navigation/pagination.md +16 -0
- package/docs/components/progress/progress.md +7 -1
- package/docs/composables/color-mode.md +44 -6
- 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 +60 -61
- package/docs/forms/autocomplete.md +96 -0
- package/docs/forms/file-input.md +97 -0
- package/docs/forms/form-checkbox.md +75 -25
- package/docs/forms/form-datepicker.md +58 -28
- package/docs/forms/form-group.md +79 -44
- package/docs/forms/form-input.md +88 -31
- package/docs/forms/form-radio.md +60 -36
- package/docs/forms/form-select.md +75 -31
- package/docs/forms/form-spinbutton.md +72 -33
- package/docs/forms/form-switch.md +53 -25
- package/docs/forms/form-textarea.md +60 -29
- package/docs/forms/form-wysiwyg.md +73 -38
- package/docs/forms/input-group.md +53 -34
- package/docs/forms/validation.md +135 -521
- 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 +407 -47
- package/package.json +42 -13
- 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/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/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}/useFormValidation.d.ts +0 -0
- /package/dist/{main.d.ts → src/main.d.ts} +0 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# VibeResizable
|
|
2
|
+
|
|
3
|
+
Wrap a child element with up to 8 directional handles for user-driven resizing. Useful for dashboard panels, image crops, draggable widgets.
|
|
4
|
+
|
|
5
|
+
## Props
|
|
6
|
+
|
|
7
|
+
| Prop | Type | Default | Description |
|
|
8
|
+
|------|------|---------|-------------|
|
|
9
|
+
| `handles` | `Handle[]` | `['se']` | Subset of `'n' \| 's' \| 'e' \| 'w' \| 'ne' \| 'nw' \| 'se' \| 'sw'` |
|
|
10
|
+
| `width` | `Number` | `200` | Initial width in px (use `update:width` v-model) |
|
|
11
|
+
| `height` | `Number` | `150` | Initial height in px (use `update:height` v-model) |
|
|
12
|
+
| `minWidth` | `Number` | `20` | |
|
|
13
|
+
| `maxWidth` | `Number` | `Infinity` | |
|
|
14
|
+
| `minHeight` | `Number` | `20` | |
|
|
15
|
+
| `maxHeight` | `Number` | `Infinity` | |
|
|
16
|
+
| `aspectRatio` | `Number` | `undefined` | Lock width/height ratio (e.g. `16/9`) |
|
|
17
|
+
| `grid` | `Number` | `1` | Snap step in px |
|
|
18
|
+
| `disabled` | `Boolean` | `false` | Hide handles, ignore drags |
|
|
19
|
+
|
|
20
|
+
### Events
|
|
21
|
+
|
|
22
|
+
| Event | Payload | Description |
|
|
23
|
+
|-------|---------|-------------|
|
|
24
|
+
| `update:width` | `Number` | New width (use with `v-model:width`) |
|
|
25
|
+
| `update:height` | `Number` | New height |
|
|
26
|
+
| `resizestart` | `{ width, height }` | Drag began |
|
|
27
|
+
| `resize` | `{ width, height, handle }` | Continuous during drag |
|
|
28
|
+
| `resizeend` | `{ width, height }` | Drag committed |
|
|
29
|
+
|
|
30
|
+
## Examples
|
|
31
|
+
|
|
32
|
+
### Resizable card
|
|
33
|
+
|
|
34
|
+
```vue
|
|
35
|
+
<script setup>
|
|
36
|
+
import { ref } from 'vue'
|
|
37
|
+
const w = ref(320)
|
|
38
|
+
const h = ref(200)
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<template>
|
|
42
|
+
<VibeResizable
|
|
43
|
+
v-model:width="w"
|
|
44
|
+
v-model:height="h"
|
|
45
|
+
:handles="['se', 's', 'e']"
|
|
46
|
+
:grid="10"
|
|
47
|
+
:min-width="160"
|
|
48
|
+
>
|
|
49
|
+
<div class="card h-100">
|
|
50
|
+
<div class="card-body">
|
|
51
|
+
Content reflows: {{ w }}×{{ h }}
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
</VibeResizable>
|
|
55
|
+
</template>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Layout caveat
|
|
59
|
+
|
|
60
|
+
Handles are positioned just outside the box (negative top/right/bottom/left) so they sit *on* the edge for easier grabbing. If the resizable lives inside a parent with `overflow: hidden`, or sits at the viewport edge, the handles may clip. Either lift `overflow` from the parent or wrap the resizable in extra padding.
|
|
61
|
+
|
|
62
|
+
### Aspect-locked image preview
|
|
63
|
+
|
|
64
|
+
```vue
|
|
65
|
+
<VibeResizable
|
|
66
|
+
v-model:width="w"
|
|
67
|
+
v-model:height="h"
|
|
68
|
+
:aspect-ratio="16/9"
|
|
69
|
+
:handles="['nw', 'ne', 'sw', 'se']"
|
|
70
|
+
>
|
|
71
|
+
<img :src="src" class="w-100 h-100" />
|
|
72
|
+
</VibeResizable>
|
|
73
|
+
```
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# VibeSlider
|
|
2
|
+
|
|
3
|
+
Cross-browser range slider with consistent look and full keyboard support. Single mode binds a number; range mode binds `[low, high]` with handles that cannot cross.
|
|
4
|
+
|
|
5
|
+
## Props
|
|
6
|
+
|
|
7
|
+
| Prop | Type | Default | Description |
|
|
8
|
+
|------|------|---------|-------------|
|
|
9
|
+
| `modelValue` | `Number \| [Number, Number]` | `0` | Slider value (v-model) |
|
|
10
|
+
| `min` | `Number` | `0` | |
|
|
11
|
+
| `max` | `Number` | `100` | |
|
|
12
|
+
| `step` | `Number` | `1` | Snap step |
|
|
13
|
+
| `range` | `Boolean` | `false` | Two-handle range mode |
|
|
14
|
+
| `vertical` | `Boolean` | `false` | Vertical orientation |
|
|
15
|
+
| `disabled` | `Boolean` | `false` | |
|
|
16
|
+
|
|
17
|
+
### Events
|
|
18
|
+
|
|
19
|
+
| Event | Payload | Description |
|
|
20
|
+
|-------|---------|-------------|
|
|
21
|
+
| `update:modelValue` | `Number \| [Number, Number]` | New value |
|
|
22
|
+
| `change` | Same | Alias |
|
|
23
|
+
|
|
24
|
+
## Keyboard
|
|
25
|
+
|
|
26
|
+
| Key | Action |
|
|
27
|
+
|-----|--------|
|
|
28
|
+
| `ArrowRight` / `ArrowUp` | +1 step |
|
|
29
|
+
| `ArrowLeft` / `ArrowDown` | –1 step |
|
|
30
|
+
| `Home` | Jump to `min` |
|
|
31
|
+
| `End` | Jump to `max` |
|
|
32
|
+
| `PageUp` | +10 steps |
|
|
33
|
+
| `PageDown` | –10 steps |
|
|
34
|
+
|
|
35
|
+
## Examples
|
|
36
|
+
|
|
37
|
+
### Single value
|
|
38
|
+
|
|
39
|
+
```vue
|
|
40
|
+
<VibeSlider v-model="volume" :min="0" :max="100" :step="5" />
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Range
|
|
44
|
+
|
|
45
|
+
```vue
|
|
46
|
+
<VibeSlider v-model="priceRange" range :min="0" :max="500" />
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Vertical
|
|
50
|
+
|
|
51
|
+
```vue
|
|
52
|
+
<VibeSlider v-model="value" vertical />
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Accessibility
|
|
56
|
+
|
|
57
|
+
Each handle has `role="slider"`, `aria-valuemin`, `aria-valuemax`, `aria-valuenow`, and `tabindex="0"`. Disabled state sets `aria-disabled="true"`.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# VibeSortable
|
|
2
|
+
|
|
3
|
+
Drag-and-drop reorderable list. Generic over the row type; binds an ordered array via `v-model`.
|
|
4
|
+
|
|
5
|
+
## Props
|
|
6
|
+
|
|
7
|
+
| Prop | Type | Default | Description |
|
|
8
|
+
|------|------|---------|-------------|
|
|
9
|
+
| `modelValue` | `T[]` | required | Ordered array (v-model) |
|
|
10
|
+
| `itemKey` | `String` | `undefined` | Property name on each item to use as the `:key`. When omitted, index is used as key. Set this when items have a stable unique identifier to avoid key conflicts during reorder. |
|
|
11
|
+
| `disabled` | `Boolean` | `false` | Disable dragging |
|
|
12
|
+
| `tag` | `String` | `'div'` | Container element |
|
|
13
|
+
| `itemTag` | `String` | `'div'` | Element wrapping each item |
|
|
14
|
+
|
|
15
|
+
### Events
|
|
16
|
+
|
|
17
|
+
| Event | Payload | Description |
|
|
18
|
+
|-------|---------|-------------|
|
|
19
|
+
| `update:modelValue` | `T[]` | New ordered array after a successful drop |
|
|
20
|
+
| `reorder` | `{ from, to, item }` | Convenience event for persisting moves |
|
|
21
|
+
|
|
22
|
+
### Slots
|
|
23
|
+
|
|
24
|
+
The default slot is scoped per item with `{ item, index }`.
|
|
25
|
+
|
|
26
|
+
## Examples
|
|
27
|
+
|
|
28
|
+
```vue
|
|
29
|
+
<script setup>
|
|
30
|
+
import { ref } from 'vue'
|
|
31
|
+
const tasks = ref(['Wireframes', 'Backend', 'QA', 'Launch'])
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<template>
|
|
35
|
+
<VibeSortable v-model="tasks">
|
|
36
|
+
<template #default="{ item, index }">
|
|
37
|
+
<div class="card p-2 mb-2">
|
|
38
|
+
{{ index + 1 }}. {{ item }}
|
|
39
|
+
</div>
|
|
40
|
+
</template>
|
|
41
|
+
</VibeSortable>
|
|
42
|
+
</template>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Items get `data-vibe-sortable-item` for testability and styling. The currently dragging item gets a `vibe-sortable-dragging` class.
|
|
46
|
+
|
|
47
|
+
## When to use VibeSortable vs VibeDraggable
|
|
48
|
+
|
|
49
|
+
- **VibeSortable**: a single ordered list whose only operation is reorder.
|
|
50
|
+
- **VibeDraggable + VibeDroppable**: cross-list moves, kanban boards, builders, anywhere the source and target are different containers.
|
|
51
|
+
|
|
52
|
+
> **Scope:** VibeSortable handles in-list reorder only. Cross-list moves between two `<VibeSortable>` instances are not supported — use `VibeDraggable` + `VibeDroppable` for that.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# VibeStepper
|
|
2
|
+
|
|
3
|
+
Multi-step wizard with linear / non-linear modes and per-step validation guards. Use for sprint setup, initiative gates, onboarding flows.
|
|
4
|
+
|
|
5
|
+
## Props
|
|
6
|
+
|
|
7
|
+
| Prop | Type | Default | Description |
|
|
8
|
+
|------|------|---------|-------------|
|
|
9
|
+
| `modelValue` | `Number` | `0` | Active step index (v-model) |
|
|
10
|
+
| `steps` | `StepperStep[]` | required | Step descriptors (see below) |
|
|
11
|
+
| `linear` | `Boolean` | `true` | Forbid clicking ahead of current step |
|
|
12
|
+
| `vertical` | `Boolean` | `false` | Vertical layout |
|
|
13
|
+
| `beforeNext` | `(currentIndex, 'next') => boolean \| Promise<boolean>` | `undefined` | Guard for advancing |
|
|
14
|
+
| `beforePrev` | `(currentIndex, 'prev') => boolean \| Promise<boolean>` | `undefined` | Guard for going back |
|
|
15
|
+
| `nextText` | `String` | `'Next'` | Default action button label |
|
|
16
|
+
| `prevText` | `String` | `'Back'` | |
|
|
17
|
+
| `finishText` | `String` | `'Finish'` | Replaces `nextText` on the last step |
|
|
18
|
+
|
|
19
|
+
### Step descriptor
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
interface StepperStep {
|
|
23
|
+
label: string
|
|
24
|
+
description?: string // small caption under the label
|
|
25
|
+
disabled?: boolean
|
|
26
|
+
icon?: string // reserved; use marker slot for now
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Events
|
|
31
|
+
|
|
32
|
+
| Event | Payload | Description |
|
|
33
|
+
|-------|---------|-------------|
|
|
34
|
+
| `update:modelValue` | `Number` | Active step changed |
|
|
35
|
+
| `finish` | — | Emitted when next is clicked on the last step |
|
|
36
|
+
| `component-error` | `unknown` | Emitted if a `beforeNext` / `beforePrev` guard throws |
|
|
37
|
+
|
|
38
|
+
### Slots
|
|
39
|
+
|
|
40
|
+
| Slot | Scope | Description |
|
|
41
|
+
|------|-------|-------------|
|
|
42
|
+
| `step` | `{ index, step }` | Body for the active step |
|
|
43
|
+
| `marker` | `{ index, step, active }` | Override the step's marker (default: number) |
|
|
44
|
+
| `label` | `{ index, step }` | Override the step header label |
|
|
45
|
+
| `actions` | `{ next, prev, isFirst, isLast }` | Replace default footer buttons |
|
|
46
|
+
|
|
47
|
+
## Examples
|
|
48
|
+
|
|
49
|
+
```vue
|
|
50
|
+
<script setup>
|
|
51
|
+
import { ref } from 'vue'
|
|
52
|
+
const active = ref(0)
|
|
53
|
+
const steps = [
|
|
54
|
+
{ label: 'Account', description: 'Basics' },
|
|
55
|
+
{ label: 'Profile' },
|
|
56
|
+
{ label: 'Confirm' }
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
async function validateStep(index) {
|
|
60
|
+
// call server, validate fields...
|
|
61
|
+
return true
|
|
62
|
+
}
|
|
63
|
+
</script>
|
|
64
|
+
|
|
65
|
+
<template>
|
|
66
|
+
<VibeStepper
|
|
67
|
+
v-model="active"
|
|
68
|
+
:steps="steps"
|
|
69
|
+
:before-next="validateStep"
|
|
70
|
+
@finish="submit"
|
|
71
|
+
>
|
|
72
|
+
<template #step="{ index }">
|
|
73
|
+
<component :is="forms[index]" />
|
|
74
|
+
</template>
|
|
75
|
+
</VibeStepper>
|
|
76
|
+
</template>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Non-linear (free navigation)
|
|
80
|
+
|
|
81
|
+
```vue
|
|
82
|
+
<VibeStepper :steps="steps" v-model="active" :linear="false" />
|
|
83
|
+
```
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# VibeTabs / VibeTab
|
|
2
|
+
|
|
3
|
+
Self-contained tab container with content switching. Distinct from `VibeNav` (which is a pure `.nav` rendering primitive) — `VibeTabs` manages active state and renders tab panes.
|
|
4
|
+
|
|
5
|
+
## VibeTabs Props
|
|
6
|
+
|
|
7
|
+
| Prop | Type | Default | Description |
|
|
8
|
+
|------|------|---------|-------------|
|
|
9
|
+
| `modelValue` | `String` | `undefined` | Active tab `name` (v-model). Auto-defaults to first non-disabled tab. |
|
|
10
|
+
| `variant` | `'tabs' \| 'pills' \| 'underline'` | `'tabs'` | Bootstrap nav style |
|
|
11
|
+
| `fill` | `Boolean` | `false` | Apply `.nav-fill` |
|
|
12
|
+
| `justified` | `Boolean` | `false` | Apply `.nav-justified` |
|
|
13
|
+
| `vertical` | `Boolean` | `false` | Render nav vertically alongside content |
|
|
14
|
+
| `lazy` | `Boolean` | `false` | Defer rendering of inactive tabs until first activation |
|
|
15
|
+
|
|
16
|
+
### Events
|
|
17
|
+
|
|
18
|
+
| Event | Payload | Description |
|
|
19
|
+
|-------|---------|-------------|
|
|
20
|
+
| `update:modelValue` | `String` | Emitted when active tab changes |
|
|
21
|
+
| `change` | `String` | Same payload, alias for non-v-model usage |
|
|
22
|
+
|
|
23
|
+
## VibeTab Props
|
|
24
|
+
|
|
25
|
+
| Prop | Type | Default | Description |
|
|
26
|
+
|------|------|---------|-------------|
|
|
27
|
+
| `name` | `String` | required | Stable identifier matching `modelValue` |
|
|
28
|
+
| `label` | `String` | required | Tab nav button text |
|
|
29
|
+
| `disabled` | `Boolean` | `false` | Skip activation; appears greyed out |
|
|
30
|
+
|
|
31
|
+
The default slot is the tab body.
|
|
32
|
+
|
|
33
|
+
## Examples
|
|
34
|
+
|
|
35
|
+
```vue
|
|
36
|
+
<script setup>
|
|
37
|
+
import { ref } from 'vue'
|
|
38
|
+
const active = ref('details')
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<template>
|
|
42
|
+
<VibeTabs v-model="active" variant="pills">
|
|
43
|
+
<VibeTab name="details" label="Details">
|
|
44
|
+
<p>Issue body, status, assignee...</p>
|
|
45
|
+
</VibeTab>
|
|
46
|
+
<VibeTab name="activity" label="Activity">
|
|
47
|
+
<ActivityTimeline />
|
|
48
|
+
</VibeTab>
|
|
49
|
+
<VibeTab name="links" label="Linked Issues" disabled>
|
|
50
|
+
<p>Link feature coming soon.</p>
|
|
51
|
+
</VibeTab>
|
|
52
|
+
</VibeTabs>
|
|
53
|
+
</template>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Lazy mounting for heavy panes
|
|
57
|
+
|
|
58
|
+
```vue
|
|
59
|
+
<VibeTabs v-model="active" lazy>
|
|
60
|
+
<VibeTab name="metrics" label="Metrics">
|
|
61
|
+
<ExpensiveChart />
|
|
62
|
+
</VibeTab>
|
|
63
|
+
</VibeTabs>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`<ExpensiveChart>` is not mounted until the user clicks the Metrics tab. Once activated, it stays mounted across switches so internal state isn't lost.
|
|
@@ -2,7 +2,68 @@
|
|
|
2
2
|
|
|
3
3
|
Push notifications for lightweight alerts.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
The recommended way to show toasts is the **`useToast()` service** with a single `<VibeToastHost />` mounted once near your app root. The `<VibeToast>` component is the lower-level primitive for when you need a toast bound directly to local component state.
|
|
6
|
+
|
|
7
|
+
## Toast Service (recommended)
|
|
8
|
+
|
|
9
|
+
Mount `<VibeToastHost />` once (typically in `App.vue`), then call `useToast()` anywhere to enqueue toasts. The host manages containers, positioning, and dismissal for you.
|
|
10
|
+
|
|
11
|
+
```vue
|
|
12
|
+
<!-- App.vue -->
|
|
13
|
+
<template>
|
|
14
|
+
<RouterView />
|
|
15
|
+
<VibeToastHost />
|
|
16
|
+
</template>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
```vue
|
|
20
|
+
<!-- AnyComponent.vue -->
|
|
21
|
+
<template>
|
|
22
|
+
<VibeButton @click="save">Save</VibeButton>
|
|
23
|
+
</template>
|
|
24
|
+
|
|
25
|
+
<script setup>
|
|
26
|
+
import { useToast } from '@velkymx/vibeui'
|
|
27
|
+
|
|
28
|
+
const toast = useToast()
|
|
29
|
+
|
|
30
|
+
function save() {
|
|
31
|
+
// ...persist...
|
|
32
|
+
toast.success('Saved successfully')
|
|
33
|
+
toast.error('Something went wrong', { title: 'Error' })
|
|
34
|
+
toast.info('Heads up', { delay: 8000 })
|
|
35
|
+
}
|
|
36
|
+
</script>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### `useToast()` API
|
|
40
|
+
|
|
41
|
+
| Method | Signature | Description |
|
|
42
|
+
|--------|-----------|-------------|
|
|
43
|
+
| `show` | `(body, options?) => ToastSpec` | Enqueue a toast with explicit options |
|
|
44
|
+
| `success` | `(body, options?) => ToastSpec` | Shorthand for `variant: 'success'` |
|
|
45
|
+
| `error` | `(body, options?) => ToastSpec` | Shorthand for `variant: 'danger'` |
|
|
46
|
+
| `warn` | `(body, options?) => ToastSpec` | Shorthand for `variant: 'warning'` |
|
|
47
|
+
| `info` | `(body, options?) => ToastSpec` | Shorthand for `variant: 'info'` |
|
|
48
|
+
| `dismiss` | `(id) => boolean` | Remove a toast by id; returns `true` if found |
|
|
49
|
+
| `clear` | `() => void` | Remove all queued toasts |
|
|
50
|
+
| `toasts` | `readonly ToastSpec[]` | Reactive readonly snapshot of the queue |
|
|
51
|
+
|
|
52
|
+
`ToastShowOptions`: `{ id?, title?, variant?, placement?, autohide?, delay? }`. Passing an existing `id` updates that toast in place instead of adding a new one. The variant shorthands (`success`, `error`, `warn`, `info`) accept the same options minus `variant`.
|
|
53
|
+
|
|
54
|
+
### `<VibeToastHost />` Props
|
|
55
|
+
|
|
56
|
+
| Prop | Type | Default | Description |
|
|
57
|
+
|------|------|---------|-------------|
|
|
58
|
+
| `defaultPlacement` | `ToastPlacement` | `'top-end'` | Placement used for toasts that don't specify one |
|
|
59
|
+
|
|
60
|
+
Toasts are grouped into one fixed container per placement, so toasts with different `placement` values render in the correct corner automatically.
|
|
61
|
+
|
|
62
|
+
## VibeToast Component (primitive)
|
|
63
|
+
|
|
64
|
+
Use the component directly when you want a toast tied to a local `v-model`.
|
|
65
|
+
|
|
66
|
+
### Props
|
|
6
67
|
|
|
7
68
|
| Prop | Type | Default | Description |
|
|
8
69
|
|------|------|---------|-------------|
|
|
@@ -14,8 +75,9 @@ Push notifications for lightweight alerts.
|
|
|
14
75
|
| `delay` | `Number` | `5000` | Delay in milliseconds |
|
|
15
76
|
| `teleport` | `Boolean\|String` | `'body'` | Destination for Vue Teleport |
|
|
16
77
|
| `placement` | `ToastPlacement` | `'top-end'` | Position of the toast |
|
|
78
|
+
| `noContainer` | `Boolean` | `false` | Render only the `.toast` element (no Teleport/container). Used internally by `VibeToastHost` to group toasts |
|
|
17
79
|
|
|
18
|
-
|
|
80
|
+
### Events
|
|
19
81
|
|
|
20
82
|
| Event | Payload | Description |
|
|
21
83
|
|-------|---------|-------------|
|
|
@@ -24,17 +86,16 @@ Push notifications for lightweight alerts.
|
|
|
24
86
|
| `shown` | - | Emitted when toast is fully shown |
|
|
25
87
|
| `hide` | - | Emitted when toast starts hiding |
|
|
26
88
|
| `hidden` | - | Emitted when toast is fully hidden |
|
|
89
|
+
| `component-error` | `ComponentError` | Emitted if Bootstrap JS fails to load |
|
|
27
90
|
|
|
28
|
-
|
|
91
|
+
### Slots
|
|
29
92
|
|
|
30
93
|
| Slot | Description |
|
|
31
94
|
|------|-------------|
|
|
32
95
|
| `default` | Toast body content |
|
|
33
96
|
| `header` | Custom header content |
|
|
34
97
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
### Reactive Usage (v-model)
|
|
98
|
+
### Usage
|
|
38
99
|
|
|
39
100
|
```vue
|
|
40
101
|
<template>
|
|
@@ -53,57 +114,59 @@ const showToast = ref(false)
|
|
|
53
114
|
</script>
|
|
54
115
|
```
|
|
55
116
|
|
|
56
|
-
|
|
117
|
+
#### Placed Toasts
|
|
57
118
|
|
|
58
119
|
```vue
|
|
59
120
|
<template>
|
|
60
121
|
<div>
|
|
61
|
-
<VibeToast
|
|
122
|
+
<VibeToast
|
|
123
|
+
v-model="showSuccess"
|
|
124
|
+
title="Success"
|
|
125
|
+
variant="success"
|
|
126
|
+
placement="bottom-end"
|
|
127
|
+
>
|
|
62
128
|
Operation completed successfully!
|
|
63
129
|
</VibeToast>
|
|
64
130
|
|
|
65
|
-
<VibeToast title="Center" variant="info" placement="middle-center">
|
|
131
|
+
<VibeToast v-model="showInfo" title="Center" variant="info" placement="middle-center">
|
|
66
132
|
I am in the middle of the screen
|
|
67
133
|
</VibeToast>
|
|
68
134
|
</div>
|
|
69
135
|
</template>
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
### Colored Toasts
|
|
73
|
-
|
|
74
|
-
```vue
|
|
75
|
-
<template>
|
|
76
|
-
<div>
|
|
77
|
-
<VibeToast title="Success" variant="success">
|
|
78
|
-
Operation completed successfully!
|
|
79
|
-
</VibeToast>
|
|
80
136
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
</
|
|
137
|
+
<script setup>
|
|
138
|
+
import { ref } from 'vue'
|
|
139
|
+
const showSuccess = ref(false)
|
|
140
|
+
const showInfo = ref(false)
|
|
141
|
+
</script>
|
|
86
142
|
```
|
|
87
143
|
|
|
88
|
-
|
|
144
|
+
#### Custom Delay
|
|
89
145
|
|
|
90
146
|
```vue
|
|
91
147
|
<template>
|
|
92
|
-
<VibeToast title="Quick Toast" :delay="2000">
|
|
148
|
+
<VibeToast v-model="show" title="Quick Toast" :delay="2000">
|
|
93
149
|
This will auto-hide after 2 seconds
|
|
94
150
|
</VibeToast>
|
|
95
151
|
</template>
|
|
152
|
+
|
|
153
|
+
<script setup>
|
|
154
|
+
import { ref } from 'vue'
|
|
155
|
+
const show = ref(true)
|
|
156
|
+
</script>
|
|
96
157
|
```
|
|
97
158
|
|
|
98
159
|
## Important Notes
|
|
99
160
|
|
|
100
|
-
**
|
|
161
|
+
**Prefer the service:** Use `useToast()` + `<VibeToastHost />` for most cases. It dedupes/updates by `id`, queues multiple toasts, and handles container placement. Reach for `<VibeToast>` only when you need a toast bound to a single local boolean.
|
|
162
|
+
|
|
163
|
+
**Automatic Container:** Unlike raw Bootstrap, `<VibeToast>` automatically manages its own `toast-container` and positioning based on the `placement` prop (unless `noContainer` is set).
|
|
101
164
|
|
|
102
|
-
**
|
|
165
|
+
**Teleportation:** By default, the component teleports to the `<body>` so it is always visible on top of other elements.
|
|
103
166
|
|
|
104
|
-
**
|
|
167
|
+
**SSR:** The toast queue is a module singleton. In SSR runtimes call `resetToastStoreForSSR()` in your per-request reset hook to avoid leaking one request's pending toasts into another's render.
|
|
105
168
|
|
|
106
|
-
**
|
|
169
|
+
**Escape Hatch:** `<VibeToast>` exposes `_unsafe_bsInstance` (raw Bootstrap Toast instance) via template ref. It is **not** part of the stable API — calling `dispose()` or other lifecycle methods on it directly **will** break the component. Prefer `v-model` and the exposed `show()` / `hide()` methods.
|
|
107
170
|
|
|
108
171
|
## Bootstrap CSS Classes
|
|
109
172
|
|
|
@@ -33,12 +33,6 @@ Column component for Bootstrap's grid system. Supports responsive sizing, offset
|
|
|
33
33
|
- **OrderValue:** `0 | 1 | 2 | 3 | 4 | 5 | 'first' | 'last'`
|
|
34
34
|
- **AlignItems:** `'start' | 'center' | 'end' | 'baseline' | 'stretch'`
|
|
35
35
|
|
|
36
|
-
## Events
|
|
37
|
-
|
|
38
|
-
| Event | Payload | Description |
|
|
39
|
-
|-------|---------|-------------|
|
|
40
|
-
| `component-error` | `Object` | Emitted when an error occurs |
|
|
41
|
-
|
|
42
36
|
## Slots
|
|
43
37
|
|
|
44
38
|
| Slot | Description |
|
|
@@ -13,12 +13,6 @@ Responsive container component wrapping Bootstrap's `.container`, `.container-fl
|
|
|
13
13
|
|
|
14
14
|
`'sm' | 'md' | 'lg' | 'xl' | 'xxl'`
|
|
15
15
|
|
|
16
|
-
## Events
|
|
17
|
-
|
|
18
|
-
| Event | Payload | Description |
|
|
19
|
-
|-------|---------|-------------|
|
|
20
|
-
| `component-error` | `Object` | Emitted when an error occurs |
|
|
21
|
-
|
|
22
16
|
## Slots
|
|
23
17
|
|
|
24
18
|
| Slot | Description |
|
|
@@ -41,12 +41,6 @@ Row component for Bootstrap's grid system. Provides gutters, row columns, and al
|
|
|
41
41
|
- **AlignItems:** `'start' | 'center' | 'end' | 'baseline' | 'stretch'`
|
|
42
42
|
- **JustifyContent:** `'start' | 'center' | 'end' | 'around' | 'between' | 'evenly'`
|
|
43
43
|
|
|
44
|
-
## Events
|
|
45
|
-
|
|
46
|
-
| Event | Payload | Description |
|
|
47
|
-
|-------|---------|-------------|
|
|
48
|
-
| `component-error` | `Object` | Emitted when an error occurs |
|
|
49
|
-
|
|
50
44
|
## Slots
|
|
51
45
|
|
|
52
46
|
| Slot | Description |
|
|
@@ -208,6 +208,10 @@ const handleClick = ({ item, index }) => {
|
|
|
208
208
|
</script>
|
|
209
209
|
```
|
|
210
210
|
|
|
211
|
+
## Important Notes
|
|
212
|
+
|
|
213
|
+
**`href` sanitization:** Each item's `href` is sanitized. Only `https://`/`http://` URLs, absolute paths (`/path`), relative paths (`./`, `../`), and anchors (`#section`) are allowed. Dangerous values such as `javascript:`, `data:`, `vbscript:`, and protocol-relative `//host` URLs are stripped — the item renders without an `href` (falling back to a non-link element). Use `to` for in-app navigation via Vue Router.
|
|
214
|
+
|
|
211
215
|
## Bootstrap CSS Classes
|
|
212
216
|
|
|
213
217
|
- `.list-group`
|
|
@@ -109,6 +109,10 @@ const handleClick = ({ item, index }) => {
|
|
|
109
109
|
</script>
|
|
110
110
|
```
|
|
111
111
|
|
|
112
|
+
## Important Notes
|
|
113
|
+
|
|
114
|
+
**`href` sanitization:** Item `href` values are sanitized. Only `https?://`, root-relative (`/path`), relative (`./`, `../`), and anchor (`#anchor`) URLs are allowed. `javascript:`, `data:`, and protocol-relative (`//`) URLs are stripped — such items render as plain text instead of links. Use `to` for Vue Router navigation.
|
|
115
|
+
|
|
112
116
|
## Bootstrap CSS Classes
|
|
113
117
|
|
|
114
118
|
- `.breadcrumb`
|
|
@@ -22,6 +22,8 @@ interface NavItem {
|
|
|
22
22
|
text: string
|
|
23
23
|
href?: string
|
|
24
24
|
to?: string | object
|
|
25
|
+
/** Tab panel selector (e.g. '#panel-id') for tabs/pills mode with router-link items */
|
|
26
|
+
target?: string
|
|
25
27
|
active?: boolean
|
|
26
28
|
disabled?: boolean
|
|
27
29
|
children?: NavItem[] // Support for dropdowns
|
|
@@ -46,6 +48,28 @@ interface NavItem {
|
|
|
46
48
|
|
|
47
49
|
## Usage
|
|
48
50
|
|
|
51
|
+
### Custom item content (`#item` slot)
|
|
52
|
+
|
|
53
|
+
Use the `item` slot to render richer nav links — e.g. an icon next to the label. The slot is scoped with `{ item, index }`.
|
|
54
|
+
|
|
55
|
+
```vue
|
|
56
|
+
<template>
|
|
57
|
+
<VibeNav :items="navItems">
|
|
58
|
+
<template #item="{ item }">
|
|
59
|
+
<VibeIcon :icon="item.icon" class="me-2" />
|
|
60
|
+
{{ item.text }}
|
|
61
|
+
</template>
|
|
62
|
+
</VibeNav>
|
|
63
|
+
</template>
|
|
64
|
+
|
|
65
|
+
<script setup>
|
|
66
|
+
const navItems = [
|
|
67
|
+
{ text: 'Home', href: '#', icon: 'house', active: true },
|
|
68
|
+
{ text: 'Profile', href: '#', icon: 'person' }
|
|
69
|
+
]
|
|
70
|
+
</script>
|
|
71
|
+
```
|
|
72
|
+
|
|
49
73
|
### Interactive Tabs
|
|
50
74
|
|
|
51
75
|
When using the `tabs` or `pills` props, nav links with an `href` starting with `#` will automatically act as Bootstrap tab triggers.
|
|
@@ -72,9 +96,48 @@ const tabPanes = [
|
|
|
72
96
|
</script>
|
|
73
97
|
```
|
|
74
98
|
|
|
99
|
+
### Tabs with Vue Router Links
|
|
100
|
+
|
|
101
|
+
Items using `to` (router-link) also work in tabs/pills mode. Provide a `target` field pointing to the panel ID, and Bootstrap Tab events (`show`, `shown`, `hide`, `hidden`) will fire correctly:
|
|
102
|
+
|
|
103
|
+
```vue
|
|
104
|
+
<template>
|
|
105
|
+
<VibeNav tabs :items="navItems" @shown="onShown" />
|
|
106
|
+
<div class="tab-content mt-3">
|
|
107
|
+
<div id="overview-pane" class="tab-pane fade show active">Overview content</div>
|
|
108
|
+
<div id="settings-pane" class="tab-pane fade">Settings content</div>
|
|
109
|
+
</div>
|
|
110
|
+
</template>
|
|
111
|
+
|
|
112
|
+
<script setup>
|
|
113
|
+
const navItems = [
|
|
114
|
+
{ text: 'Overview', to: '/overview', target: '#overview-pane', active: true },
|
|
115
|
+
{ text: 'Settings', to: '/settings', target: '#settings-pane' }
|
|
116
|
+
]
|
|
117
|
+
|
|
118
|
+
const onShown = (event) => {
|
|
119
|
+
console.log('Tab shown:', event.target)
|
|
120
|
+
}
|
|
121
|
+
</script>
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Items with `to` starting with `#` are also treated as tab targets without needing an explicit `target` field.
|
|
125
|
+
|
|
126
|
+
## Exposed Methods
|
|
127
|
+
|
|
128
|
+
| Method | Description |
|
|
129
|
+
|--------|-------------|
|
|
130
|
+
| `refresh()` | Tears down and re-initializes Bootstrap Tab instances. Call after dynamically changing items if needed. |
|
|
131
|
+
|
|
132
|
+
> **Escape hatch:** `_unsafe_bsInstances` (a `Map` of the underlying Bootstrap `Tab` instances) is also exposed. It is **not** part of the stable API — calling `dispose()` or other lifecycle methods on these directly WILL break the component.
|
|
133
|
+
|
|
75
134
|
## Important Notes
|
|
76
135
|
|
|
77
|
-
|
|
136
|
+
**`href` sanitization:** Item and child `href` values are sanitized. Only `https?://`, root-relative (`/path`), relative (`./`, `../`), and anchor (`#anchor`) URLs are allowed; `javascript:`, `data:`, and protocol-relative (`//`) URLs are stripped. Use `to` for Vue Router navigation.
|
|
137
|
+
|
|
138
|
+
**Disabled button items:** Items without `href` or `to` (plain button items) receive the HTML `disabled` attribute when `disabled: true` is set. Link and router-link items only get the disabled visual style — the browser attribute is omitted because `<a disabled>` has no native effect.
|
|
139
|
+
|
|
140
|
+
**Automatic Initialization:** When using `tabs` or `pills`, this component automatically initializes Bootstrap's Tab functionality for items that target a local panel ID via `href="#..."`, `target="#..."`, or `to="#..."`.
|
|
78
141
|
|
|
79
142
|
**State Management:** For complex tab state, combine `VibeNav` with `VibeTabContent` and manage the `active` state through your data.
|
|
80
143
|
|