@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,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`).
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# VibeUI Form Components
|
|
2
|
+
|
|
3
|
+
Form controls with built-in validation, accessibility, and Bootstrap 5.3 styling.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
VibeUI form controls integrate with Bootstrap and include validation and accessibility out of the box.
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
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
|
|
17
|
+
|
|
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
|
+
```
|
|
34
|
+
|
|
35
|
+
## Components
|
|
36
|
+
|
|
37
|
+
### Inputs
|
|
38
|
+
|
|
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) |
|
|
49
|
+
|
|
50
|
+
### Choice controls
|
|
51
|
+
|
|
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) |
|
|
57
|
+
|
|
58
|
+
### Advanced
|
|
59
|
+
|
|
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) |
|
|
64
|
+
|
|
65
|
+
## Validation
|
|
66
|
+
|
|
67
|
+
See the [Validation Guide](./validation.md) for `useFormValidation`, `useForm`, the built-in `validators`, and async/API validation.
|
|
68
|
+
|
|
69
|
+
```vue
|
|
70
|
+
<script setup lang="ts">
|
|
71
|
+
import { useFormValidation, validators } from '@velkymx/vibeui'
|
|
72
|
+
|
|
73
|
+
const email = useFormValidation('')
|
|
74
|
+
const validateEmail = () =>
|
|
75
|
+
email.validate([validators.required(), validators.email()])
|
|
76
|
+
</script>
|
|
77
|
+
|
|
78
|
+
<template>
|
|
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" />
|
|
85
|
+
</VibeFormGroup>
|
|
86
|
+
</template>
|
|
87
|
+
```
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# VibeAutocomplete
|
|
2
|
+
|
|
3
|
+
Search-as-you-type input backed by an array or async source, with keyboard navigation. Generic over the item type `T`.
|
|
4
|
+
|
|
5
|
+
## Props
|
|
6
|
+
|
|
7
|
+
| Prop | Type | Default | Description |
|
|
8
|
+
|------|------|---------|-------------|
|
|
9
|
+
| `modelValue` | `string` | `''` | The text value of the input (v-model). |
|
|
10
|
+
| `source` | `T[] \| (query: string) => T[] \| Promise<T[]>` | — (required) | Array to filter, or a function returning matches (sync or async). |
|
|
11
|
+
| `itemText` | `(item: T) => string` | `undefined` | Maps an item to its display/filter text. Defaults to the string form of the item. |
|
|
12
|
+
| `minChars` | `number` | `1` | Minimum characters before searching. |
|
|
13
|
+
| `debounce` | `number` | `200` | Debounce in ms before running a query (`0` disables debouncing). |
|
|
14
|
+
| `maxResults` | `number` | `10` | Maximum number of results shown. |
|
|
15
|
+
| `placeholder` | `string` | `''` | Placeholder text. |
|
|
16
|
+
| `label` | `string` | `undefined` | Label text. |
|
|
17
|
+
| `id` | `string` | auto-generated | Element id. |
|
|
18
|
+
| `disabled` | `boolean` | `false` | Disable the input. |
|
|
19
|
+
|
|
20
|
+
## Events
|
|
21
|
+
|
|
22
|
+
| Event | Payload | Description |
|
|
23
|
+
|-------|---------|-------------|
|
|
24
|
+
| `update:modelValue` | `string` | Emitted as the user types and when an item is selected. |
|
|
25
|
+
| `select` | `T` | Emitted with the chosen item when a result is selected. |
|
|
26
|
+
|
|
27
|
+
## Slots
|
|
28
|
+
|
|
29
|
+
| Slot | Scope | Description |
|
|
30
|
+
|------|-------|-------------|
|
|
31
|
+
| `item` | `{ item: T, index: number, label: string }` | Custom rendering for each result row. |
|
|
32
|
+
| `empty` | — | Content shown when there are no results. Defaults to "No results". |
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
|
|
36
|
+
### Array source
|
|
37
|
+
|
|
38
|
+
```vue
|
|
39
|
+
<script setup lang="ts">
|
|
40
|
+
import { ref } from 'vue'
|
|
41
|
+
const query = ref('')
|
|
42
|
+
const fruits = ['Apple', 'Apricot', 'Banana', 'Cherry', 'Mango']
|
|
43
|
+
</script>
|
|
44
|
+
|
|
45
|
+
<template>
|
|
46
|
+
<VibeAutocomplete v-model="query" :source="fruits" label="Fruit" />
|
|
47
|
+
</template>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Async source with typed items
|
|
51
|
+
|
|
52
|
+
```vue
|
|
53
|
+
<script setup lang="ts">
|
|
54
|
+
import { ref } from 'vue'
|
|
55
|
+
|
|
56
|
+
interface User { id: number; name: string }
|
|
57
|
+
const value = ref('')
|
|
58
|
+
|
|
59
|
+
const search = async (q: string): Promise<User[]> => {
|
|
60
|
+
const res = await fetch(`/api/users?q=${encodeURIComponent(q)}`)
|
|
61
|
+
return res.json()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const onSelect = (user: User) => console.log('picked', user.id)
|
|
65
|
+
</script>
|
|
66
|
+
|
|
67
|
+
<template>
|
|
68
|
+
<VibeAutocomplete
|
|
69
|
+
v-model="value"
|
|
70
|
+
:source="search"
|
|
71
|
+
:item-text="(u) => u.name"
|
|
72
|
+
:min-chars="2"
|
|
73
|
+
label="Assignee"
|
|
74
|
+
@select="onSelect"
|
|
75
|
+
>
|
|
76
|
+
<template #item="{ item }">
|
|
77
|
+
<strong>{{ item.name }}</strong> <small class="text-muted">#{{ item.id }}</small>
|
|
78
|
+
</template>
|
|
79
|
+
<template #empty>No users found</template>
|
|
80
|
+
</VibeAutocomplete>
|
|
81
|
+
</template>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Important Notes
|
|
85
|
+
|
|
86
|
+
- **Async race-safety:** in-flight async queries are token-guarded, so only the latest query's results are applied.
|
|
87
|
+
- **Keyboard support:** Arrow Up/Down move the highlight, Enter selects, Escape closes the menu. The combobox exposes `aria-expanded`, `aria-controls`, and `aria-activedescendant`.
|
|
88
|
+
- **Selection text:** selecting an item sets the input to the item's `itemText` (or string) value and emits both `update:modelValue` and `select`.
|
|
89
|
+
- **Outside click:** clicking outside the component closes the menu.
|
|
90
|
+
|
|
91
|
+
## Bootstrap CSS Classes
|
|
92
|
+
|
|
93
|
+
- `.form-control`
|
|
94
|
+
- `.form-label`
|
|
95
|
+
|
|
96
|
+
The dropdown menu, items, and empty state use VibeUI's own `.vibe-autocomplete*` classes (styled with Bootstrap CSS variables).
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# VibeFileInput
|
|
2
|
+
|
|
3
|
+
File attachment input with optional drag-and-drop zone. `v-model` exposes the selected files as `File[]`.
|
|
4
|
+
|
|
5
|
+
## Props
|
|
6
|
+
|
|
7
|
+
| Prop | Type | Default | Description |
|
|
8
|
+
|------|------|---------|-------------|
|
|
9
|
+
| `modelValue` | `File[]` | `[]` | The accepted files (v-model). |
|
|
10
|
+
| `id` | `string` | auto-generated | Element id. |
|
|
11
|
+
| `label` | `string` | `undefined` | Label text. |
|
|
12
|
+
| `multiple` | `boolean` | `false` | Allow selecting multiple files. |
|
|
13
|
+
| `accept` | `string` | `undefined` | Comma-separated MIME types and/or extensions (e.g. `image/*,.pdf`). Used both as the native filter and for rejection. |
|
|
14
|
+
| `maxSize` | `number` | `undefined` | Maximum file size in bytes; larger files are rejected. |
|
|
15
|
+
| `dragDrop` | `boolean` | `false` | Render a drag-and-drop zone instead of the native control. |
|
|
16
|
+
| `disabled` | `boolean` | `false` | Disable the input. |
|
|
17
|
+
| `size` | `'sm' \| 'lg'` | `undefined` | Control size (native control only). |
|
|
18
|
+
| `helpText` | `string` | `undefined` | Help text below the input. |
|
|
19
|
+
| `dropzoneText` | `string` | `'Drag files here or click to browse'` | Text shown inside the drop zone. |
|
|
20
|
+
|
|
21
|
+
## Events
|
|
22
|
+
|
|
23
|
+
| Event | Payload | Description |
|
|
24
|
+
|-------|---------|-------------|
|
|
25
|
+
| `update:modelValue` | `File[]` | Emitted with the accepted files. |
|
|
26
|
+
| `change` | `File[]` | Emitted with the accepted files (mirrors `update:modelValue`). |
|
|
27
|
+
| `invalid` | `File[]` | Emitted with files rejected by `accept` or `maxSize`. |
|
|
28
|
+
|
|
29
|
+
## Slots
|
|
30
|
+
|
|
31
|
+
| Slot | Description |
|
|
32
|
+
|------|-------------|
|
|
33
|
+
| `dropzone` | Custom content for the drop zone (only when `dragDrop` is set). |
|
|
34
|
+
|
|
35
|
+
## Exposed methods
|
|
36
|
+
|
|
37
|
+
| Method | Description |
|
|
38
|
+
|--------|-------------|
|
|
39
|
+
| `clearFiles()` | Clears the native input and emits an empty selection. |
|
|
40
|
+
|
|
41
|
+
## Usage
|
|
42
|
+
|
|
43
|
+
### Native input
|
|
44
|
+
|
|
45
|
+
```vue
|
|
46
|
+
<script setup lang="ts">
|
|
47
|
+
import { ref } from 'vue'
|
|
48
|
+
const files = ref<File[]>([])
|
|
49
|
+
</script>
|
|
50
|
+
|
|
51
|
+
<template>
|
|
52
|
+
<VibeFileInput
|
|
53
|
+
v-model="files"
|
|
54
|
+
label="Attachment"
|
|
55
|
+
accept="image/*,.pdf"
|
|
56
|
+
:max-size="5 * 1024 * 1024"
|
|
57
|
+
help-text="Images or PDF, up to 5 MB."
|
|
58
|
+
/>
|
|
59
|
+
</template>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Drag-and-drop with rejection handling
|
|
63
|
+
|
|
64
|
+
```vue
|
|
65
|
+
<script setup lang="ts">
|
|
66
|
+
import { ref } from 'vue'
|
|
67
|
+
const files = ref<File[]>([])
|
|
68
|
+
|
|
69
|
+
const onInvalid = (rejected: File[]) => {
|
|
70
|
+
console.warn('Rejected:', rejected.map((f) => f.name))
|
|
71
|
+
}
|
|
72
|
+
</script>
|
|
73
|
+
|
|
74
|
+
<template>
|
|
75
|
+
<VibeFileInput
|
|
76
|
+
v-model="files"
|
|
77
|
+
multiple
|
|
78
|
+
drag-drop
|
|
79
|
+
accept="image/*"
|
|
80
|
+
:max-size="2 * 1024 * 1024"
|
|
81
|
+
@invalid="onInvalid"
|
|
82
|
+
/>
|
|
83
|
+
</template>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Important Notes
|
|
87
|
+
|
|
88
|
+
- **Same-file reselection:** the native input is reset after each change so picking the same file again still fires `change`.
|
|
89
|
+
- **Rejection:** files failing `accept` or exceeding `maxSize` are excluded from the model and reported via the `invalid` event.
|
|
90
|
+
- **Drag safety:** a document-level fallback resets the dragging state if a drag ends or drops outside the zone.
|
|
91
|
+
|
|
92
|
+
## Bootstrap CSS Classes
|
|
93
|
+
|
|
94
|
+
- `.form-control`, `.form-control-{sm|lg}`
|
|
95
|
+
- `.form-label`, `.form-text`
|
|
96
|
+
|
|
97
|
+
The drop zone uses VibeUI's own `.vibe-file-input-dropzone*` classes.
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# VibeFormCheckbox
|
|
2
|
+
|
|
3
|
+
Checkbox for a single boolean value or as part of a grouped multi-select (array model).
|
|
4
|
+
|
|
5
|
+
## Props
|
|
6
|
+
|
|
7
|
+
| Prop | Type | Default | Description |
|
|
8
|
+
|------|------|---------|-------------|
|
|
9
|
+
| `modelValue` | `boolean \| string \| number \| (string \| number \| boolean)[]` | `false` | v-model. A boolean (or `value`/`uncheckedValue`) for single checkboxes; an array for grouped checkboxes. |
|
|
10
|
+
| `value` | `string \| number \| boolean` | `true` | Value emitted/added to the array when checked. |
|
|
11
|
+
| `uncheckedValue` | `string \| number \| boolean` | `false` | Value emitted when unchecked (non-array model only). |
|
|
12
|
+
| `id` | `string` | auto-generated | Element id. Auto-generated, or inherited from a parent `VibeFormGroup`. |
|
|
13
|
+
| `label` | `string` | `undefined` | Checkbox label text. |
|
|
14
|
+
| `disabled` | `boolean` | `false` | Disable the checkbox. |
|
|
15
|
+
| `required` | `boolean` | `false` | Mark as required. |
|
|
16
|
+
| `inline` | `boolean` | `false` | Render inline (`.form-check-inline`). |
|
|
17
|
+
| `indeterminate` | `boolean` | `false` | Show the indeterminate (mixed) state. |
|
|
18
|
+
| `reverse` | `boolean` | `false` | Place the label before the checkbox (`.form-check-reverse`). |
|
|
19
|
+
| `validationState` | `'valid' \| 'invalid' \| null` | `null` | Visual validation state. |
|
|
20
|
+
| `validationMessage` | `string` | `undefined` | Feedback message for the current state. |
|
|
21
|
+
| `validationRules` | `ValidationRule[] \| ValidatorFunction` | `undefined` | Rules carried for use with a validation composable. |
|
|
22
|
+
| `validateOn` | `'change' \| 'blur'` | `'change'` | When the `validate` event fires. |
|
|
23
|
+
| `helpText` | `string` | `undefined` | Help text below the checkbox. |
|
|
24
|
+
|
|
25
|
+
## Events
|
|
26
|
+
|
|
27
|
+
| Event | Payload | Description |
|
|
28
|
+
|-------|---------|-------------|
|
|
29
|
+
| `update:modelValue` | `boolean \| string \| number \| (string \| number \| boolean)[]` | Emitted on toggle. Array result when the model is an array. |
|
|
30
|
+
| `change` | `Event` | Native change event. |
|
|
31
|
+
| `blur` | `FocusEvent` | Emitted on blur. |
|
|
32
|
+
| `focus` | `FocusEvent` | Emitted on focus. |
|
|
33
|
+
| `validate` | — | Emitted when the `validateOn` trigger occurs. |
|
|
34
|
+
|
|
35
|
+
## Slots
|
|
36
|
+
|
|
37
|
+
None.
|
|
38
|
+
|
|
39
|
+
## Usage
|
|
40
|
+
|
|
41
|
+
### Single checkbox
|
|
42
|
+
|
|
43
|
+
```vue
|
|
44
|
+
<script setup lang="ts">
|
|
45
|
+
import { ref } from 'vue'
|
|
46
|
+
const agreed = ref(false)
|
|
47
|
+
</script>
|
|
48
|
+
|
|
49
|
+
<template>
|
|
50
|
+
<VibeFormCheckbox v-model="agreed" label="I accept the terms" />
|
|
51
|
+
</template>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Custom checked / unchecked values
|
|
55
|
+
|
|
56
|
+
```vue
|
|
57
|
+
<script setup lang="ts">
|
|
58
|
+
import { ref } from 'vue'
|
|
59
|
+
const status = ref('off')
|
|
60
|
+
</script>
|
|
61
|
+
|
|
62
|
+
<template>
|
|
63
|
+
<VibeFormCheckbox
|
|
64
|
+
v-model="status"
|
|
65
|
+
value="on"
|
|
66
|
+
unchecked-value="off"
|
|
67
|
+
label="Enable feature"
|
|
68
|
+
/>
|
|
69
|
+
</template>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Grouped checkboxes (array model)
|
|
73
|
+
|
|
74
|
+
```vue
|
|
75
|
+
<script setup lang="ts">
|
|
76
|
+
import { ref } from 'vue'
|
|
77
|
+
const selected = ref<string[]>([])
|
|
78
|
+
</script>
|
|
79
|
+
|
|
80
|
+
<template>
|
|
81
|
+
<VibeFormGroup label="Interests">
|
|
82
|
+
<VibeFormCheckbox v-model="selected" value="vue" label="Vue" />
|
|
83
|
+
<VibeFormCheckbox v-model="selected" value="ts" label="TypeScript" />
|
|
84
|
+
<VibeFormCheckbox v-model="selected" value="css" label="CSS" />
|
|
85
|
+
</VibeFormGroup>
|
|
86
|
+
</template>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Important Notes
|
|
90
|
+
|
|
91
|
+
- **Array vs. single model:** if `modelValue` is an array, checking adds `value` and unchecking removes it. Otherwise the model toggles between `value` and `uncheckedValue`.
|
|
92
|
+
- **Indeterminate:** purely visual; it does not change the checked value and is reset once the user toggles.
|
|
93
|
+
- **Group linking:** in a grouped layout, the first checkbox consumes the `VibeFormGroup` id; subsequent checkboxes generate their own ids.
|
|
94
|
+
|
|
95
|
+
## Bootstrap CSS Classes
|
|
96
|
+
|
|
97
|
+
- `.form-check`
|
|
98
|
+
- `.form-check-input`, `.form-check-label`
|
|
99
|
+
- `.form-check-inline`, `.form-check-reverse`
|
|
100
|
+
- `.is-valid`, `.is-invalid`
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# VibeFormDatepicker
|
|
2
|
+
|
|
3
|
+
Date, time, and datetime input using the native browser pickers, with built-in validation and accessibility.
|
|
4
|
+
|
|
5
|
+
## Props
|
|
6
|
+
|
|
7
|
+
| Prop | Type | Default | Description |
|
|
8
|
+
|------|------|---------|-------------|
|
|
9
|
+
| `modelValue` | `string` | `''` | The value (v-model) as an ISO-style string matching the chosen `type` (e.g. `2026-05-28`). |
|
|
10
|
+
| `type` | `'date' \| 'time' \| 'datetime-local' \| 'month' \| 'week'` | `'date'` | Native picker type. |
|
|
11
|
+
| `id` | `string` | auto-generated | Element id. Auto-generated, or inherited from a parent `VibeFormGroup`. |
|
|
12
|
+
| `label` | `string` | `undefined` | Standalone label text. |
|
|
13
|
+
| `disabled` | `boolean` | `false` | Disable the input. |
|
|
14
|
+
| `readonly` | `boolean` | `false` | Make the input read-only. |
|
|
15
|
+
| `required` | `boolean` | `false` | Mark as required. |
|
|
16
|
+
| `min` | `string` | `undefined` | Minimum allowed value. |
|
|
17
|
+
| `max` | `string` | `undefined` | Maximum allowed value. |
|
|
18
|
+
| `size` | `'sm' \| 'lg'` | `undefined` | Control size. |
|
|
19
|
+
| `validationState` | `'valid' \| 'invalid' \| null` | `null` | Visual validation state. |
|
|
20
|
+
| `validationMessage` | `string` | `undefined` | Feedback message for the current state. |
|
|
21
|
+
| `validationRules` | `ValidationRule[] \| ValidatorFunction` | `undefined` | Rules carried for use with a validation composable. |
|
|
22
|
+
| `validateOn` | `'input' \| 'blur' \| 'change'` | `'blur'` | When the `validate` event fires. |
|
|
23
|
+
| `helpText` | `string` | `undefined` | Help text below the input. |
|
|
24
|
+
|
|
25
|
+
## Events
|
|
26
|
+
|
|
27
|
+
| Event | Payload | Description |
|
|
28
|
+
|-------|---------|-------------|
|
|
29
|
+
| `update:modelValue` | `string` | Emitted on input. |
|
|
30
|
+
| `input` | `Event` | Native input event. |
|
|
31
|
+
| `change` | `Event` | Native change event. |
|
|
32
|
+
| `blur` | `FocusEvent` | Emitted on blur. |
|
|
33
|
+
| `focus` | `FocusEvent` | Emitted on focus. |
|
|
34
|
+
| `validate` | — | Emitted when the `validateOn` trigger occurs. |
|
|
35
|
+
|
|
36
|
+
## Slots
|
|
37
|
+
|
|
38
|
+
None.
|
|
39
|
+
|
|
40
|
+
## Usage
|
|
41
|
+
|
|
42
|
+
### Recommended: inside a VibeFormGroup
|
|
43
|
+
|
|
44
|
+
```vue
|
|
45
|
+
<script setup lang="ts">
|
|
46
|
+
import { ref } from 'vue'
|
|
47
|
+
const startDate = ref('')
|
|
48
|
+
</script>
|
|
49
|
+
|
|
50
|
+
<template>
|
|
51
|
+
<VibeFormGroup label="Start Date">
|
|
52
|
+
<VibeFormDatepicker v-model="startDate" type="date" min="2026-01-01" />
|
|
53
|
+
</VibeFormGroup>
|
|
54
|
+
</template>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Standalone
|
|
58
|
+
|
|
59
|
+
```vue
|
|
60
|
+
<script setup lang="ts">
|
|
61
|
+
import { ref } from 'vue'
|
|
62
|
+
const meetingTime = ref('')
|
|
63
|
+
</script>
|
|
64
|
+
|
|
65
|
+
<template>
|
|
66
|
+
<VibeFormDatepicker v-model="meetingTime" label="Meeting Time" type="datetime-local" />
|
|
67
|
+
</template>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Important Notes
|
|
71
|
+
|
|
72
|
+
- **String model only:** `modelValue` must be a string. When using a validation composable, bind to `.value` (e.g. `v-model="field.value"`), not the field object — a dev-mode warning is logged otherwise.
|
|
73
|
+
- **Native pickers:** rendering and locale formatting come from the browser's native input, so the on-screen appearance varies by platform.
|
|
74
|
+
- **Group linking:** wrapped in a `VibeFormGroup`, the input consumes the group id so the label and feedback link automatically.
|
|
75
|
+
|
|
76
|
+
## Bootstrap CSS Classes
|
|
77
|
+
|
|
78
|
+
- `.form-control`
|
|
79
|
+
- `.form-control-{sm|lg}`
|
|
80
|
+
- `.is-valid`, `.is-invalid`
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# VibeFormGroup
|
|
2
|
+
|
|
3
|
+
Wrapper that owns a form control's label, help text, validation feedback, and id. It is the recommended way to make any VibeUI form control accessible.
|
|
4
|
+
|
|
5
|
+
## How it works
|
|
6
|
+
|
|
7
|
+
`VibeFormGroup` generates a single id and provides it (plus its label/help/validation flags) to descendant controls. The **first** child control consumes the group id and renders against it; the group renders the `<label>` (linked via `for`), `help-text`, and validation feedback so the child does not duplicate them. This auto-wires `for`/`id` and `aria-describedby` with no manual ids.
|
|
8
|
+
|
|
9
|
+
## Props
|
|
10
|
+
|
|
11
|
+
| Prop | Type | Default | Description |
|
|
12
|
+
|------|------|---------|-------------|
|
|
13
|
+
| `label` | `string` | `undefined` | Label text rendered for the first child control. |
|
|
14
|
+
| `labelFor` | `string` | `undefined` | Explicit id to use instead of the auto-generated one. |
|
|
15
|
+
| `required` | `boolean` | `false` | Append a `*` to the label. |
|
|
16
|
+
| `validationState` | `'valid' \| 'invalid' \| null` | `null` | Render valid/invalid feedback. |
|
|
17
|
+
| `validationMessage` | `string` | `undefined` | Feedback message text. |
|
|
18
|
+
| `helpText` | `string` | `undefined` | Help text rendered below the control. |
|
|
19
|
+
| `floating` | `boolean` | `false` | Use Bootstrap floating-label layout (`.form-floating`). |
|
|
20
|
+
| `row` | `boolean` | `false` | Use a horizontal (grid row) layout. |
|
|
21
|
+
| `labelCols` | `number \| string` | `undefined` | Label column width (`col-sm-{n}`) in row layout. |
|
|
22
|
+
| `labelAlign` | `'start' \| 'center' \| 'end'` | `undefined` | Label text alignment in row layout. |
|
|
23
|
+
|
|
24
|
+
## Events
|
|
25
|
+
|
|
26
|
+
None.
|
|
27
|
+
|
|
28
|
+
## Slots
|
|
29
|
+
|
|
30
|
+
| Slot | Description |
|
|
31
|
+
|------|-------------|
|
|
32
|
+
| default | The form control(s) to wrap. |
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
|
|
36
|
+
### Basic
|
|
37
|
+
|
|
38
|
+
```vue
|
|
39
|
+
<script setup lang="ts">
|
|
40
|
+
import { ref } from 'vue'
|
|
41
|
+
const email = ref('')
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
<template>
|
|
45
|
+
<VibeFormGroup label="Email" help-text="We'll never share it.">
|
|
46
|
+
<VibeFormInput v-model="email" type="email" />
|
|
47
|
+
</VibeFormGroup>
|
|
48
|
+
</template>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### With validation
|
|
52
|
+
|
|
53
|
+
```vue
|
|
54
|
+
<script setup lang="ts">
|
|
55
|
+
import { useFormValidation, validators } from '@velkymx/vibeui'
|
|
56
|
+
|
|
57
|
+
const name = useFormValidation('')
|
|
58
|
+
const validateName = () => name.validate([validators.required()])
|
|
59
|
+
</script>
|
|
60
|
+
|
|
61
|
+
<template>
|
|
62
|
+
<VibeFormGroup
|
|
63
|
+
label="Name"
|
|
64
|
+
required
|
|
65
|
+
:validation-state="name.validationState"
|
|
66
|
+
:validation-message="name.validationMessage"
|
|
67
|
+
>
|
|
68
|
+
<VibeFormInput v-model="name.value" @validate="validateName" />
|
|
69
|
+
</VibeFormGroup>
|
|
70
|
+
</template>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Horizontal (row) layout
|
|
74
|
+
|
|
75
|
+
```vue
|
|
76
|
+
<script setup lang="ts">
|
|
77
|
+
import { ref } from 'vue'
|
|
78
|
+
const username = ref('')
|
|
79
|
+
</script>
|
|
80
|
+
|
|
81
|
+
<template>
|
|
82
|
+
<VibeFormGroup label="Username" row :label-cols="3" label-align="end">
|
|
83
|
+
<VibeFormInput v-model="username" />
|
|
84
|
+
</VibeFormGroup>
|
|
85
|
+
</template>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Floating label
|
|
89
|
+
|
|
90
|
+
```vue
|
|
91
|
+
<script setup lang="ts">
|
|
92
|
+
import { ref } from 'vue'
|
|
93
|
+
const city = ref('')
|
|
94
|
+
</script>
|
|
95
|
+
|
|
96
|
+
<template>
|
|
97
|
+
<VibeFormGroup label="City" floating>
|
|
98
|
+
<VibeFormInput v-model="city" placeholder="City" />
|
|
99
|
+
</VibeFormGroup>
|
|
100
|
+
</template>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Important Notes
|
|
104
|
+
|
|
105
|
+
- **Single-consumer rule:** only the first child control consumes the group id. With multiple controls (e.g. a checkbox or radio group), subsequent controls generate their own ids — the group's label still describes the set.
|
|
106
|
+
- **Don't double up:** when the group provides `label`, `helpText`, or validation, omit those same props on the child control; the child suppresses its own copies to avoid duplicates.
|
|
107
|
+
- **Floating labels** require the label to render after the control, which the group handles automatically; pair it with a `placeholder` on the input.
|
|
108
|
+
|
|
109
|
+
## Bootstrap CSS Classes
|
|
110
|
+
|
|
111
|
+
- `.mb-3`, `.row`
|
|
112
|
+
- `.form-floating`
|
|
113
|
+
- `.form-label`, `.col-form-label`, `.col-sm-{n}`, `.text-{start|center|end}`
|
|
114
|
+
- `.form-text`
|
|
115
|
+
- `.valid-feedback`, `.invalid-feedback`
|