@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,70 @@
|
|
|
1
|
+
# VibeInputGroup
|
|
2
|
+
|
|
3
|
+
Wraps a control with prepended/appended text, buttons, or other elements using Bootstrap's input-group.
|
|
4
|
+
|
|
5
|
+
## Props
|
|
6
|
+
|
|
7
|
+
| Prop | Type | Default | Description |
|
|
8
|
+
|------|------|---------|-------------|
|
|
9
|
+
| `size` | `'sm' \| 'lg'` | `undefined` | Input-group size. |
|
|
10
|
+
| `prepend` | `string` | `undefined` | Convenience text rendered before the control as `.input-group-text`. |
|
|
11
|
+
| `append` | `string` | `undefined` | Convenience text rendered after the control as `.input-group-text`. |
|
|
12
|
+
| `tag` | `Tag` | `'div'` | Root element tag. |
|
|
13
|
+
|
|
14
|
+
## Events
|
|
15
|
+
|
|
16
|
+
None.
|
|
17
|
+
|
|
18
|
+
## Slots
|
|
19
|
+
|
|
20
|
+
| Slot | Description |
|
|
21
|
+
|------|-------------|
|
|
22
|
+
| default | The control to wrap (typically a `VibeFormInput` with `no-wrapper`). |
|
|
23
|
+
| `prepend` | Custom prepended content (overrides the `prepend` prop). |
|
|
24
|
+
| `append` | Custom appended content (overrides the `append` prop). |
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
### Text prepend / append
|
|
29
|
+
|
|
30
|
+
```vue
|
|
31
|
+
<script setup lang="ts">
|
|
32
|
+
import { ref } from 'vue'
|
|
33
|
+
const price = ref('')
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
<template>
|
|
37
|
+
<VibeInputGroup prepend="$" append=".00">
|
|
38
|
+
<VibeFormInput v-model="price" type="number" no-wrapper />
|
|
39
|
+
</VibeInputGroup>
|
|
40
|
+
</template>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Custom slots with a button
|
|
44
|
+
|
|
45
|
+
```vue
|
|
46
|
+
<script setup lang="ts">
|
|
47
|
+
import { ref } from 'vue'
|
|
48
|
+
const term = ref('')
|
|
49
|
+
</script>
|
|
50
|
+
|
|
51
|
+
<template>
|
|
52
|
+
<VibeInputGroup>
|
|
53
|
+
<VibeFormInput v-model="term" placeholder="Search" no-wrapper />
|
|
54
|
+
<template #append>
|
|
55
|
+
<VibeButton variant="primary">Go</VibeButton>
|
|
56
|
+
</template>
|
|
57
|
+
</VibeInputGroup>
|
|
58
|
+
</template>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Important Notes
|
|
62
|
+
|
|
63
|
+
- **Use `no-wrapper` on the input:** pass `no-wrapper` to `VibeFormInput` so it renders just the `<input>` and Bootstrap can style it as part of the group.
|
|
64
|
+
- **Slots override props:** providing the `prepend`/`append` slots replaces the corresponding text props.
|
|
65
|
+
|
|
66
|
+
## Bootstrap CSS Classes
|
|
67
|
+
|
|
68
|
+
- `.input-group`
|
|
69
|
+
- `.input-group-{sm|lg}`
|
|
70
|
+
- `.input-group-text`
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# Form Validation
|
|
2
|
+
|
|
3
|
+
VibeUI ships a validation system for all form components, supporting synchronous and asynchronous rules. Use it with the `useFormValidation` composable (single field), the `useForm` composable (whole form), or by passing rule results directly.
|
|
4
|
+
|
|
5
|
+
## Built-in Validators
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { validators } from '@velkymx/vibeui'
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Each validator returns a `ValidationRule`. A validator function returns `true` for valid, `false` to use the rule's `message`, or a non-empty `string` as the error message. Most validators skip empty values — pair them with `required()` to make a field mandatory.
|
|
12
|
+
|
|
13
|
+
| Validator | Signature | Description |
|
|
14
|
+
|-----------|-----------|-------------|
|
|
15
|
+
| `required` | `(message?)` | Non-empty value (also checks array length). |
|
|
16
|
+
| `email` | `(message?)` | Valid email format. |
|
|
17
|
+
| `minLength` | `(min, message?)` | String length ≥ `min`. |
|
|
18
|
+
| `maxLength` | `(max, message?)` | String length ≤ `max`. |
|
|
19
|
+
| `min` | `(min, message?)` | Numeric value ≥ `min`. |
|
|
20
|
+
| `max` | `(max, message?)` | Numeric value ≤ `max`. |
|
|
21
|
+
| `pattern` | `(regex, message?)` | Matches the regex. |
|
|
22
|
+
| `url` | `(message?)` | Parses as a valid URL. |
|
|
23
|
+
| `async` | `(fn)` | Wraps an async validator `(value) => Promise<boolean \| string>`. |
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
const rules = [
|
|
27
|
+
validators.required('Email is required'),
|
|
28
|
+
validators.email()
|
|
29
|
+
]
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Custom Rules
|
|
33
|
+
|
|
34
|
+
A rule is `{ validator, message? }`. The validator may be sync or async.
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
const passwordStrength = {
|
|
38
|
+
validator: (value: unknown) => {
|
|
39
|
+
const v = String(value ?? '')
|
|
40
|
+
if (!v) return true
|
|
41
|
+
if (!/[A-Z]/.test(v)) return 'Needs an uppercase letter'
|
|
42
|
+
if (!/\d/.test(v)) return 'Needs a number'
|
|
43
|
+
return true
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const rules = [validators.required(), validators.minLength(8), passwordStrength]
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Single-field validation: `useFormValidation`
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
import { useFormValidation } from '@velkymx/vibeui'
|
|
54
|
+
|
|
55
|
+
const field = useFormValidation('') // initial value
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
|
|
60
|
+
| Member | Type | Description |
|
|
61
|
+
|--------|------|-------------|
|
|
62
|
+
| `value` | `Ref` | The field value (bind with `v-model="field.value"`). |
|
|
63
|
+
| `validationState` | `Ref<'valid' \| 'invalid' \| null>` | Current state. |
|
|
64
|
+
| `validationMessage` | `Ref<string>` | Current message. |
|
|
65
|
+
| `isDirty` | `Ref<boolean>` | Set via `markAsDirty()`. |
|
|
66
|
+
| `isTouched` | `Ref<boolean>` | Set via `markAsTouched()`. |
|
|
67
|
+
| `isValidating` | `Ref<boolean>` | True while an async rule is running. |
|
|
68
|
+
| `isValid` | `ComputedRef<boolean>` | `validationState === 'valid'`. |
|
|
69
|
+
| `isInvalid` | `ComputedRef<boolean>` | `validationState === 'invalid'`. |
|
|
70
|
+
| `validate(rules)` | `(rules) => Promise<FormValidationResult>` | Runs rules; concurrency-safe (only the latest call wins). |
|
|
71
|
+
| `reset()` | `() => void` | Reset to initial value and clear state. |
|
|
72
|
+
| `markAsTouched()` / `markAsDirty()` | `() => void` | Manual flag setters. |
|
|
73
|
+
|
|
74
|
+
```vue
|
|
75
|
+
<script setup lang="ts">
|
|
76
|
+
import { useFormValidation, validators } from '@velkymx/vibeui'
|
|
77
|
+
|
|
78
|
+
const email = useFormValidation('')
|
|
79
|
+
const validateEmail = () =>
|
|
80
|
+
email.validate([validators.required(), validators.email()])
|
|
81
|
+
</script>
|
|
82
|
+
|
|
83
|
+
<template>
|
|
84
|
+
<VibeFormGroup
|
|
85
|
+
label="Email"
|
|
86
|
+
:validation-state="email.validationState"
|
|
87
|
+
:validation-message="email.validationMessage"
|
|
88
|
+
>
|
|
89
|
+
<VibeFormInput v-model="email.value" type="email" @validate="validateEmail" />
|
|
90
|
+
</VibeFormGroup>
|
|
91
|
+
</template>
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Editing an invalid field clears its `invalid` state automatically until you re-validate.
|
|
95
|
+
|
|
96
|
+
## Whole-form validation: `useForm`
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
import { useForm } from '@velkymx/vibeui'
|
|
100
|
+
|
|
101
|
+
const form = useForm({ username: '', email: '', age: 0 })
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
|
|
106
|
+
| Member | Type | Description |
|
|
107
|
+
|--------|------|-------------|
|
|
108
|
+
| `fields` | reactive `T` | Form values; bind with `v-model="form.fields.x"`. |
|
|
109
|
+
| `errors` | reactive map | Per-field error messages (`''` when valid). |
|
|
110
|
+
| `touched` | reactive map | Per-field touched flags. |
|
|
111
|
+
| `isDirty` | `ComputedRef<boolean>` | True once any field differs from its initial value. |
|
|
112
|
+
| `isValid` | `ComputedRef<boolean>` | **`false` until the first `validate()`/`validateField()` call**, then reflects whether all errors are empty. |
|
|
113
|
+
| `values` | `ComputedRef<T>` | Snapshot copy of `fields`. |
|
|
114
|
+
| `validate(rules)` | `(rules) => Promise<{ valid, errors }>` | Validate the listed fields. |
|
|
115
|
+
| `validateField(key, rule)` | `(key, rule) => Promise<string>` | Validate one field; returns its message. |
|
|
116
|
+
| `reset()` | `() => void` | Restore initial values and clear errors/touched/validated state. |
|
|
117
|
+
| `markTouched(key)` / `markAllTouched()` | — | Touched flag setters. |
|
|
118
|
+
| `setField(key, value)` | — | Programmatically set a field. |
|
|
119
|
+
|
|
120
|
+
```vue
|
|
121
|
+
<script setup lang="ts">
|
|
122
|
+
import { useForm, validators } from '@velkymx/vibeui'
|
|
123
|
+
|
|
124
|
+
const form = useForm({ username: '', email: '', password: '' })
|
|
125
|
+
|
|
126
|
+
const rules = {
|
|
127
|
+
username: [validators.required(), validators.minLength(3)],
|
|
128
|
+
email: [validators.required(), validators.email()],
|
|
129
|
+
password: [validators.required(), validators.minLength(8)]
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const onSubmit = async () => {
|
|
133
|
+
const { valid } = await form.validate(rules)
|
|
134
|
+
if (valid) console.log('submit', form.values.value)
|
|
135
|
+
}
|
|
136
|
+
</script>
|
|
137
|
+
|
|
138
|
+
<template>
|
|
139
|
+
<form @submit.prevent="onSubmit">
|
|
140
|
+
<VibeFormGroup label="Username" :validation-message="form.errors.username"
|
|
141
|
+
:validation-state="form.errors.username ? 'invalid' : null">
|
|
142
|
+
<VibeFormInput v-model="form.fields.username"
|
|
143
|
+
@validate="form.validateField('username', rules.username)" />
|
|
144
|
+
</VibeFormGroup>
|
|
145
|
+
|
|
146
|
+
<VibeFormGroup label="Email" :validation-message="form.errors.email"
|
|
147
|
+
:validation-state="form.errors.email ? 'invalid' : null">
|
|
148
|
+
<VibeFormInput v-model="form.fields.email" type="email"
|
|
149
|
+
@validate="form.validateField('email', rules.email)" />
|
|
150
|
+
</VibeFormGroup>
|
|
151
|
+
|
|
152
|
+
<VibeFormGroup label="Password" :validation-message="form.errors.password"
|
|
153
|
+
:validation-state="form.errors.password ? 'invalid' : null">
|
|
154
|
+
<VibeFormInput v-model="form.fields.password" type="password"
|
|
155
|
+
@validate="form.validateField('password', rules.password)" />
|
|
156
|
+
</VibeFormGroup>
|
|
157
|
+
|
|
158
|
+
<VibeButton type="submit" variant="primary" :disabled="!form.isValid">
|
|
159
|
+
Register
|
|
160
|
+
</VibeButton>
|
|
161
|
+
</form>
|
|
162
|
+
</template>
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## When validation runs: `validateOn`
|
|
166
|
+
|
|
167
|
+
Form controls emit `validate` based on `validateOn`:
|
|
168
|
+
|
|
169
|
+
- Text-like controls (input, textarea, datepicker, spinbutton, wysiwyg): `'input' \| 'blur' \| 'change'`, default `'blur'`.
|
|
170
|
+
- Choice controls (checkbox, radio, switch, select): `'change' \| 'blur'`, default `'change'`.
|
|
171
|
+
|
|
172
|
+
Wire `@validate` to your validation call:
|
|
173
|
+
|
|
174
|
+
```vue
|
|
175
|
+
<VibeFormInput v-model="field.value" validate-on="input" @validate="run" />
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Async / API Validation
|
|
179
|
+
|
|
180
|
+
`validators.async` runs an async check (e.g. uniqueness). Because `useFormValidation.validate` is concurrency-safe, only the latest call applies its result.
|
|
181
|
+
|
|
182
|
+
```vue
|
|
183
|
+
<script setup lang="ts">
|
|
184
|
+
import { useFormValidation, validators } from '@velkymx/vibeui'
|
|
185
|
+
|
|
186
|
+
const username = useFormValidation('')
|
|
187
|
+
|
|
188
|
+
const isAvailable = validators.async(async (value) => {
|
|
189
|
+
const res = await fetch(`/api/check-username?u=${encodeURIComponent(String(value))}`)
|
|
190
|
+
const data = await res.json()
|
|
191
|
+
return data.available || 'Username is already taken'
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
const validateUsername = () =>
|
|
195
|
+
username.validate([validators.required(), validators.minLength(3), isAvailable])
|
|
196
|
+
</script>
|
|
197
|
+
|
|
198
|
+
<template>
|
|
199
|
+
<VibeFormGroup
|
|
200
|
+
label="Username"
|
|
201
|
+
:validation-state="username.validationState"
|
|
202
|
+
:validation-message="username.validationMessage"
|
|
203
|
+
>
|
|
204
|
+
<VibeFormInput v-model="username.value" @validate="validateUsername" />
|
|
205
|
+
</VibeFormGroup>
|
|
206
|
+
</template>
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Notes
|
|
210
|
+
|
|
211
|
+
- **`useForm.isValid` starts `false`** and only becomes meaningful after the first validation runs — safe to use for disabling a submit button before any input.
|
|
212
|
+
- **Editing clears `invalid`** (in `useFormValidation`) so messages don't linger while the user fixes the field.
|
|
213
|
+
- **Empty-value skipping:** most validators pass on empty values; add `required()` to enforce presence.
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# Starter Template
|
|
2
|
+
|
|
3
|
+
A complete, copy-pasteable minimal VibeUI app showing the recommended setup and patterns. Everything below runs as-is — no placeholders.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @velkymx/vibeui bootstrap
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
### Optional peer dependencies
|
|
12
|
+
|
|
13
|
+
Install these only if you use the components that need them:
|
|
14
|
+
|
|
15
|
+
- `bootstrap-icons` — required by `VibeIcon`.
|
|
16
|
+
- `quill` + `dompurify` — required by `VibeFormWysiwyg`.
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install bootstrap-icons # for VibeIcon
|
|
20
|
+
npm install quill dompurify # for VibeFormWysiwyg
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## `main.ts`
|
|
24
|
+
|
|
25
|
+
Register the plugin, import Bootstrap's CSS, and restore the saved color-mode preference **before** mounting so the theme is correct on first paint.
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { createApp } from 'vue'
|
|
29
|
+
import VibeUI, { useColorMode } from '@velkymx/vibeui'
|
|
30
|
+
import 'bootstrap/dist/css/bootstrap.min.css'
|
|
31
|
+
import App from './App.vue'
|
|
32
|
+
|
|
33
|
+
// Restore the saved light/dark/auto preference and attach the OS-theme listener.
|
|
34
|
+
useColorMode().initColorMode()
|
|
35
|
+
|
|
36
|
+
createApp(App).use(VibeUI).mount('#app')
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
> **Do NOT import Bootstrap JS — VibeUI manages it.** Importing `bootstrap` (the JS bundle) yourself can create duplicate component instances and event conflicts. VibeUI loads and disposes Bootstrap's JS internally. You only import Bootstrap's **CSS**.
|
|
40
|
+
|
|
41
|
+
## `App.vue`
|
|
42
|
+
|
|
43
|
+
Mount `<VibeToastHost />` once at the root so `useToast()` works anywhere. The example below wires `useForm` validation into a `<VibeFormGroup>` + `<VibeFormInput>`, shows a confirmation in a `<VibeModal v-model>`, and fires a toast on submit.
|
|
44
|
+
|
|
45
|
+
```vue
|
|
46
|
+
<script setup lang="ts">
|
|
47
|
+
import { ref } from 'vue'
|
|
48
|
+
import { useForm, useToast, validators } from '@velkymx/vibeui'
|
|
49
|
+
|
|
50
|
+
const toast = useToast()
|
|
51
|
+
|
|
52
|
+
const { fields, errors, validate, reset } = useForm({
|
|
53
|
+
name: '',
|
|
54
|
+
email: '',
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
const showConfirm = ref(false)
|
|
58
|
+
|
|
59
|
+
const onSubmit = async () => {
|
|
60
|
+
const result = await validate({
|
|
61
|
+
name: [validators.required('Name is required')],
|
|
62
|
+
email: [validators.required('Email is required'), validators.email()],
|
|
63
|
+
})
|
|
64
|
+
if (!result.valid) return
|
|
65
|
+
showConfirm.value = true
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const confirm = () => {
|
|
69
|
+
showConfirm.value = false
|
|
70
|
+
toast.success(`Welcome, ${fields.name}!`)
|
|
71
|
+
reset()
|
|
72
|
+
}
|
|
73
|
+
</script>
|
|
74
|
+
|
|
75
|
+
<template>
|
|
76
|
+
<VibeContainer class="py-4" style="max-width: 480px;">
|
|
77
|
+
<h1 class="mb-4">Sign up</h1>
|
|
78
|
+
|
|
79
|
+
<form @submit.prevent="onSubmit">
|
|
80
|
+
<VibeFormGroup label="Name">
|
|
81
|
+
<VibeFormInput
|
|
82
|
+
v-model="fields.name"
|
|
83
|
+
placeholder="Jane Doe"
|
|
84
|
+
:validation-state="errors.name ? 'invalid' : null"
|
|
85
|
+
:validation-message="errors.name"
|
|
86
|
+
/>
|
|
87
|
+
</VibeFormGroup>
|
|
88
|
+
|
|
89
|
+
<VibeFormGroup label="Email" class="mt-3">
|
|
90
|
+
<VibeFormInput
|
|
91
|
+
v-model="fields.email"
|
|
92
|
+
type="email"
|
|
93
|
+
placeholder="jane@example.com"
|
|
94
|
+
:validation-state="errors.email ? 'invalid' : null"
|
|
95
|
+
:validation-message="errors.email"
|
|
96
|
+
/>
|
|
97
|
+
</VibeFormGroup>
|
|
98
|
+
|
|
99
|
+
<VibeButton type="submit" variant="primary" class="mt-4">
|
|
100
|
+
Create account
|
|
101
|
+
</VibeButton>
|
|
102
|
+
</form>
|
|
103
|
+
|
|
104
|
+
<VibeModal v-model="showConfirm" title="Confirm details" centered>
|
|
105
|
+
<p>Create an account for <strong>{{ fields.email }}</strong>?</p>
|
|
106
|
+
<template #footer>
|
|
107
|
+
<VibeButton variant="secondary" @click="showConfirm = false">Cancel</VibeButton>
|
|
108
|
+
<VibeButton variant="primary" @click="confirm">Confirm</VibeButton>
|
|
109
|
+
</template>
|
|
110
|
+
</VibeModal>
|
|
111
|
+
|
|
112
|
+
<!-- Mount once at the app root; renders the global toast queue. -->
|
|
113
|
+
<VibeToastHost />
|
|
114
|
+
</VibeContainer>
|
|
115
|
+
</template>
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
> `isValid` is `false` until the first `validate()` call, so gate submit on the `validate()` result (as above) rather than disabling the button by `isValid` alone.
|
|
119
|
+
|
|
120
|
+
## Recommended project structure
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
src/
|
|
124
|
+
main.ts # createApp + plugin + initColorMode (before mount)
|
|
125
|
+
App.vue # app shell; mounts <VibeToastHost /> once
|
|
126
|
+
components/ # your reusable components
|
|
127
|
+
composables/ # app-specific composables (useForm wrappers, etc.)
|
|
128
|
+
views/ # route-level pages (if using vue-router)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Keep cross-app concerns at the root: `<VibeToastHost />` belongs in `App.vue`, and `initColorMode()` belongs in `main.ts` — both should appear exactly once.
|
|
132
|
+
|
|
133
|
+
## Coming from BootstrapVue?
|
|
134
|
+
|
|
135
|
+
The mental model is familiar — component-per-feature, data-driven `items` arrays, `v-model` for visibility/value, and props-based styling (`variant`, `size`) — but VibeUI is **not** a drop-in port. Component and prop names differ (`Vibe*` prefix), Bootstrap JS is managed for you, and interactive components lean on `v-model`. Use the [component docs](../README.md#components) to map features rather than expecting identical APIs.
|
|
136
|
+
```
|