@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
|
@@ -82,6 +82,8 @@ const navItems = [
|
|
|
82
82
|
|
|
83
83
|
## Important Notes
|
|
84
84
|
|
|
85
|
+
**`href` sanitization:** `VibeNavbarBrand` (and `VibeNavbarNav` items) sanitize `href` values. 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.
|
|
86
|
+
|
|
85
87
|
**Automatic Synchronization:** `VibeNavbarToggle` is refactored to ensure that clicking it updates both Vue's internal state and the underlying Bootstrap `Collapse` instance simultaneously.
|
|
86
88
|
|
|
87
89
|
**Smooth Transitions:** `VibeCollapse` now utilizes Bootstrap's JavaScript engine by default, providing smooth sliding animations when toggled via the navbar.
|
|
@@ -13,6 +13,7 @@ Data-driven pagination component with v-model support.
|
|
|
13
13
|
| `showPrevNext` | `Boolean` | `true` | Show previous/next buttons |
|
|
14
14
|
| `prevText` | `String` | `'Previous'` | Text for previous button |
|
|
15
15
|
| `nextText` | `String` | `'Next'` | Text for next button |
|
|
16
|
+
| `maxVisiblePages` | `Number` | `7` | Maximum page buttons to show before collapsing to ellipsis |
|
|
16
17
|
|
|
17
18
|
## Events
|
|
18
19
|
|
|
@@ -88,6 +89,21 @@ const currentPage = ref(1)
|
|
|
88
89
|
</template>
|
|
89
90
|
```
|
|
90
91
|
|
|
92
|
+
### Large Page Counts (Ellipsis)
|
|
93
|
+
|
|
94
|
+
When `totalPages` exceeds `maxVisiblePages`, intermediate pages are replaced with ellipsis (`…`). First and last pages are always shown, and the window of visible pages tracks the current page.
|
|
95
|
+
|
|
96
|
+
```vue
|
|
97
|
+
<template>
|
|
98
|
+
<!-- 100 pages, only 7 buttons max — ellipsis collapses the rest -->
|
|
99
|
+
<VibePagination
|
|
100
|
+
:total-pages="100"
|
|
101
|
+
v-model:current-page="currentPage"
|
|
102
|
+
:max-visible-pages="7"
|
|
103
|
+
/>
|
|
104
|
+
</template>
|
|
105
|
+
```
|
|
106
|
+
|
|
91
107
|
### Custom Page Rendering
|
|
92
108
|
|
|
93
109
|
Use scoped slots for complete customization:
|
|
@@ -6,7 +6,7 @@ Data-driven progress bar component supporting single or multiple bars.
|
|
|
6
6
|
|
|
7
7
|
| Prop | Type | Default | Description |
|
|
8
8
|
|------|------|---------|-------------|
|
|
9
|
-
| `height` | `String` | `undefined` | Custom height (e.g., `'20px'`, `'2rem'`) |
|
|
9
|
+
| `height` | `String` | `undefined` | Custom height (e.g., `'20px'`, `'2rem'`). Validated as a CSS length; invalid values are ignored |
|
|
10
10
|
| `bars` | `ProgressBar[]` | Required | Array of progress bars to display |
|
|
11
11
|
|
|
12
12
|
### ProgressBar Interface
|
|
@@ -173,6 +173,12 @@ const tasks = ref([
|
|
|
173
173
|
</template>
|
|
174
174
|
```
|
|
175
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
|
+
|
|
176
182
|
## Bootstrap CSS Classes
|
|
177
183
|
|
|
178
184
|
- `.progress`
|
|
@@ -17,8 +17,9 @@ import { useColorMode } from '@velkymx/vibeui'
|
|
|
17
17
|
| `colorMode` | `Ref<ColorMode>` | Reactive current mode. Bind to template or watch for changes. |
|
|
18
18
|
| `setColorMode(mode)` | `(mode: ColorMode) => void` | Set a specific mode. Persists to `localStorage` and updates `<html data-bs-theme>`. |
|
|
19
19
|
| `toggleColorMode()` | `() => void` | Cycle through `light → dark → auto → light`. |
|
|
20
|
-
| `initColorMode()` | `() => void` | Restore the saved preference from `localStorage
|
|
21
|
-
| `
|
|
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. |
|
|
22
23
|
|
|
23
24
|
## Type
|
|
24
25
|
|
|
@@ -92,7 +93,8 @@ const options: { label: string; value: ColorMode }[] = [
|
|
|
92
93
|
<VibeButton
|
|
93
94
|
v-for="option in options"
|
|
94
95
|
:key="option.value"
|
|
95
|
-
:variant="colorMode === option.value ? 'primary' : '
|
|
96
|
+
:variant="colorMode === option.value ? 'primary' : 'secondary'"
|
|
97
|
+
:outline="colorMode !== option.value"
|
|
96
98
|
@click="setColorMode(option.value)"
|
|
97
99
|
>
|
|
98
100
|
{{ option.label }}
|
|
@@ -119,6 +121,43 @@ watch(colorMode, (mode) => {
|
|
|
119
121
|
</script>
|
|
120
122
|
```
|
|
121
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
|
+
|
|
122
161
|
### Scoping color mode to a single component
|
|
123
162
|
|
|
124
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.
|
|
@@ -135,7 +174,6 @@ Bootstrap's `data-bs-theme` attribute works on any element, not just `<html>`. F
|
|
|
135
174
|
|
|
136
175
|
- **Persistence** — the selected mode is stored in `localStorage` under the key `vibe-color-mode`. It survives page reloads.
|
|
137
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.
|
|
138
|
-
- **SSR-safe** — all `document` and `localStorage` access is guarded. The composable is safe to import in server-rendered environments;
|
|
139
|
-
...
|
|
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.
|
|
140
178
|
- **Invalid values** — `setColorMode` silently ignores any value that is not `'light'`, `'dark'`, or `'auto'`. No error is thrown.
|
|
141
|
-
- **`initColorMode` is idempotent** —
|
|
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
|
+
```
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# v-vibe-tooltip
|
|
2
|
+
|
|
3
|
+
Inline tooltip directive. Equivalent to `<VibeTooltip>` but applied directly to the element, without a wrapper. Useful when adding tooltips to existing markup or many small elements (icons, button labels) where wrapping each is noisy.
|
|
4
|
+
|
|
5
|
+
Registered globally by `app.use(VibeUI)`.
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
```vue
|
|
10
|
+
<template>
|
|
11
|
+
<!-- String binding: tooltip title -->
|
|
12
|
+
<button v-vibe-tooltip="'Save changes'">
|
|
13
|
+
<VibeIcon name="save" />
|
|
14
|
+
</button>
|
|
15
|
+
|
|
16
|
+
<!-- Options object -->
|
|
17
|
+
<span v-vibe-tooltip="{ title: 'New tab', placement: 'right' }">
|
|
18
|
+
<VibeIcon name="external-link" />
|
|
19
|
+
</span>
|
|
20
|
+
</template>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Binding values
|
|
24
|
+
|
|
25
|
+
| Type | Behavior |
|
|
26
|
+
|------|----------|
|
|
27
|
+
| `string` | Sets the tooltip `title` |
|
|
28
|
+
| `{ title, placement, trigger }` | Full options |
|
|
29
|
+
| `undefined` | Renders nothing useful, but does not throw |
|
|
30
|
+
|
|
31
|
+
### Options
|
|
32
|
+
|
|
33
|
+
| Option | Type | Default | Description |
|
|
34
|
+
|--------|------|---------|-------------|
|
|
35
|
+
| `title` | `string` | — | Tooltip body. Aliases: `text`, `content` (whichever is set first wins) |
|
|
36
|
+
| `placement` | `TooltipPlacement` | `'top'` | Bootstrap placement |
|
|
37
|
+
| `trigger` | `string` | `'hover focus'` | Auto-swaps to `'click'` on touch devices |
|
|
38
|
+
|
|
39
|
+
> Tooltip content is always rendered as plain text (`html: false`). HTML in `title` is escaped; there is no `html` option on this directive.
|
|
40
|
+
|
|
41
|
+
## Direct import
|
|
42
|
+
|
|
43
|
+
For component-local registration without the plugin:
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import { vTooltip } from '@velkymx/vibeui'
|
|
47
|
+
|
|
48
|
+
export default {
|
|
49
|
+
directives: { 'vibe-tooltip': vTooltip }
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## When to use the component instead
|
|
54
|
+
|
|
55
|
+
`<VibeTooltip>` is better when:
|
|
56
|
+
- You want the tooltip body to come from a slot (rich content rather than a string).
|
|
57
|
+
- The triggering element is itself a slot of another component and you can't access it to bind a directive.
|
|
58
|
+
- You need imperative show/hide via the component's escape-hatch instance (`_unsafe_bsInstance`).
|
package/docs/forms/README.md
CHANGED
|
@@ -1,88 +1,87 @@
|
|
|
1
1
|
# VibeUI Form Components
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Form controls with built-in validation, accessibility, and Bootstrap 5.3 styling.
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
7
|
-
VibeUI
|
|
7
|
+
VibeUI form controls integrate with Bootstrap and include validation and accessibility out of the box.
|
|
8
8
|
|
|
9
9
|
### Features
|
|
10
10
|
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
- ✅ **TypeScript** - Fully typed components and APIs
|
|
11
|
+
- **Bootstrap 5.3 styling** via native classes
|
|
12
|
+
- **Zero-boilerplate ids** — `id` props are optional and auto-generated
|
|
13
|
+
- **Accessible by default** — `VibeFormGroup` auto-links labels, help text, and feedback to its first child control
|
|
14
|
+
- **Two-way binding** with `v-model` on every control
|
|
15
|
+
- **Validation** — `useFormValidation`, `useForm`, and built-in `validators`
|
|
16
|
+
- **Fully typed** props, events, and APIs
|
|
18
17
|
|
|
19
|
-
##
|
|
18
|
+
## Recommended pattern: VibeFormGroup
|
|
19
|
+
|
|
20
|
+
Wrap a control in `VibeFormGroup` to auto-generate the id and wire the `<label>`, `aria-describedby`, help text, and validation feedback. The first child control consumes the group id (single-consumer rule).
|
|
21
|
+
|
|
22
|
+
```vue
|
|
23
|
+
<script setup lang="ts">
|
|
24
|
+
import { ref } from 'vue'
|
|
25
|
+
const email = ref('')
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
<template>
|
|
29
|
+
<VibeFormGroup label="Email Address" help-text="We'll never share it.">
|
|
30
|
+
<VibeFormInput v-model="email" type="email" placeholder="Enter email" />
|
|
31
|
+
</VibeFormGroup>
|
|
32
|
+
</template>
|
|
33
|
+
```
|
|
20
34
|
|
|
21
|
-
|
|
35
|
+
## Components
|
|
22
36
|
|
|
23
|
-
|
|
24
|
-
|-----------|-------------|---------------|
|
|
25
|
-
| **VibeFormInput** | Text, email, password, number, and other input types | [Docs](./form-input.md) |
|
|
26
|
-
| **VibeInputGroup** | Input group for prepending/appending text or buttons | [Docs](./input-group.md) |
|
|
27
|
-
| **VibeFormTextarea** | Multi-line text input | [Docs](./form-textarea.md) |
|
|
28
|
-
| **VibeFormSelect** | Single and multiple selection dropdowns | [Docs](./form-select.md) |
|
|
29
|
-
| **VibeFormSpinbutton** | Number input with increment/decrement buttons | [Docs](./form-spinbutton.md) |
|
|
30
|
-
| **VibeFormDatepicker** | Date, time, and datetime inputs | [Docs](./form-datepicker.md) |
|
|
37
|
+
### Inputs
|
|
31
38
|
|
|
32
|
-
|
|
39
|
+
| Component | Description | Docs |
|
|
40
|
+
|-----------|-------------|------|
|
|
41
|
+
| VibeFormInput | Text, email, password, number, and other input types | [form-input.md](./form-input.md) |
|
|
42
|
+
| VibeInputGroup | Prepend/append text, buttons, or elements | [input-group.md](./input-group.md) |
|
|
43
|
+
| VibeFormTextarea | Multi-line text with optional char count | [form-textarea.md](./form-textarea.md) |
|
|
44
|
+
| VibeFormSelect | Single and multiple selection | [form-select.md](./form-select.md) |
|
|
45
|
+
| VibeFormSpinbutton | Number input with increment/decrement | [form-spinbutton.md](./form-spinbutton.md) |
|
|
46
|
+
| VibeFormDatepicker | Date, time, and datetime inputs | [form-datepicker.md](./form-datepicker.md) |
|
|
47
|
+
| VibeAutocomplete | Search-as-you-type with array/async source | [autocomplete.md](./autocomplete.md) |
|
|
48
|
+
| VibeFileInput | File picker with optional drag-and-drop | [file-input.md](./file-input.md) |
|
|
33
49
|
|
|
34
|
-
|
|
35
|
-
|-----------|-------------|---------------|
|
|
36
|
-
| **VibeFormCheckbox** | Single checkboxes and checkbox groups | [Docs](./form-checkbox.md) |
|
|
37
|
-
| **VibeFormRadio** | Radio button groups for exclusive selection | [Docs](./form-radio.md) |
|
|
38
|
-
| **VibeFormSwitch** | Toggle switches for boolean settings | [Docs](./form-switch.md) |
|
|
50
|
+
### Choice controls
|
|
39
51
|
|
|
40
|
-
|
|
52
|
+
| Component | Description | Docs |
|
|
53
|
+
|-----------|-------------|------|
|
|
54
|
+
| VibeFormCheckbox | Single checkbox or checkbox group | [form-checkbox.md](./form-checkbox.md) |
|
|
55
|
+
| VibeFormRadio | Radio button groups | [form-radio.md](./form-radio.md) |
|
|
56
|
+
| VibeFormSwitch | Toggle switch | [form-switch.md](./form-switch.md) |
|
|
41
57
|
|
|
42
|
-
|
|
43
|
-
|-----------|-------------|---------------|
|
|
44
|
-
| **VibeFormWysiwyg** | WYSIWYG editor powered by QuillJS | [Docs](./form-wysiwyg.md) |
|
|
45
|
-
| **VibeFormGroup** | Form group container for layout and automated label linking | [Docs](./form-group.md) |
|
|
58
|
+
### Advanced
|
|
46
59
|
|
|
47
|
-
|
|
60
|
+
| Component | Description | Docs |
|
|
61
|
+
|-----------|-------------|------|
|
|
62
|
+
| VibeFormWysiwyg | Rich-text editor (requires `quill` + `dompurify`) | [form-wysiwyg.md](./form-wysiwyg.md) |
|
|
63
|
+
| VibeFormGroup | Layout + automatic label/id/feedback linking | [form-group.md](./form-group.md) |
|
|
48
64
|
|
|
49
|
-
|
|
65
|
+
## Validation
|
|
50
66
|
|
|
51
|
-
|
|
67
|
+
See the [Validation Guide](./validation.md) for `useFormValidation`, `useForm`, the built-in `validators`, and async/API validation.
|
|
52
68
|
|
|
53
69
|
```vue
|
|
54
70
|
<script setup lang="ts">
|
|
55
|
-
import {
|
|
56
|
-
|
|
71
|
+
import { useFormValidation, validators } from '@velkymx/vibeui'
|
|
72
|
+
|
|
73
|
+
const email = useFormValidation('')
|
|
74
|
+
const validateEmail = () =>
|
|
75
|
+
email.validate([validators.required(), validators.email()])
|
|
57
76
|
</script>
|
|
58
77
|
|
|
59
78
|
<template>
|
|
60
|
-
<VibeFormGroup
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
/>
|
|
79
|
+
<VibeFormGroup
|
|
80
|
+
label="Email"
|
|
81
|
+
:validation-state="email.validationState"
|
|
82
|
+
:validation-message="email.validationMessage"
|
|
83
|
+
>
|
|
84
|
+
<VibeFormInput v-model="email.value" type="email" @validate="validateEmail" />
|
|
66
85
|
</VibeFormGroup>
|
|
67
86
|
</template>
|
|
68
87
|
```
|
|
69
|
-
|
|
70
|
-
## Documentation
|
|
71
|
-
|
|
72
|
-
### Component Documentation
|
|
73
|
-
|
|
74
|
-
- [VibeFormInput](./form-input.md) - Text inputs with validation
|
|
75
|
-
- [VibeInputGroup](./input-group.md) - Prepend/append groups
|
|
76
|
-
- [VibeFormSelect](./form-select.md) - Select dropdowns
|
|
77
|
-
- [VibeFormTextarea](./form-textarea.md) - Multi-line text input
|
|
78
|
-
- [VibeFormSpinbutton](./form-spinbutton.md) - Number input with buttons
|
|
79
|
-
- [VibeFormDatepicker](./form-datepicker.md) - Date and time inputs
|
|
80
|
-
- [VibeFormCheckbox](./form-checkbox.md) - Checkboxes and groups
|
|
81
|
-
- [VibeFormRadio](./form-radio.md) - Radio button groups
|
|
82
|
-
- [VibeFormSwitch](./form-switch.md) - Toggle switches
|
|
83
|
-
- [VibeFormGroup](./form-group.md) - Form group container
|
|
84
|
-
- [VibeFormWysiwyg](./form-wysiwyg.md) - WYSIWYG editor
|
|
85
|
-
|
|
86
|
-
### Guides
|
|
87
|
-
|
|
88
|
-
- [Validation Guide](./validation.md) - Comprehensive validation documentation
|